[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 结构的实例有以下属性。
- Set.prototype.constructor:构造函数,默认就是 Set 函数。
- Set.prototype.size:返回 Set 实例的成员总数。
Set 实例的操作方法:
- add(value):添加某个值,返回 Set 结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为 Set 的成员。
- clear():清除所有成员,没有返回值。
Set 实例的遍历方法:
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- 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 对象的设计目的有这样几个。
- 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
- 修改某些 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 迭代器
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 模块化
参考前端模块化