虽然在编写项目的过程中,也会用到ES6的语法以及新特性。但感觉学习的不是特别系统,索性这两天重新刷了一下Understanding The ES6,也对ES6有了更深的理解。这里,针对感觉应用比较多,知识点比较重要的部分做了一下总结。内容有点多,因此预计将分为三个部分。

块级绑定-作用域

在ES5中,有一个知识难点,就是变量提升和作用域。而这一部分之所以让人困扰,则是因为JS中变量的声明方式会影响变量实际的创建位置。

有一条规则是:使用var关键字声明的变量,无论其实际声明位置在何处,都会被声明与函数的顶部,这就是变量提升(hosting)。

ES6中为了让变量的生命周期更加可控,引入了块级声明。即,让所声明的变量在指定块的作用域外无法被访问。ES6主要引入了两个新的关键字,letconst

let

letvar类似,不同是的,let声明的变量不会存在变量提升的情况。因此,下面这个代码中:

1
2
3
4
5
{
let a = 1;
}

console.log(a);

是无法访问到a变量的。

另一个限制的地方是,禁止重复声明相同的变量。

1
2
var a = 1;
let a = 2;

上面的代码也会报错。

const

这个关键字也是限制了变量提升以及重复声明,而且变量值是不可更改的。

1
2
const a = 1;
a = 2; //报错

但是,如果绑定的是对象,可以更改对象属性值。

1
2
3
const person = { 'name': 'xiaoming', 'age': 12};
person.name = 'zhangsan';
console.log(person.name); //zhangsan

暂时性死区

利用letconst声明的变量,代码在访问到声明处前都是禁止访问的。

1
2
3
let a = 2;
a = b; // b is not defined
let b = 3;

循环中的let和const声明

请看下面这段代码:

1
2
3
4
5
6
7
8
9
var array = [];

for(var i = 0 ; i < 10; i++) {
array.push(function() {
console.log(i);
});
}

array.forEach(function(func) { func(); }); //会输出10次10

使用var声明的变量,会在每次循环中被共享了。因此,在循环内部创建的函数都用于同一个变量的引用。通常解决这种方式,在ES5中会采用闭包的方式。而ES6中,可以使用let关键字

1
2
3
4
5
6
7
8
9
var array = [];

for(let i = 0 ; i < 10; i++) {
array.push(function() {
console.log(i);
});
}

array.forEach(function(func) { func(); }); //会依次输出0...9

这是因为let关键字在每次循环中,都会创建一个新的i变量,而循环内部的函数获取的i变量其实都是副本。

const关键字不可以用在传统的for循环中,但是可以用在for...offor … in语法中。使用效果和let一样。

函数

ES6中对函数的功能做了新的扩展,使之使用起来更方便。

箭头函数

自从有了箭头函数,在遇到回调函数写法的时候,就特别方便。当然它的功能肯定不止于此。箭头函数的语法像这样:

