ES6学习笔记

let 和 const 对比

ES6 新增了letconst,它们声明的变量,都处于“块级作用域”。并且不存在“变量提升”,不允许重复声明。

同时,const声明的变量所指向的内存地址保存的数据不得改变:

  • 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
  • 对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),不能保证指向的数据结构不可变。

如果要保证指向的数据结构也不可变,需要自行封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 冻结对象
* @param {Object} obj
* @return {Object}
*/
function constantize(obj) {
if (Object.isFrozen(obj)) {
return obj;
}
Reflect.ownKeys(obj).forEach(key => {
// 如果属性是对象,递归冻结
typeof obj[key] === "object" && (obj[key] = constantize(obj[key]));
});
return Object.freeze(obj);
}
/********测试代码 **********/
const obj = {
a: 1,
b: {
c: 2,
d: {
a: 1
}
},
d: [1, 2]
};
const fronzenObj = constantize(obj);
try {
fronzenObj.d = [];
fronzenObj.b.c = 3;
} catch (error) {
console.log(error.message);
}

Set 和 Map对比

题目:解释下Set和Map。

  • Set 元素不允许重复
  • Map 类似对象,但是它的键(key)可以是任意数据类型

①Set 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实例化一个set
const set = new Set([1, 2, 3, 4]);
// 遍历set
for (let item of set) {
console.log(item);
}
// 添加元素,返回Set本身
set.add(5).add(6);
// Set大小
console.log(set.size);
// 检查元素存在
console.log(set.has(0));
// 删除指定元素,返回bool
let success = set.delete(1);
console.log(success);
set.clear();

其他遍历方法:由于没有键名,values()keys()返回同样结果。

1
2
3
4
5
6
7
8
9
10
for (let item of set.keys()) {
console.log(item);
}
for (let item of set.values()) {
console.log(item);
}
for (let item of set.entries()) {
console.log(item);
}

②Map 常用方法

Map 接口基本和 Set 一致。不同的是增加新元素的 API 是:set(key, value)

1
2
3
4
5
6
7
const map = new Map();
// 以任意对象为 Key 值
// 这里以 Date 对象为例
let key = new Date();
map.set(key, "today");
console.log(map.get(key));

Proxy

他可以实现 js 中的“元编程”:在目标对象之前架设拦截,可以过滤和修改外部的访问。

它支持多达 13 种拦截操作,例如下面代码展示的setget方法,分别可以在设置对象属性和访问对象属性时候进行拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const handler = {
// receiver 指向 proxy 实例
get(target, property, receiver) {
console.log(`GET: target is ${target}, property is ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`SET: target is ${target}, property is ${property}`);
return Reflect.set(target, property, value);
}
};
const obj = { a: 1, b: { c: 0, d: { e: -1 } } };
const newObj = new Proxy(obj, handler);
/**
* 以下是测试代码
*/
newObj.a; // output: GET...
newObj.b.c; // output: GET...
newObj.a = 123; // output: SET...
newObj.b.c = -1; // output: GET...

运行这段代码,会发现最后一行的输出是 GET ...。也就是说它触发的是get拦截器,而不是期望的set拦截器。这是因为对于对象的深层属性,需要专门对其设置 Proxy

更多请见《阮一峰 ES6 入门:Proxy》

generator 函数

generator函数是 es6 提供的新特性,它的最大特点是:控制函数的执行。让我们从网上最火的一个例子来看:

1
2
3
4
5
6
7
8
9
10
function* foo(x) {
var y = 2 * (yield x + 1);
var z = yield y / 3;
return x + y + z;
}
var b = foo(5);
b.next(); // { value:6, done:false }
b.next(12); // { value:8, done:false }
b.next(13); // { value:42, done:true }

通俗的解释下为什么会有这种输出:

  1. 给函数 foo 传入参数 5,但由于它是 generator,所以执行到第一个 yield 前就停止了。
  2. 第一次调用 next(),这次传入的参数会被忽略暂停__。
  3. 第二次调用 next(12),传入的参数会被当作上一个 yield 表达式的返回值。因此,y = 2 * 12 = 24。执行到第二个 yield,返回其后的表达式的值 24 / 3 = 8。然后函数在此处暂停。
  4. 第三次调用 next(13),没有 yield,只剩 return 了,按照正常函数那样返回 return 的表达式的值,并且donetrue

难点:在于为什么最后的value是 42 呢?

首先,x的值是刚开始调用 foo 函数传入的 5。而最后传入的 13 被当作第二个 yield 的返回值,所以z的值是 13。对于y的值,我们在前面第三步中已经计算出来了,就是 24。

所以,x + y + z = 5 + 24 + 13 = 42

看懂了上面的分析,再看下面这段代码就很好理解了:

1
2
3
4
5
6
7
8
9
10
function* foo(x) {
var y = 2 * (yield x + 1);
var z = yield y / 3;
return x + y + z;
}
var a = foo(5);
a.next(); // Object{value:6, done:false}
a.next(); // Object{value:NaN, done:false}
a.next(); // Object{value:NaN, done:true}

只有第一次调用 next 函数的时候,输出的 value 是 6。其他时候由于没有给 next 传入参数,因此 yield 的返回值都是undefined,进行运算后自然是NaN