变量
(一)const与let以及var
1、三者之间的区别
· 1.变量提升
- var声明 无论声明在何处,都会被视为声明在函数的最顶部
-
· 2.作用域
var是函数作用域,在函数内部作用,但是{}里是一样会提升的
-
· 3.重复声明
var可以重复定义
-
· 4.const常量不可修改
const声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了
· 5.如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:
const student = { name: 'cc' }
student.name = 'yy';// 不报错
student = { name: 'yy' };// 报错
2. 暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
· 上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
有几个点需要注意:
let 关键词声明的变量不具备变量提升(hoisting)特性
let 和 const 声明只在最靠近的一个块中(花括号内)有效
当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
const 在声明时必须被赋值
3. 建议
在日常开发中,我的建议是全面拥抱let/const,一般的变量声明使用let关键字,而当声明一些配置项
(类似接口地址,npm依赖包,分页器默认页数等一些一旦声明后就不会改变的变量)的时候可以使用const,
来显式的告诉项目其他开发者,这个变量是不能改变的(const声明的常量建议使用全大写字母标识,单词间
用下划线),同时也建议了解var关键字的缺陷(变量提升,污染全局变量等),这样才能更好的使用新语法
函数
(一)箭头函数(Arrow Functions)
·ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;
1.箭头函数对于使用finction关键字创建的函数有以下区别:
- 箭头函数没有arguments(建议使用更好的语法,剩余运算符替代)
- 箭头函数没有prototype属性,不能用作构造函数(不能用new关键字调用)
- 箭头函数没有自己this,它的this是词法的,引用的是上下文的this,即在你写这行代码的时候就箭头函数的this就已经和外层执行上下文的this绑定了(这里个人认为并不代表完全是静态的,因为外层的上下文仍是动态的可以使用call,apply,bind修改,这里只是说明了箭头函数的this始终等于它上层上下文中的this)
2. 箭头函数最直观的三个特点
不需要 function 关键字来创建函数
省略 return 关键字
继承当前上下文的 this 关键字
· 细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;
3. 规则
- 使用了块语句的箭头函数不会自动返回值,你需要使用return语句将所需值返回。
- 不可以当作构造函数,即,不可以使用new 关键字来实例化对象,否则会抛出一个错误。
- 不可以使用arguments对象,更不能通过arguments对象访问传入参数,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数.
- 返回的就是一个对象, 需要在外面加一个括号 var getTempItem = id = > ({id: id,});
(二) 函数的参数默认值
// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
printText();// default
(三) Promise(常用)
Promise作为ES6中推出的新的概念,改变了JS的异步编程,现代前端大部分的异步请求都是使用Promise实现,
fetch这个web api也是基于Promise的,这里不得简述一下之前统治JS异步编程的回调函数,回调函数有什么
缺点,Promise又是怎么改善这些缺点
回调函数缺点
- 多重嵌套,导致回调地狱
- 代码跳跃,并非人类习惯的思维模式
- 信任问题,你不能把你的回调完全寄托与第三方库,因为你不知道第三方库到底会怎么执行回调(多次执行)
- 第三方库可能没有提供错误处理
- 不清楚回调是否都是异步调用的(可以同步调用ajax,在收到响应前会阻塞整个线程,会陷入假死状态,非常不推荐)
Promise
针对回调函数这么多缺点,ES6中引入了一个新的概念Promise,Promise是一个构造函数,通过new关键字创
建一个Promise的实例,来看看Promise是怎么解决回调函数的这些问题
- 不清楚回调是否都是异步调用的(可以同步调用ajax,在收到响应前会阻塞整个线程,会陷入假死状态,非常不推荐)
Promise在设计的时候保证所有响应的处理回调都是异步调用的,不会阻塞代码的执行,Promise将then方法的回调放入一个叫微任务的队列中(MicroTask),确保这些回调任务在同步任务执行完以后再执行,这部分同样也是事件循环的知识点,有兴趣的朋友可以深入研究一下
建议
在日常开发中,建议全面拥抱新的Promise语法,其实现在的异步编程基本也都使用的是Promise
- 建议使用ES7的async/await进一步的优化Promise的写法,async函数始终返回一个Promise,await可以实现一个”等待”的功能,async/await被成为异步编程的终极解决方案,即用同步的形式书写异步代码,并且能够更优雅的实现异步代码顺序执行以及在发生异步的错误时提供更精准的错误信息
字符串
(一) 字符串模板
- 需要拼接字符串的时候尽量改成使用模板字符串:
// bad
const foo = 'this is a' + example;
// good
const foo = `this is a ${example}`;
而对ES6来说
- 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
- ES6反引号(``)直接搞定;
数组
for … of循环
- for … of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构并且返回各项的值,和ES3中的for … in的区别如下
- for … of只能用在可迭代对象上,获取的是迭代器返回的value值, for … in 可以获取所有对象的键名
- for … in会遍历对象的整个原型链,性能非常差不推荐使用, 而for … of只遍历当前对象不会遍历它的原型链
- 对于数组的遍历,for … in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性), for … of只返回数组的下标对应的属性值
- for … of循环的原理其实也是利用了可迭代对象内部部署的iterator接口,如果将for … of循环分解成最原始的for循环,内部实现的机制可以这么理解
·可以看到只要满足第二个条件(iterator.next()存在且res.done为true)就可以一直循环下去,并且每次把迭代器的next方法生成的对象赋值给res,然后将res的value属性赋值给for … of第一个条件中声明的变量即可,res的done属性控制是否继续遍历下去
3. for… of循环同时支持break,continue,return(在函数中调用的话)并且可以和对象解构赋值一起使用
- arr数组每次使用for … of循环都返回一对象({a:1},{a:2},{a:3}),然后会经过对象解构,寻找属性为a的值,赋值给obj.a,所以在每轮循环的时候obj.a会分别赋值为1,2,3
对象
(一) 对象属性/方法简写(常用)
1. es6允许当对象的属性和值相同时,省略属性名
需要注意的是**
结合上文的解构赋值,这里的代码会其实是声明了x,y,z变量,因为bar函数会返回一个对象,这个对象有x,y,z这3个属性,解构赋值会寻找等号右边表达式的x,y,z属性,找到后赋值给声明的x,y,z变量
3. 方法简写
es6允许当一个对象的属性的值是一个函数(即是一个方法),可以使用简写的形式
Module模块化(常用)
在ES6 Module出现之前,模块化一直是前端开发者讨论的重点,面对日益增长的需求和代码,
需要一种方案来将臃肿的代码拆分成一个个小模块,从而推出了AMD,CMD和CommonJs这3种模块化方案,
前者用在浏览器端,后面2种用在服务端,直到ES6 Module出现
- ES6 Module是静态的,也就是说它是在编译阶段运行,和var以及function一样具有提升效果(这个特点使得它支持tree shaking)
- 自动采用严格模式(顶层的this返回undefined)
- ES6 Module支持使用export {<变量>}导出具名的接口,或者export default导出匿名的接口
心得:ES6不仅支持变量的导出,也支持常量的导出。 export const sqrt = Math.sqrt;//导出常量 心得:一条import 语句可以同时导入默认函数和其它变量。import default Method, { otherMethod } from ‘xxx.js’;
module.js导出
import导入
- 这两者的区别是,export {<变量>}导出的是一个变量的引用,export default导出的是一个值
- 什么意思呢,就是说在a.js中使用import导入这2个变量的后,在module.js中因为某些原因x变量被改变了,那么会立刻反映到a.js,而module.js中的y变量改变后,a.js中的y还是原来的值
ES6 Module和CommonJs的一些区别
- CommonJs输出的是一个值的拷贝,ES6 Module通过export {<变量>}输出的是一个变量的引用,export default输出的是一个值
- CommonJs运行在服务器上,被设计为运行时加载,即代码执行到那一行才回去加载模块,而ES6 Module是静态的输出一个接口,发生在编译的阶段
- CommonJs在第一次加载的时候运行一次并且会生成一个缓存,之后加载返回的都是缓存中的内容
import()动态加载
- 关于ES6 Module静态编译的特点,导致了无法动态加载,但是总是会有一些需要动态加载模块的需求,所以现在有一个提案,使用把import作为一个函数可以实现动态加载模块,它返回一个Promise,Promise被resolve时的值为输出的模块
Vue中路由的懒加载的ES6写法就是使用了这个技术,使得在路由切换的时候能够动态的加载组件渲染视图
解构赋值
理解
解构赋值可以直接使用对象的某个属性,而不需要通过属性访问的形式使用,对象解构原理个人认为是通过寻找相同的属性名,然后原对象的这个属性名的值赋值给新对象对应的属性, 键找键,找到了就赋值了
解构数组
var arr = [1, 2, 3, 4];
let [a, b, c, d] = arr;
console.log(a); // 1
console.log(b); // 2
复制代码
解构对象
var luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;
console.log(occupation); // jedi
console.log(father); // anakin
复制代码
vuex使用对象解构
建议
同样建议使用,因为解构赋值语意化更强,对于作为对象的函数参数来说,可以减少形参的声明,直接使用对象的属性(如果嵌套层数过多我个人认为不适合用对象解构,不太优雅)
剩余运算符rest/扩展运算符(常用)
剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(…),后面跟着一个含有iterator接口的数据结构
扩展运算符
- 以数组为例,使用扩展运算符使得可以”展开”这个数组,可以这么理解,数组是存放元素集合的一个容器,而使用扩展运算符可以将这个容器拆开,这样就只剩下元素集合,你可以把这些元素集合放到另外一个数组里面, 代替ES3中数组原型的concat方法
剩余运算符
剩余运算符最重要的一个特点就是替代了以前的arguments rest只是形参, 可以随意取名
- 访问函数的arguments对象是一个很昂贵的操作,以前的arguments.callee,arguments.caller都被废止了,建议在支持ES6语法的环境下不要在使用arguments对象,使用剩余运算符替代(箭头函数没有arguments,必须使用剩余运算符才能访问参数集合)
- 剩余运算符可以和数组的解构赋值一起使用,但是必须放在最后一个,因为剩余运算符的原理其实是利用了数组的迭代器,它会消耗3个点后面的数组的所有迭代器,读取所有迭代器生成对象的value属性,剩运算符后不能在有解构赋值,因为剩余运算符已经消耗了所有迭代器,而数组的解构赋值也是消耗迭代器,但是这个时候已经没有迭代器了,所以会报错
这里first会消耗右边数组的一个迭代器,…arr会消耗剩余所有的迭代器,而第二个例子…arr直接消耗了所有迭代器,导致last没有迭代器可供消耗了,所以会报错,因为这是毫无意义的操作
区别
剩余运算符和扩展运算符的区别就是,剩余运算符会收集这些集合,放到右边的数组中,扩展运算符是将右边的数组拆分成元素的集合,它们是相反的
在对象中使用扩展运算符
这个是ES9的语法,ES9中支持在对象中使用扩展运算符,之前说过数组的扩展运算符原理是消耗所有迭代器,但对象中并没有迭代器,我个人认为可能是实现原理不同,但是仍可以理解为将键值对从对象中拆开,它可以放到另外一个普通对象中
其实它和另外一个ES6新增的API相似,即Object.assign,它们都可以合并对象,但是还是有一些不同Object.assign会触发目标对象的setter函数,而对象扩展运算符不会
建议
使用扩展运算符可以快速的将类数组转为一个真正的数组
- 合并多个数组
函数柯理化
类(class) (ES6)
对熟悉Java,object-c,c#等纯面向对象语言的开发者来说,都会对class有一种特殊的情怀。ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解。
class Animal {
// 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');//实例化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错.
// 如果没有置顶consructor,默认带super函数的constructor将会被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
复制代码
includes
includes() 函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false。
includes 函数与 indexOf 函数很相似,下面两个表达式是等价的:
arr.includes(x)
arr.indexOf(x) >= 0
复制代码
接下来我们来判断数字中是否包含某个元素:
在ES7之前的做法
使用indexOf()验证数组中是否存在某个元素,这时需要根据返回值是否为-1来判断:
let arr = ['react', 'angular', 'vue'];
if (arr.indexOf('react') !== -1)
{
console.log('react存在');
}
复制代码
使用ES7的includes()
使用includes()验证数组中是否存在某个元素,这样更加直观简单:
let arr = ['react', 'angular', 'vue'];
if (arr.includes('react'))
{
console.log('react存在');
}
复制代码
指数操作符
在ES7中引入了指数运算符**
,**
具有与Math.pow(..)
等效的计算结果。
不使用指数操作符
使用自定义的递归函数calculateExponent或者Math.pow()进行指数运算:
function calculateExponent(base, exponent)
{
if (exponent === 1)
{
return base;
}
else
{
return base * calculateExponent(base, exponent - 1);
}
}
console.log(calculateExponent(2, 10)); // 输出1024
console.log(Math.pow(2, 10)); // 输出1024
复制代码
复制代码
使用指数操作符
使用指数运算符**,就像+、-等操作符一样:
console.log(2**10);// 输出1024