es6 学习笔记

es6 学习笔记 参考 阮一峰的ES6教程

let 和 const

es6 中新增了 let 和 const 命令,用来声明变量。let 声明的变量,只在 let 命令所在的代码块内有效。

let 必须先声明,后使用。

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。被称作“暂时性死区” (temporal dead zone,简称 TDZ)

const 用来声明一个只读的常量,声明后,常量的值就不能改变。

变量的结构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

1
let [a, b, c] = [1, 2, 3];

这种写法也可以称作“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

如果解析不成功,值为 undefined

1
2
let [foo] = [];
let [bar, foo] = [1];

不完全解构,即等号左边的模式,只匹配一部分等号右边的数组,这种情况下依旧可以解构成功。

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

如果右边不是数组(或者说不是可遍历的结构),会报错。

… op 为剩余运算符

1
2
3
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]

字符串的拓展

模板字符串

1
2
3
4
5
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

函数的拓展

可以给函数加默认值

1
2
3
4
5
6
7
function log(x, y = 'World') {
console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

参数的默认值可以和结构赋值结合使用。

作用域

一旦设置参数的默认值,函数进行声明结构初始化时,参数会形成一个单独的作用域,等初始化完成后,作用域就会消失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var x = 1;

function f(x, y = x) {
console.log(y);
}

/**
* 调用 f 时,单独形成一个作用域,此时的 y = 参数 x,而不是全局变量 x
**/
f(2) // 2

let x = 1;

function f(y = x) {
let x = 2;
console.log(y);
}

/**
* 调用 f 时,形成作用域,因为在这个作用域没有 x,所以使用了全局变量 x 的值,后续局部变量影响不到全部变量的值
* 所以是全局变量的值。
**/
f() // 1

利用参数默认值,可以指定一个参数不得省略,如果省略就抛出一个错误。

1
2
3
4
5
6
7
8
9
10
function throwIfMissing() {
throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}

foo()
// Error: Missing parameter

rest 参数

es6 中引入了 rest 参数 (形式为 …变量名),类似 java 中的可变参数。用于获取函数多余的参数,这样就不需要使用 arguments 对象了。

rest 参数实际上就是一个数组。

要注意,rest 参数后不能有其他参数,所以 rest 参数必须是最后一个参数。

函数的 length 属性里,不包括 rest 参数。

严格模式

ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

name 属性

函数的 name 属性,返回该函数的

ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

如果将一个具有名字的函数赋值给一个变量,es5和es6都会返回这个函数原有的名字

1
2
3
4
5
6
7
const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

箭头函数

1
2
3
4
5
6
7
8
9
// 语法规则
var f = (参数1,参数2,参数3) => 函数体

var f = v => v;

// 等同于
var f = function (v) {
return v;
};

如果箭头函数不需要参数或者需要多个参数,要用一个圆括号代码参数列表。

如果箭头函数代码块多于一条语句,要用 {} 将它们括起来。由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

1
let getTempItem = id => ({ id: id, name: "Temp" });

使用箭头函数时,要注意

  1. 函数体内的 this 对象,就是定义所在的对象,而不是使用所在的对象。
  2. 不可以当作构造函数,也就是说,不能使用 new 命令。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

除了 this, 以下三个变量在箭头函数中也是不存在的,指向外层函数的对应变量:arguments, super, new.target。

箭头函数不适用常见

第一个场合是定义对象的方法,且该方法内部包括 this。

因为对象不构成单独的作用域,所以 this 指向会指向全局,在某些情况下导致错误发生。

第二个场合是需要动态this的时候,也不应使用箭头函数。

对象的拓展

属性简洁写法

ES6 允许直接写入变量和函数,作为对象的属性和方法

1
2
3
4
5
6
7
8
9
10
11
function f(x, y) {
return {x, y};
}

// 等同于

function f(x, y) {
return {x: x, y: y};
}

f(1, 2) // Object {x: 1, y: 2}

方法简写

1
2
3
4
5
6
7
8
9
10
11
12
13
const o = {
method() {
return "Hello!";
}
};

// 等同于

const o = {
method: function() {
return "Hello!";
}
};

注意,简洁写法的属性名总是字符串

1
2
3
4
5
6
7
8
9
const obj = {
class () {}
};

// 等同于

var obj = {
'class': function() {}
};

属性名表达式

JavaScript 定义对象的属性,有两种方法。

1
2
3
4
5
// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

如果使用字面量方式定义对象(直接使用大括号),在ES5中只能使用方法一定义属性。

ES6 中允许在字面量方式定义对象时,采用方法二作为对象的属性名。

但要注意:属性名表达式与简洁表示法,不能同时使用,会报错。

1
2
3
4
5
6
7
8
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};

要注意:如果属性名表达式是一个对象,会自动将对象转换为字符串 “[object Object]”。

1
2
3
4
5
6
7
8
9
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

可枚举性

对象的每一个属性都有一个描述对象 (Descriptor),用来控制该属性的行为。

Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

1
2
3
4
5
6
7
8
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

描述对象的 enumerable 属性,称作 “可枚举性”,如果该属性为 false ,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略 enumerablefalse 的属性。

  1. for…in循环:只遍历对象自身的和继承的可枚举的属性。
  2. Object.keys():返回对象自身的所有可枚举的属性的键名。
  3. JSON.stringify():只串行化对象自身的可枚举的属性。
  4. Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

其中第四条为 ES6 中新增的。

引入可枚举性,就是为了在某些操作下,可以规避进行规避,避免所有的内部属性和方法都被遍历。

另外,ES6规定,所有的Class的原型方法都是不可枚举的。

属性的遍历

ES6 中一共有5种方法可以遍历对象的属性。

  1. for…in
    • for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
  2. Object.keys(obj)
    • Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
  3. Object.getOwnPropertyNames(obj)
    • Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
  4. Object.getOwnPropertySymbols(obj)
    • Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
  5. Reflect.ownKeys(obj)
    • Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

以上5种方法遍历对象的键名,都遵守同样的属性遍历次序规则

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
1
2
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

Symbol

ES6 中引入了新的原始数据类型 Symbol,用来表示一个独一无二的值。

Symbol 值通过函数 Symbol 生成。

Symbol 函数可以接受一个字符串作为参数,用来作为 Symbol 的描述,从而来更好的区分 Symbol 。