[toc]

Symbol

==ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型。==

1
2
let s = Symbol();
typeof s // "symbol"

调用 Symbol() 方法将创建一个新的 Symbol 类型的值,并且该值不与其它任何值相等,每个 Symbol 都是独一无二的

1
2
3
4
5
6
7
8
9
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false

用途:

  • 扩展对象属性名:ES5 的对象属性名都是字符串,这容易造成属性名的冲突。

Set 和 Map 数据结构

ES6 提供了新的数据结构 Set、Map 来弥补 ES5 在数据结构上的不足,类似 Java 的 Map 和 Set。

JS 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了 Map 和 Set,这样就有了四种数据集合。

Set

Set 类似于数组,但是成员的值都是唯一的,没有重复的值。

==使用建议:若每一个集合中的元素都是唯一的,这时就用 Set 不用 Array。==

Set 结构的实例有以下属性。

  1. Set.prototype.constructor:构造函数,默认就是 Set 函数。
  2. Set.prototype.size:返回 Set 实例的成员总数。

Set 实例的操作方法:

  1. add(value):添加某个值,返回 Set 结构本身。
  2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. has(value):返回一个布尔值,表示该值是否为 Set 的成员。
  4. clear():清除所有成员,没有返回值。

Set 实例的遍历方法:

  1. keys():返回键名的遍历器
  2. values():返回键值的遍历器
  3. entries():返回键值对的遍历器
  4. forEach():使用回调函数遍历每个成员
1
2
3
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
s // Set(4) {2, 3, 5, 4}
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
let list = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => list.add(x));
for (let i of list) {
console.log(i); // 2 3 5 4
}

// 添加
list.add(x)

// 获取 Set 的长度
list.size

// 判断是否存在某值
list.has(3)

// 删除
list.delete(1)

// 清空
list.clear()

// 遍历
for (let i of list) {
console.log(i);
}

for (let [key, value] of list.entries()) {
console.log(`${key},${value}`);
}

list.forEach((item) => {
console.log(item);
});

Set 用途:

  • 数组去重

Map

Map 类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

==使用建议:一般的,只有在模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value形式的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。==

Map 操作方法:

  • size
  • set(key, value)
  • get(key)
  • has(key)
  • delete(key)
  • clear()
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
35
36
// 声明、添加
let list = new Map();
list.set('key','value');
console.log(list); // {"key" => "value"}

let arr = [['k1', 'v1'], ['k2', 'v2'], ['k3', 'v3']];
let list = new Map(arr);
console.log(list); // {"k1" => "v1", "k2" => "v2", "k3" => "v3"}

// 获取 Map 的长度
list.size

// 获取
list.get('key'); // value

// 判断是否存在某值
list.has('key'); // true

// 删除
list.delete('key'); // true

// 清空
list.clear();

// 遍历
for (let i of list) {
console.log(i);
}

for (let [key, value] of list.entries()) {
console.log(`${key},${value}`);
}

list.forEach((item) => {
console.log(item);
});

Proxy 代理器

代理(Proxy)和反射(Reflect)是 Java 框架的底层原理。

  • 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。
    这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
    核心思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
  • JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    对于任意一个对象,都能够调用它的任意方法和属性;
    这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,即用自己的定义覆盖了语言的原始定义。所以 Proxy 属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截。

1
2
3
4
// new Proxy() 表示生成一个Proxy实例
// target 参数表示所要拦截的目标对象
// handler 参数也是一个对象,用来定制拦截行为。
var proxy = new Proxy(target, handler);
1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
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
// 原始对象
let obj = {
name: '白小明',
age: 22
};

// 新建一个 Proxy 实例
let objProxy = new Proxy(obj, {
// 拦截对象属性的读取
get(target, key) {
return target[key].replace('白小明', '小明');
},
// 拦截对象设置属性
set(target, key, value) {
if (key === 'name') {
return (target[key] = value);
} else {
return target[key];
}
}
});

console.log(objProxy.name); // 小明

objProxy.name = '小白';
objProxy.age = 18;
console.log(objProxy); // Proxy {name: "小白", age: 22}

Reflect 反射器

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。

Reflect 对象的方法与 Proxy 对象的方法一一对应,只是 Reflect 不用 new

1
2
Reflect.get(target, key) {}
Reflect.set(target, key, value) {}

Reflect 对象的设计目的有这样几个。

  1. 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
  2. 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc)则会返回 false。
1
2
3
4
5
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

Decorator 装饰器

Decorator 是一个函数,用来修改类的行为。许多面向对象的语言都有修饰器的特性,如 Java 的注解机制。

目前,该功能还是一个提案。可以使用一个 babel 插件来让 babel 支持 Decorator 函数,并设置 .babelrc

1
npm i babel-plugin-transform-decorators-legacy --save-dev
1
2
3
4
5
6
7
8
{
"presets": [
"es2015"
],
"plugins": [
"babel-plugin-transform-decorators-legacy"
]
}

实例:使用 Decorator 函数限制某个属性是只读的

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
/**
* 实例:使用 Decorator 函数限制某个属性是只读的
* @param {*} target 修改的类
* @param {*} name 修改的类的属性
* @param {*} descriptor 该属性的描述对象
*/
let readOnly = (target, name, descriptor) => {
descriptor.writable = false;
return descriptor;
}

class Test {
@readOnly
say() {
console.log("hello");
}
}

let t = new Test();

t.say();
// hello

t.say = () => { console.log("hello2"); }
// Uncaught TypeError: Cannot assign to read only property 'say' of object '#<Test>'

实例:使用 Decorator 函数实现前端埋点

将埋点逻辑与业务逻辑解耦

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
/**
* 实例:使用 Decorator 函数实现前端日志埋点
* @param {*} target 修改的类
* @param {*} name 修改的类的属性
* @param {*} descriptor 该属性的描述对象
*/
let logBurying = (type) => {
return (target, name, descriptor) => {
let originMethod = descriptor.value; // 原始函数体

descriptor.value = (...args) => {
originMethod.apply(target, args);
console.info(`logBurying ${type}`); // 模拟埋点
}
}
}

class AD {
@logBurying("show")
show() {
console.log("show");
}

@logBurying("click")
click() {
console.log("click");
}
}

let t = new AD();

t.show(); // show, logBurying show
t.click(); // click, logBurying click

core-decorators

core-decorators 是一个第三方库,实现了许多实用的 Decorator 函数。

https://github.com/jayphelps/core-decorators

Iterator 迭代器

  • ==Iterator(迭代器)的概念==

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了 Map 和 Set,这样就有了四种数据集合。

Iterator(迭代器)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员),我们就称这种数据结构是“可遍历的”(iterable)。

原生具备 Iterator 接口的数据结构如下:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}

// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}

// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'

扩展运算符会默认调用 Iterator 接口,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。

1
2
3
4
5
6
7
8
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即 for…of 循环

1
2
3
4
5
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
console.log(v); // red green blue
}

for…of 循环可以代替数组实例的 forEach 方法。

1
2
3
4
5
6
7
8
9
10
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
console.log(v); // red green blue
}

arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});

Promise 对象

参考《前端通讯——异步编程 · 解决方案》

Generator 状态机

参考《前端通讯——异步编程 · 解决方案》

async/await

参考《前端通讯——异步编程 · 解决方案》

Class 类

参考《JavaScript 面向对象》

Module 模块化

参考前端模块化