1
2
3
4
5
(变量名,[其他变量名]) => { //函数体} 
//有以下几种变形
(value1, value2) => {return value1 + value2;}
(value1, value2) => value1 + value2; //函数体只有一条return语句的时候,可以省略return关键字和{}
value => value1 + 1; //单参数函数时候,可以省略括号

是传统函数写法的一种便捷写法,同时,有以下几个特点:

  1. 不允许重复的具名参数
  2. 没有arguments对象
  3. 不能更改this,在整个函数声明周期内其值会保持不变,因此,在遇到this问题时,不用再像ES5之前的时期使用类似var self =thisbind(this)之类的方法了。
  4. 不能被new调用
  5. 没有this、super、arguments、也没有new.target绑定

默认参数和不具名参数

先看下面这个例子,同时使用了默认参数和不具名参数(也叫做剩余参数):

1
2
3
4
5
6
7
8
9
10
11
//num1如果不传值或者为undefined的话为0, num2是一个数组代表后续传进来的参数集合
function add(num1 = 0, ...num2) {
num2.forEach(i => num1 = num1 + i);
return num1;
}

console.log(add(undefined,2)); //2

console.log(add(undefined,2,3)); //5

console.log(add(1,2,3)); //6

采用了默认参数值时,仍然可以使用arguments对象来反应真实函数的调用状态, 而arguments对象也可以和不具名参数进行协同工作。

1
2
3
4
5
6
7
8
function add(num1 = 1, num2 = 1) {
console.log(arguments);
}

add(1); //[1]
add(2, 3); //[2,3]
add(); //[]
add(2,3,4,5,6); //[2,3,4,5,6]

同时,函数的默认值甚至可以利用函数来动态生成,而非写死的,如:

1
2
3
4
5
6
7
8
9
10
11
function getNum() {
return parseInt(Math.random() * 10);
}

function add(num1 = getNum(), num2 = 1) {
console.log(num1 + num2);
}

//每次调用的结果是随机的,以下为某一次调用的结果
add(); // 8
add(); // 4

需要注意的是,函数的不具名参数是不能够在对象的setter上使用的,这是因为setter只接受单一值作为它的参数。

扩展运算符

这三个点运算符用在函数声明的时候,就是不具名参数,但是它同时也能用在解构对象上,因此,在函数调用过程中,可以使用该运算符,进行多个参数传递。如

1
2
var randomArray = [2,3,1231,455,231,23,553];
console.log(Math.max(...randomArray)); //1231

new.target

这是ES6函数对象的元属性,可以通过检查new.target对象是否被定义,可以判断函数是否通过new进行调用。

1
2
3
4
5
6
7
8
9
10
function Cat() {
if(new.target === undefined) {
console.log('this is not called by NEW keyword');
}else {
console.log('this is called by NEW keyword');
}
}

new Cat(); //this is called by NEW keyword
Cat.call(this); //this is not called by NEW keyword

原理方面,其实就是当函数的构造器(constructor)方法被调用时,new.target 会被填入 new 运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的 this 值。而若call方法被执行的时候, new.target 的值则会是undefined

解构

解构的意思是将数据结构分解为更小的部分,而ES6中引入了解构的方式目的就是能够更好地提取数据。

##对象解构
直接上例子:

1
2
3
4
let { type, name } = node //普通解构
let { type, name, value = true } = node; //默认值解构
let { type: localType, name: localName } = node; //赋值给不同的本地对象
let { loc: { start: localStart }} = node; //嵌套的解构方式,等同于localStart = node.loc.start

以上需要注意的是,当解构赋值表达式的右侧( = 后面的表达式)的计算结果为 null 或 undefined 时,会抛出错误。

##数组解构

数组解构时,解构作用在数组内部的位置上,而不是作用在对象的具名属性上。

1
2
3
4
5
6
7
8
9
let [ , , thirdColor ] = colors;

let [ firstColor, secondColor = "green" ] = colors; //默认值

let [ firstColor, [ secondColor ] ] = colors; //嵌套解构

let [ firstColor, ...restColors ] = colors; //剩余项

[a, b] = [b, a]; //数组解构赋值有一个非常独特的用例,能轻易地互换两个变量的值

参数解构

1
2
3
function doSomething(value1, value2, {value3, value4, value5}) {

}

需要注意的是默认情况下调用函数时未给参数解构传值会抛出错误。但若你要求它是可选的,可以给解构的参数提供默认值来处理这种行为。

1
2
3
function dosomething(value1, value2, {value3, value4, value5} = {}) {
//dosomething
}

扩展的对象功能

在ES6中也对对象的使用方式做了进一步的扩展,使其无论是在代码编写形式层面还是底层操作对象的层面都有了更多的特性。

对象类别

在ES6规范中,定义了对象的每种类别:

1.普通对象:拥有JS对象所有默认的内部行为

2.奇异对象:有别于默认的内部行为的对象

3.标准对象:是在ES6中被定义的对象,可以是普通对象也可以是奇异对象。

4.内置对象:在脚本开始运行时由JS运行环境(浏览器或Node)提供的对象。所有标准对象都是内置对象。

属性初始化器

当对象的一个属性名称与本地变量名相同的时候,可以省略冒号和值,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function createPerson(name, age) {
return {
name,
age
}
}
//等同于
function createPerson(name, age) {
return {
name: name,
age: age
}
}

方法简写

方法简写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
name: 'nihao',
getName() {
return this.name;
}
}

//等同与
var person = {
name: 'nihao',
getName: function() {
return this.name;
}
}

需要注意的是,使用方法简写,在方法内部可以使用super方法,而传统的写法是无法使用的。

计算性属性名

属性名可以使用拼接的写法,如:

1
2
3
4
5
6
7
8
9
var name = 'name';

var person = {
['first' + name]: 'scq',
['last' + name]: '000',
getName() {
return this.firstname + this.lastname;
}
}

对于某些需要动态生成属性名的场合,写法更加方便。

Object.is方法

为了避免在对象比较过程中的强制对象转换。通常该方法的运行结果和===一样,但是+0-0,NaNNaN不相同。

Object.assign(source, target) 方法

该方法接受一个接收者,以及任意数量的供应者,并会返回接收者。我通常在使用的时候,用来做继承或者说深度拷贝??。

1
2
3
4
5
6
7
8
9
const person = {
name: 'scq000',
age: 23,
addressInfo: {
city: 'zs',
address: 'some address'
}
}
const person2 = Object.assign({}, person);

Object.assign() 方法接受任意数量的供应者,而接收者会按照供应者在参数中的顺序来依次接收它们的属性。这意味着在接收者中,第二个供应者的属性可能会覆盖第一个供应者的。

关于重复的对象字面量属性

ES6 移除了重复属性的检查,严格模式与非严格模式都不再检查重复的属性。当存在重复属性时,排在后面的属性的值会成为该属性的实际值。

自有属性的枚举顺序

1.所有的数字类型键,按升序排列。

2.所有的字符串类型键,按被添加到对象的顺序排列。

3.所有的符号类型键,也按添加顺序排列。

修改对象的原型

对象原型的实际值被存储在一个内部属性[[Prototype]] 上, Object.getPrototypeOf() 方法会返回此属性存储的值,而 Object.setPrototypeOf() 方法则能够修改该值。ES6 通过添加 Object.setPrototypeOf()方法而改变了这种假定,此方法允许你修改任意指定对象的原型。它接受两个参数:需要被修改原型的对象,以及将会成为前者原型的对象。
因为这个特性的添加,可以使用 super 进行简单的原型访问。super 是指向当前对象的原型的一个指针,实际上就是 Object.getPrototypeOf(this) 的值。这个功能在使用ES6类的继承的时候,提供了更好的访问父类的方式。

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
constructor(name, size) {
this.name = name;
this.size = size;
}
}

class Cat extends Animal {
constructor(size) {
super('cat', size);
}
}