ECMAScript6学习笔记
ECMAScript6发布于2015年,是下一代ECMAScript的标准。当前ES6的支持情况如下:
- 最新的浏览器:Chrome 54、Opera 41、Safari 10、Firefox 50、Edge 14、iOS 10等已经支持ES6的大部分特性
 - 利用Babel等JavaScript编译器,可以把ES6编译为老旧浏览器支持的脚本。使用Babel 6.5 + core-js 2.4可以支持大部分的ES6特性。编译器的存在意味着你可以放心的使用ES6编写代码,却不需要担心浏览器能否支持
 - 在服务器端,Node.js 6.5、7.x支撑大部分ES6特性
 
JavaScript原本由网景公司发明,它将其提交给标准化组织ECMA管理,ECMA发布语言标准后,将其命名为ECMAScript。可以认为我们常说的JavaScript是ECMAScript标准的实现。
本节介绍ES7(2016)版本引入的新特性。
判断数组是否包含指定的值。
指数操作符。
本节介绍ES8(2017)版本引入的新特性。
async用于声明一个函数是异步的。await用于等待一个异步函数执行完成。await只能出现在async函数的函数体中(不能是内嵌函数的函数体)。
async会导致一个函数的返回值变成Promise:
| 
					 1 2 3 4 5 6  | 
						async function testAsync() {     return "hello async"; } const result = testAsync(); console.log(result);  // Promise { 'hello async' }  | 
					
对于上面这个返回直接量的函数,async会直接利用 Promise.resolve()将其封装为Promise。 如果函数没有返回值,则将undefined封装为Promise。
await用于等待一个表达式的结果:
- 如果表达式的结果是直接量,则直接返回
 - 如果表达式的结果是一个Promise,则它会阻塞直到该Promoise被resolve
 
本质上async就是Promise的语法糖。下面的例子,让代码变得简洁:
| 
					 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 37 38 39 40 41 42 43 44 45 46 47 48 49  | 
						function takeLongTime(n) {     return new Promise(resolve => {         setTimeout(() => resolve(n + 200), n);     }); } function step1(n) {     console.log(`step1 with ${n}`);     return takeLongTime(n); } function step2(n) {     console.log(`step2 with ${n}`);     return takeLongTime(n); } function step3(n) {     console.log(`step3 with ${n}`);     return takeLongTime(n); } // Promise语法 function doIt() {     console.time("doIt");     const time1 = 300;     step1(time1)         .then(time2 => step2(time2))         .then(time3 => step3(time3))         .then(result => {             console.log(`result is ${result}`);             console.timeEnd("doIt");         }); } doIt(); // async/wait语法 async function doIt() {     console.time("doIt");     const time1 = 300;     const time2 = await step1(time1);     const time3 = await step2(time2);     const result = await step3(time3);     console.log(`result is ${result}`);     console.timeEnd("doIt"); } doIt();  | 
					
要注意await后面的Promise可能会被Rejected,因此最好:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
						async function f() {   try {     await returnPromise();   } catch (err) {     console.log(err);   } } // 或者 async function f() {   await returnPromise().catch(function (err){     console.log(err);   }); }   | 
					
返回所有属性的值。 Object.keys()用于返回所有属性的名字。
返回所有属性键值对的数组。
字符串补白。
返回对象自身属性的描述符。
创建通用的原始二进制数据缓冲区,类似于ArrayBuffer。
提供SharedArrayBuffer上的原子操作。
ES引入了一系列的新语法,本章内容将一一介绍。
该关键字类似于var,用于声明一个变量。但是,let声明的变量的作用域仅仅在当前代码块。也就是说,let为JavaScript引入了块级作用域:
| 
					 1 2 3 4 5 6  | 
						{     var user1 = 'meng';     let user0 = 'alex'; } user1; user0;  // ReferenceError: user0 is not defined  | 
					
let关键字适用于for循环中的计数器变量定义。
let变量不存在var变量那种所谓变量提升(Hoisting)的特性,也就是说,必须先声明后使用,否则会报ReferenceError错。
而且,只要在一个代码块中的任何地方声明了 let varname ,则块外部声明的任何名为varname的变量都被屏蔽:
| 
					 1 2 3 4 5 6  | 
						var varname = true; {     varname = false;  // ReferenceError: varname is not defined     typeof varname;   // ReferenceError: varname is not defined 而不是返回undefined     let varname; }  | 
					
上例中,在let声明出现之前,变量varname不可用的现象称为temporal dead zone。
所谓全局对象,是指浏览器中的window/self变量、Node.js中的global变量。在全局作用域通过var声明的变量,自动成为全局对象的属性。
ES6规定,以var、function声明的全局变量,仍然是全局对象的属性,但是以let、const、class关键字声明的全局变量,不作为全局对象的属性。
let关键字为JavaScript引入了块级作用域,其特性如下:
- 块级作用域可以无限嵌套
 - 内部块可以看到外部块的let变量
 - 内部块可以覆盖外部块的let变量定义
 - 外部块不能读写内部块定义的let变量
 
有了块级作用域后,以防止污染命名空间为目的的立即调用函数表达式(IIFE)不再需要:
| 
					 1 2 3 4 5 6 7 8  | 
						// IIFE风格 (function () {     var tmp = {}; }()); // 等价的块作用域风格 {     let tmp = {}; }  | 
					
ES5规定,函数只能在全局作用域,或者函数作用域的最外层声明,不能定义在代码块中。但是,很多浏览器没有遵循此标准,仍然允许在代码块中声明函数。
ES6引入了块级作用域,在代码块中声明的函数,仅仅在块内可见,就好像加了let声明的变量一样。 但是,为了 避免兼容性问题,ES6允许在浏览器环境中运行的JavaScript不遵循此规定,而是:
- 允许在块级作用域中声明函数
 - 函数声明会提升到全局或者函数作用域
 
由于不同浏览器的行为不同,应该避免在块级作用域声明函数,如果一定要声明,应该使用函数表达式语法:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						{     // 使用这种语法,明确指定函数仅仅在块中可用     let func = function(){     }     // 这种语法,可能会导致提升,由于浏览器的差异可能导致意外行为     function func(){     } }  | 
					
该关键字用于声明一个常量,常量一旦声明,其值就不能改变。在声明常量式必须同时指定初始化式,否则会报错:
| 
					 1 2  | 
						const PI; // SyntaxError: Missing initializer in const declaration  | 
					
const变量在作用域上的特征、与全局对象的关系,与let变量一致。
对于对象、数组等类型,const实际上仅仅限定变量指针的不变性,这类似于Java中的常量。要实现C语言那样的常量,可以调用ES5引入的 Object.freeze() 方法。
JavaScript语言采用了基于原型的继承机制:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						// 构造函数,定义了一个类型 function Fruit( weight ) {     // 构造函数中,可以使用this访问正在构造的对象     this.weight = weight; } // 构造函数的prototype属性,即为类型的原型对象,每个实例都继承原型的属性、方法 Fruit.prototype.getWeight = function () {     return this.weight; } function Apple( weight ) {     this.weight = weight; } // 可以设置原型对象,达到继承的效果 Apple.prototype = new Fruit(); var apple = new Apple( 1.2 ); console.log( apple.getWeight() ); // 1.2  | 
					
这种机制与Java、Python等基于类的继承机制差异很大,容易让JavaScript新手困惑。
ES6引入类似于传统OO语言的类型定义语法:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						// 定义类型 class Fruit {     // 定义构造函数     constructor( weight ) {         this.weight = weight;         // 默认返回this,但是你可以返回任意对象     }     // 方法之间不需要逗号间隔,不需要function关键字     getWeight() {         return this.weight;     } }  | 
					
这种语法本质上仅仅是语法糖,并没有改变JavaScript的继承机制 :
| 
					 1 2 3  | 
						console.log( typeof Fruit );  // 类型仍然是function // class块中的方法,仍然是声明在原型对象上的 console.log( Fruit.prototype.getWeight  );  | 
					
注意:
- class块中的constructor函数,不通过new是无法调用的,会报错
 - class块中的定义的方法,都是不可枚举的
 - 除非显式的定义在this上面,属性/方法都是定义在原型对象上的:
1234var f = new Fruit( 1.2 );console.log( f.hasOwnProperty( 'weight' ) ); // trueconsole.log( f.hasOwnProperty( 'getWeight' ) ); // falseconsole.log( f.__proto__.hasOwnProperty( 'getWeight' ) ); // true - 类的所有实例,共享一个原型对象
 - 不存在Hoisting,即必须先声明class才能使用:
12new Fruit(); // ReferenceError: Fruit is not definedclass Fruit {} - class块内部,默认是严格模式
 
类似于函数,class也支持以表达式方式声明:
| 
					 1 2 3 4 5 6  | 
						const Fruit = class F {     getClassName() {         return F.name;     } }; console.log( new Fruit().getClassName() ); // F  | 
					
上面的例子中,对外暴露出来的类名是Fruit,F仅仅可以在class内部使用,代表当前类,F可以省略。
可以定义一个立即构造的匿名类:
| 
					 1 2 3 4 5  | 
						let apple = new class {     constructor( weight ) {         this.weight = weight;     } }( 1.2 );  | 
					
class块中定义的所有方法都对外部可见,尽管如此,我们可以利用Symbol的唯一性,来定义无法引用的私有方法:
| 
					 1 2 3 4 5  | 
						const checkWeight = Symbol( 'checkWeight' ); class Fruit {     [checkWeight]() {     } }  | 
					
ES6允许定义类的继承关系,使用类似于Java的extends关键字:
| 
					 1 2 3 4 5 6 7  | 
						// 使用extends关键字继承 class Apple extends Fruit {     constructor( weight ) {         // 调用父构造器         super( weight );     } }  | 
					
在构造子类的实例时,必须先创建父类的实例,并在其基础上添加新的属性/方法。 子类构造器必须调用父类的构造器,然后才能使用this关键字。
属性__proto__用于读写一个对象的原型对象。子类的__proto__等于父类,代表了构造函数之间的继承关系:
| 
					 1  | 
						Apple.__proto__ === Fruit // true  | 
					
子类的prototype属性的__proto__属性,等于父类的prototype属性,代表了原型对象之间的继承关系:
| 
					 1  | 
						Apple.prototype.__proto__ === Fruit.prototype // true  | 
					
子类实例的__proto__的__proto__属性,等于父类实例的__proto__属性。
此关键字可以:
- 代表父类的构造函数,只能出现在子类的构造函数中
 - 代表父类的原型对象,注意定义在父类实例上的方法/属性,无法通过super访问到。当通过super调用父类的方法/属性时,方法自动绑定到子类的this,这意味着属性值会直接设置到子类实例上:
12345678910class Fruit {}class Apple extends Fruit {constructor() {super();super.name = 'Apple';console.log( super.name ); // undefinedconsole.log( this.name ); // Apple}}new Apple(); 
在ES5中,无法扩展Boolean、Number、String、Array、Date、Function、RegExp、Error、Object等原生构造函数。这是因为子类型无法获得原生构造函数的内部属性。
ES5是先创建子类的this,然后再把父类的属性添加到此this上,而父类的内部属性无法获取。
ES6则是先创建父类的this,再基于子类构造函数修饰这个this,因此父类的所有行为都可以继承:
| 
					 1 2 3 4 5 6 7 8  | 
						class NegIdxArray extends Array {     constructor() {         super( ...arguments );     } } var nia = new NegIdxArray(); nia[ 0 ] = 0; console.log( nia.length ); // 打印1,行为正常  | 
					
当继承Object时,需要注意,ES6禁止向Object()传递任何参数。
可以在class块内定义getter/setter,其行为与ES5保持一致:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
						class Fruit {     constructor( weight ) {         this._weight = weight;     }     get weight() {         return this._weight;     }     set weight( weight ) {         console.log( `Setting weight: ${weight}`);         this._weight = weight;     } } var f = new Fruit( 1.2 ); f.weight = 1.5; // Setting weight: 1.5  | 
					
可以在class块内部定义生成器方法,例如下面的例子:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						class Foo {     constructor( ...args ) {         this.args = args;     }     // 将Foo变为Iterable类型     * [Symbol.iterator]() {         for ( let arg of this.args ) {             yield arg;         }     } }  | 
					
可以在方法定义上添加static限定符,这样的方法不会被实例继承,但是可以被子类继承。静态方法只能通过类名调用:
| 
					 1 2 3 4 5 6 7 8 9  | 
						class Fruit {     static newInstance() {         return new Fruit();     } } class Apple extends Fruit { } console.log( Fruit.newInstance() ); // Fruit {}  | 
					
ES6规定,class内部仅仅包含方法的定义,不包括任何属性。要声明属性,可以:
- ClassName.prop:这样的属性是静态属性,必须在class块外部定义
 - this.prop:这样的属性是实例属性,必须在构造函数、实例方法中使用
 
ES7包含了静态属性语法的提案,现在可以通过Babel转换器得以支持,语法如下:
| 
					 1 2 3 4  | 
						class ClassName {     static staticProp = 0;     instanceProp = 0; }  | 
					
ES6允许使用new.target这个表达式:
- 如果构造函数不是通过new关键字调用,则在构造函数中new.target的值为undefined,否则返回当前类的名称
 - 以new调用子类构造函数时,new.target总是等于子类的名字,即使该表达式出现在父类构造器中
 
使用new.target,我们可以定义“抽象类”:
| 
					 1 2 3 4 5 6 7  | 
						class Shape {     constructor() {         if ( new.target === Shape ) {             throw new Error( `Type Shape is abstract.` );         }     } }  | 
					
JavaScript一直没有模块体系,不能自然的把大型应用程序拆分为相互依赖的小文件,并通过简单的方法拼装。
在ES6之前,一些开源框架引入了模块化方案,例如CommonJS和AMD,两者分别用于服务器和浏览器。现在ES6在语言层面上添加了通用的模块化支持。
CommonJS、AMD之类的框架,都是在运行时完成模块依赖关系的处理、导入/导出变量的推导的,例如:
| 
					 1 2 3 4 5 6  | 
						// CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;  | 
					
上述代码相当于整体的加载fs模块生成一个临时对象,然后再读取该对象的属性,导出为变量的。这是典型的运行时加载,因为临时对象必须在运行时才能创建。相比之下,ES6中的模块并非对象,采用的加载方式是静态加载,利用 import 、 export 关键字显式的导入/导出变量:
| 
					 1  | 
						import { stat, exists, readFile } from 'fs';   | 
					
如果要在浏览器中引用一个ES6模块,需要使用如下标签:
| 
					 1  | 
						<script type="module" src="foo.js"></script>  | 
					
注意:ES6模块动采用严格模式,不需要在模块头部显式指定。
该关键字用于规范模块的对外接口。一个模块是一个独立文件,其内部所有变量,外部无法读取。如果希望某个变量被外部访问,就必须使用export关键字来导出此变量,export语句可以位于模块作用域的任何位置,不能位于块、函数体内部。示例:
| 
					 1 2 3 4 5 6 7 8 9  | 
						let name = 'Alex'; let age = 30; // 导出方式一: export let name; // 导出方式二:导出多个变量。这种方式更好,在脚本尾部统一导出,容易看清导出的内容 export {name, age};  // 函数、类的导出语法类似 export function func(){} export class cls{}  | 
					
默认情况下,导出变量的名字与其在模块中的名字一致,但可以使用as关键字修改为任意名字:
| 
					 1 2 3 4 5  | 
						export {     name as userName,     age as userAge,     age as userAge2   // 同一变量可以导出多次 };  | 
					
导出的变量,与模块内对应变量是动态绑定的,在运行期间,模块内变量的值发生变化则导出变量也跟着变化:
| 
					 1 2 3  | 
						export var num = 1; setTimeout( () => num = 2, 1000 ); // 在一秒后,任何导入num的脚本中num的值均为2  | 
					
这一动态绑定的特性与CommonJS完全不同,CommonJS的导出变量不会动态更新。
该关键字用于输入其它模块提供的功能。一旦某个模块通过export暴露了某些变量,其它JS文件就可以通过import命令引用它们:
| 
					 1 2  | 
						// 不需要模块的指定.js后缀 import {name, age} from './user';  | 
					
import也支持as关键字:
| 
					 1  | 
						import {name as userName} from './user';  | 
					
import具有Hoisting效果,即import语句总是相当于在脚本最顶端执行。
使用通配符 * 可以导入模块所有暴露的变量:
| 
					 1 2  | 
						import * as user from './user'; console.log( user.name );  | 
					
通配符导入,会自动忽略模块中的默认导出。
通过import命令导入变量时,你必须知道这些变量的导出名。模块作者可以指定一个变量为“默认导出”:
| 
					 1 2 3 4 5 6 7 8 9  | 
						// 默认导出函数,可以是匿名函数 export default function () { } // 默认导出变量 var name = 'Alex'; export default name; // 默认导出类 export default class {}  | 
					
这样,导入该模块的JS,可以用任意名字指代此默认导出:
| 
					 1  | 
						import customName from './export-default';  | 
					
进行默认导入时,变量名周围不能加大括号。
ES6模块,导出的是变量引用,而CommonJS导出的则是值的拷贝。
CommonJS导入的变量,与定义它的模块中的那个变量没有联动关系,各自独立变化。
ES6导入的变量,是一个“常指针”,不能对导入的变量进行重新赋值:
| 
					 1 2 3 4  | 
						import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError  | 
					
ES6的导入是在编译期处理的,这意味着,要导入什么模块在编译期就必须确定,不能依赖于运行时:
| 
					 1 2 3 4 5  | 
						const path = './' + fileName; // CommonJS 运行时决定加载的模块 const myModual = require(path);  // ES6 不支持: import * from path; // 语法错误:string literal expected  | 
					
目前有一个提案,建议引入 import() 函数,以支持运行时加载。该函数与CommonJS的 require() 不同之处是,它是异步加载,返回一个Promise。
目前的浏览器、Nodejs都不支持ES6模块。除了Babel以外,还可以使用:
- ES6模块编译器:可以将ES6模块转换为CommonJS或者AMD模块,以便在浏览器中使用
 - SystemJS:支持在浏览器中加载ES6、AMD、CommonJS模块,并将其转换为ES5代码:
123456<script src="system.js"></script><script>// import方法是异步的,返回一个Promise对象// 这里的app.js可以使用ES6模块语法System.import('./app.js');</script> 
ES6引入一种新语法,允许当赋值语句的左值、右值遵循相同的模式(Pattern)时,进行自动赋值。
| 
					 1 2 3 4  | 
						let [a, b, c] = [ 1, 2, 3 ]; // 1 2 3 [ a, [ [ b ], c ] ] = [ 4, [ [ 5, 0 ], 6 ] ]; // 4 5 6 [ a, b, c ] = [ , , 9 ]; // 4 5 9 [ a, b ] = [ 1, 2, 3 ]; // 1 2 9   | 
					
除了let变量,var、const变量也支持使用解构赋值。解构赋值可以部分成功,失败的变量自动赋值为undefined。
实际上,任何Iterator都可以作为数组解构的右值:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						// Set结构 let [a, b, c] = new Set( [ 0, 0, 0 ] ); // 生成器函数 function* gnerator() {     let a = 0;     while ( true ) {         yield ++a;     } } [ a, b, c ] = gnerator() console.log( [ a, b, c ] ); // [ 1, 2, 3 ]  | 
					
前面提到过,结构赋值失败时,变量赋值为undefined(即使此变量赋值前是有值的)。ES6允许设置解构赋值的默认值,语法如下:
| 
					 1 2  | 
						// 在变量名后面增加 = value let [a,b = 2,c = 3] = [ 1, null ];  | 
					
判断Pattern对应位置是否有值时,ES6使用严格相等操作符===与undefined比较,因此上面的例子中,b的默认值2不会生效。
默认值可以指定为一个表达式,该表达式仅仅在需要时(Pattern对应位置为undefined)才会求值:
| 
					 1 2 3 4  | 
						function defaultValue() {     console.log( 'This function won't be invoked' ); } let [a = defaultValue()] = [ null ];  | 
					
与适用于Iterator的方括号语法类似,对象语法也可以用于解构赋值:
| 
					 1 2  | 
						const { a, b, c }={ a: 1, b: 2, c: 3 } console.log( c ); // 3  | 
					
方括号语法根据位置去匹配,而对象解构赋值使用属性名去匹配,位置顺序不重要。
如果变量名与属性名不一样,必须使用下面的语法:
| 
					 1 2  | 
						const { propname:varname }={ propname: 1 }; console.log( varname );// 1  | 
					
对象解构也可以设置默认值: const { propname:varname = 1 }={}
对已声明的变量进行解构赋值,可能会被JavaScript引擎当作代码块来解释进而导致语法错误:
| 
					 1 2 3 4  | 
						var a, b, c; { a, b, c } = { a: 1, b: 2, c: 3 };  //SyntaxError: Unexpected token = // 正确的写法如下: ({ a, b, c } = { a: 1, b: 2, c: 3 })  | 
					
使用对象解构赋值,可能很方便的把对象的属性、方法赋值给多个变量,例如:
| 
					 1  | 
						let { log, sin, cos } = Math;  | 
					
字符串作为解构赋值的右值,可以把字符逐个赋值给变量:
| 
					 1  | 
						let { a, b, c } = 'alex';  | 
					
数字/布尔值作为解构赋值的右值时,引擎先将其转换为对象:
| 
					 1 2  | 
						let {toString: s} = true; s === Boolean.prototype.toString  | 
					
函数的参数,可以作为解构赋值的左值:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						function add( [x,y] ) {     return x + y; } console.log( add( [ 1, 2 ] ) ); //3 console.log( add( 1, 2 ) ); // 错误:调用必须使用解构语法 // 对象解构语法、默认值都是支持的 function add( { x, y = 1 } ) {     return x + y; } console.log(add( { x: 1 } ));  // 2  | 
					
函数的返回值,可以作为解构赋值的右值:
| 
					 1 2 3 4  | 
						function pair() {     return [ -1, 1 ]; } let [l,r ] = pair();  | 
					
可以看到,上例中的用法实现了类似Python函数调用的多个返回值的效果。
合理使用解构赋值,可以让代码更加简洁,下面是一些应用场景举例:
| 交换变量的值: [x, y] = [y, x]; | ||
| 从函数返回多个值 | ||
| 为函数参数提供默认值:因为解构赋值的左值可以提供默认值 | ||
| 
 更丰富的参数风格: 
  | 
||
| 
 快速数据提取: 解构赋值可以使用非常简洁的代码从JSON中提取数据: 
  | 
||
| 
 遍历map: 任何Iterator都可以使用for...of循环来迭代,配合解构赋值,可以很方便的获得map的键值: 
  | 
||
| 
 导入模块的指定成员: 
 
  | 
展开运算符也是以 ... 作为前导(类似于函数的Rest参数),展开运算符用于把数组展开为若干个值,主要用于函数调用:
| 
					 1 2  | 
						console.log( ...[ 1, 2, 3, 4, 5 ] );  // 1 2 3 4 5 console.log( [ 1, 2, 3, 4, 5 ] );     // [ 1, 2, 3, 4, 5 ]  | 
					
展开运算符可以代替函数的apply方法。
展开运算符也可以用来合并数组:
| 
					 1 2  | 
						var array = [ 1, 2, 3 ]; var array2 = [ 0, ...array ];  | 
					
也可以和解构赋值联合使用(用在左值列表的结尾):
| 
					 1 2  | 
						let [a, ...rest] = [ 1, 2, 3, 4, 5 ]; console.log( rest ); // [ 2, 3, 4, 5 ]  | 
					
展开运算符还可以:
- 正确的把字符串转换为字符的数组,支持所有Unicode: [...'hello']
 - 把任何Iterator转换为真正的数组: [...iterator]
 
和数组类似,对象也支持使用展开操作符:
| 
					 1 2 3 4 5 6  | 
						// 用于解构赋值,z = { a: 3, b: 4 } let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; // 取出对象z的可枚举属性,存放到对象n中。等同于使用Object.assign方法 let z = { a: 3, b: 4 }; let n = { ...z };   | 
					
ES6允许定义对象属性时,指定一个变量:
| 
					 1 2 3 4 5 6 7 8  | 
						let name = 'Alex'; let age = 30; let user = { name, age }; // 另外一个例子 function func( x, y ) {     return { x, y }; }  | 
					
这样,变量名就作为属性名,变量值则作为属性值。
和属性类似,方法也允许简写:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						var user = {     getName(){         return this.name     },     // ES5的Getter/Setter类似于方法简写     get name(){     },     // 如果某个方法是生成器,则必须加*前缀     * generator(){yield 0;} }  | 
					
ES6允许在花括号语法中,用表达式来组成属性名、方法名:
| 
					 1 2 3  | 
						var user = {     ['name' + i]: 'Alex' }  | 
					
这种写法不能和属性名简写同时使用。 另外,作为属性名的表达式会自动转换为字符串。
ES6允许为函数参数提供默认值: function log( x, base = 10 ){}
使用默认值后,函数的length属性将返回没有指定默认值的参数个数(而不是全部参数个数)。
ES6可以将参数列表中剩余的所有参数暴露为一个数组:
| 
					 1 2 3  | 
						function(a, b, ...theArgs) {     // 第三个及之后的参数,存放在theArgs变量中 }  | 
					
Rest参数必须作为最后一个形参。
函数的name属性返回函数的名称,ES6将其纳入标准。
在全局作用域下,this指向全局对象:浏览器中的window/self或者Node.js中的global
在Node.js模块、ES6模块的定义中,this指向当前模块
在函数作用域下,如果:
- 函数作为对象的方法调用,则this指向当前对象
 - 普通模式下,函数不作为方法调用,则this指向全局对象
 - 严格模式下,函数不作为方法调用,则this指向undefined
 
该操作符是ES7提案的一部分,目前可以通过Babel支持它,用于绑定函数到目标对象并调用:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13  | 
						// 绑定 foo::bar          // 等价于bar.bind(foo) // 绑定并调用 function showThis() {     console.log( this ); } ""::showThis();   // 等价于showThis.bind("")() // 将obj的foo方法绑定到obj,并返回绑定后函数 var method = obj::obj.foo; // 上述代码等价于 var method = ::obj.foo;   | 
					
在函数式编程领域,所谓尾调用(Tail Call)是指作为函数体最后一个步骤的函数调用:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						function f( x ) {     return g( x ); } // 下面三个都不是尾调用 function f( x ) {     let y = g( x );     return y; } function f( x ) {     return g( x ) + 1; } function f( x ) {     g( x );     // 这边相当于有一个 return undefined }  | 
					
在使用递归结构时,尾调用具有优化的空间:由于尾调用作为最后一步,因此可以安全的丢弃调用者的栈帧并将其作为尾调用函数的栈帧,这样栈长度就不会增长,也不会因为过深的递归导致太多的内存占用甚至栈溢出。ES6明确要求解释器优化尾递归调用。
要利用尾调用优化来提升程序性能,你需要注意调整自己的代码,例如下面的阶乘实现就是正面的例子:
| 
					 1 2 3 4 5  | 
						function factorial( n, total ) {     if ( n === 1 ) return total;     // 尾递归调用     return factorial( n - 1, n * total ); }  | 
					
这个例子用到了递归,但是代价却与循环差不多。
关键字 => 用于定义所谓箭头函数,类似于其它语言中的Lambda:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						// 最后一个语句作为返回值 // 单个参数 var square = x => x ** 2; // 多个参数 var add = ( a, b ) => a + b; // 多个语句,必须使用花括号,最后一个语句return var sayHello = who => {     var greetings = `Hello ${who}`;     console.log( greetings );     return true; } // 与解构赋值联用 (     ( { name, age } ) => {         console.log( `${name} is ${age} years old` );     } )( { name: 'Alex', age: 30 } );  | 
					
关于箭头函数,需要注意:
- 函数体中的this,就是箭头函数定义处的this,与调用时刻无关
 - 不能将箭头函数作为构造函数调用,即不能new
 - 不可以使用arguments对象
 - 不支持yield关键字,因为其不能作为生成器
 
JavaScript之前的版本支持 \uxxxx 的Unicode字符表示法,最多支持0xFFFF之前的字符。ES6则引入 \u{xxxxx} 这样的表示法,只要把代码点(Code point)放在花括号内,可以表示任意范围的Unicode字符:
| 
					 1  | 
						"\u{41}\u{42}\u{43}"   // abc  | 
					
JavaScript中拼写HTML模板异常的繁琐,需要很多字符串拼接操作。ES6引入了新的模板字符串语法,它允许声明多行字符串,以反引号( ` )限定:
| 
					 1 2 3 4 5 6 7  | 
						var user = { name: 'Alex', age: 30 }; var html = ` <div>     <div>${user.name}</div>     <div>${user.age + 1}</div> </div> `;  | 
					
模板字符串中的空白符会被原样保留,而且,模板中支持插入当前上下文中的变量,类似于Shell的变量替换或者Java的EL表达式。 ${} 内部可以包含任意表达式,例如函数调用、其它模板字符串。 如果模板字符串引用未声明的变量将会导致报错。
标签模板(tagged template)是模板字符串的一种高级应用。模板模板是紧跟着函数名之后的模板字符串,该函数会被调用,来处理模板字符串:
| 
					 1 2 3 4 5  | 
						var name = 'Alex'; console.log`${name} Hello ${name}` // 等价于 console.log( [ 'Hello ', '' ], name, name ); // 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex'  | 
					
从上面的例子可以看出,所谓标签模板本质上是函数调用表达式,将模板字符串解析为参数的规则如下:
- 模板字符串以变量为界,切分为字符串数组。该数组为函数的第一个实参,如果模板以变量开头或者结尾、或者出现连续两个变量,则添加空串到数组中,
 - 变量全部取出,依据出现的顺序,依次作为函数的第2-N个实参
 
标签模板的函数体,通常都执行这样的逻辑:
- 遍历变量数组(即2-N个实参)
 - 原样连接第一实参对应索引的元素到结果字符串
 - 处理当前变量,连接到结果字符串
 - 遍历完毕后, 返回结果字符串
 
例如下面的代码,可以过滤用户输入中的不安全字符:
| 
					 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  | 
						// 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex' // 这些变量来自用户输入 var name = 'Alex<script>'; var age = 30; function escapeHTML( str ) {     var arg = String( str );     return arg.replace( /&/g, "&" ).replace( /</g, "<" ).replace( />/g, ">" ); } function safehtml( literals, ...values ) {     var index = 0;     var result = '';     for ( let seg of literals ) {         result += seg;//原样加入         // 每个元素后面都跟着一个变量,除了最后一个元素         if ( index < values.length ) {             result += escapeHTML( values[ index++ ] );         }     }     return result; } // 这个模板需要被安全的插入到DOM var result = safehtml`     <div>Hello ${name}, You are ${age} years old</div> `; console.log( result );   // <div>Hello Alex<script>, You are 30 years old</div>   | 
					
标签模板也提供了一种直白的语法,来把其它语言的代码嵌入到JavaScript语言中,例如jsx函数可以把DOM字符串转换为React对象。
ES6引入新的二进制、八进制数值直接量语法:
| 
					 1 2 3 4  | 
						var bin = 0b1011; bin = 0B1011; var oct = 0o777; oct = 0O777;  | 
					
ES6允许正则式构造函数第二个参数指定的标记,覆盖第一个参数中内置的标记:
| 
					 1  | 
						console.log( new RegExp( /abc/ig, 'i' ).flags ); // 打印i,第一个参数的ig被替换  | 
					
该修饰符用于支持4字节表示的Unicode字符,以及支持\u{xxx}语法的字符串:
| 
					 1 2  | 
						/\u{61}/.test('a')  // false /\u{61}/u.test('a') // true  | 
					
该修饰符类似于g,也用于全局匹配,但是y限定后一次匹配必须从上一次匹配成功的下一个字符开始。
正则式对象的sticky属性指示该修饰符是否被设置。
数组的空位是没有任何值的索引点:
| 
					 1 2  | 
						// 构造函数返回的数组都是空位 console.log( Array( 2 ) );  // [ ,  ]  | 
					
注意:空位与undefined不同,后者是一个确实的值。要判断某个索引是否空位可以使用in运算符:
| 
					 1 2  | 
						console.log( 0 in [] ); // false console.log( 0 in [ undefined ] ); // true  | 
					
在ES5中:
- forEach()、 filter()、every()、some()函数会跳过空位,不执行回调
 - join()、toString()则将空位作为undefined看待
 
ES6明确的把空位作为undefined看待。
ES6引入了一种新的数据类型,叫做Symbol,用于表示独一无二的值(类似于Java的枚举)。该类型与Undefined、Null、Boolean、String、Number、Object并列,成为第七种类型。
注意:
- 尽管Boolean、String、Number、Symbol不是对象类型,但是它们的行为类似于不可变对象
 - 数组不是独立的类型,typeof返回object
 - 尽管函数属于对象类型,但是其typeof返回function
 
Symbol变量通过调用Symbol函数获得:
| 
					 1 2 3  | 
						let name = Symbol(); // 可以入参一个字符串,但是不影响不相等性 let name = Symbol('name');  | 
					
注意:
- 不能在Symbol调用前面增加new,因为Symbol不是Object类型,也因此,不能给Symbol添加属性
 - 任何两个Symbol均不相等
 - Symbol不能与其它类型进行运算,但是你可以显式的将其转换为String、Boolean类型:
123var sym = Symbol('name');String(sym) // Symbol('name')Boolean(sym) ; // true 
现在对象的属性名,除了支持字符串类型之外,还可以支持Symbol类型:
| 
					 1 2 3 4  | 
						let obj = {     [name]: 'Alex' } console.log( obj[ name ] ); // Alex  | 
					
由于每个Symbol都不相等,因此将其作为属性名时,可以防止冲突。访问由Symbol标识的属性时,只能用方括号语法,不能用点号语法。
可以使用Symbol定义一组常量:
| 
					 1 2 3 4 5  | 
						log.levels = {     DEBUG: Symbol( 'debug' ),     INFO: Symbol( 'info' ),     WARN: Symbol( 'warn' ) };  | 
					
类似于数组,但是成员的值必须唯一,即没有重复值:
| 
					 1 2 3  | 
						var set = new Set(); [ 1, 2, 3, '3' ].map( e => set.add( e ) ); console.log( set );  // Set { 1, 2, 3, '3' }  | 
					
可以看到,判断元素相等时,使用的是精确相等(Object.is)。Set构造函数有几个变体:
| 
					 1 2  | 
						let set = new Set( [ 1, 2, 3, 3 ] ); // 传入数组 console.log( set.size );  // 3  | 
					
利用Set构造函数,可以给数组去重: [...new Set(array)]
与Set类似,但是成员只能是Object类型,该集合持有对象的弱引用,GC将无视这些引用。WeakSet没有size属性,也不能遍历。但是你可以调用其add/delete/has方法
JavaScript对象本质上就是一个Hash结构,可以作为Map使用。但是这种Map的Key类型只能是String/Symbol。
ES6引入的Map,支持任何类型的对象作为映射的键:
| 
					 1 2 3 4 5 6 7  | 
						var map = new Map(); // 默认构造器 map = new Map( [     [ 'name', 'Alex' ],     [ 'age', 30 ],     [ 0 , 1 ] ] ); console.log( map.has( 'age' ) ); // true  | 
					
Map在判断两个键是否相等时,使用类似于Object.is()判断。
类似于WeakSet,键是对象的弱引用。仅仅支持将对象作为键。你可以调用其get()、set()、has()、delete()方法,不支持遍历。
现在JavaScript中用来表示集合的结构包括:Array、Map、Set,它们都是可迭代(Iteratble)对象。所有可迭代对象都可以使用 for ... of 循环,来遍历集合的全部成员。
任何对象,只要其具有名为 Symbol.iterator 的方法,则被认为是可迭代的。对前述方法进行调用,会返回一个迭代器对象(Iterator),你可以简单的返回具有next()方法的简单对象,JavaScript引擎负责自动设置其原型为Generator.prototype:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  | 
						const obj = {     [Symbol.iterator]: function () {         // 返回迭代器         return {             next: function () {                 return {                     value: 1,                     done: false                 };             }         };     } }; for ( val of obj ) {     console.log( val ); // 无限打印 }  | 
					
for..of循环遍历迭代器的过程如下:
- 第N次调用迭代器的next()方法,返回包含了集合的第N个元素的数据结构
 - 返回的数据结构为: { value : '当前元素', done: '遍历是否已经结束' } 。如果done为true则value无意义,不需要指定
 - 根据next()的返回值,判断遍历是否完成
 
除了必须的next()方法,迭代器对象还可以提供额外的两个方法。如果不提供,默认实现由Generator.prototype提供。这两个方法的说明如下:
- 
			return() :该方法会在for...of循环提前退出(出错、break)时自动调用。可以利用此方法提供资源清理逻辑:
12345678910111213141516function readLines( file ) {return {next() {// 正常退出if ( file.eof() ) {file.close();return { done: true };}},// 中途退出,默认逻辑是简单的把入参设置到结果数据结构的value字段return( v ) {file.close();return { value : v, done: true };},};} - 
			throw() :配合生成器使用,用于在生成器外部抛出错误,在生成器内部捕获:
1234567891011121314151617181920var g = function*() {try {yield;}catch ( e ) {console.log( e ); // 1}};var i = g();i.next();try {// 注意不能用throw关键字抛出,该关键字无法影响生成器i.throw( 1 ); // 这个错误在内部捕获i.throw( 2 ); // 这个错误在外部捕获,因为第一个错误已经触发yield并导致生成器结束}catch ( e ) {console.log( e ); // 2} 
以下场景下,解释器自动遍历目标对象的迭代器: 解构赋值、扩展运算符、 yield* 、 for...of 、 Array.from 、 Promise.all() 、 Promise.race() 等等。
让Symbol.iterator返回一个生成器,可以简化代码:
| 
					 1 2 3 4 5 6 7 8 9  | 
						let obj = {     // Symbol.iterator方法可以返回生成器     * [Symbol.iterator]() {         yield 1;         yield 2;         yield* [ 3, 4, 5 ];     } }; console.log( [ ...obj ] );  // [ 1, 2, 3, 4, 5 ]  | 
					
生成器是ES6提供的异步编程解决方案。它是一种特殊的函数,其声明语法如下:
| 
					 1 2 3 4 5 6 7  | 
						// 要声明一个函数为生成器,必须在function后附加 * 号 function* generator() {     // 生成器函数体可以包含yield语句     yield 1;     yield 2;     return 3; }  | 
					
对生成器函数执行调用操作,你会得到一个迭代器对象,但是生成器的函数体不会 执行:
| 
					 1 2 3 4 5 6 7 8 9  | 
						function* generator() {     yield 1;     yield 2;     return 3; } let iterable = generator(); // 此时generator的函数体并未执行 for ( e of iterable ) {     console.log( e );  // 1 2 }  | 
					
每一个yield语句后面的表达式的值,将作为迭代器的元素,注意return语句后面的表达式不作为迭代器元素。关键字yield的本义就是“产出”。
你可以手工调用上述迭代器的next()方法:
| 
					 1 2 3 4  | 
						let iterable = generator(); console.log( iterable.next() ); // { value: 1, done: false } console.log( iterable.next() ); // { value: 2, done: false } console.log( iterable.next() ); // { value: 3, done: true }  return语句后的表达式作为value  | 
					
从效果上来看,执行生成器函数体时,每当遇到yield就返回(暂停)一次。返回后,生成器函数体的内部状态仍然被保留。当再一次调用next()时,自动恢复内部状态,从上一次退出的那个点继续向下执行。这种行为特点与普通函数完全不同。
对生成器执行附带入参,可以为生成器的yield语句提供“返回值”,这里有一个特别容易误会的地方:
| 
					 1 2 3 4 5  | 
						var generator = (function *() {     console.log( yield 1); })(); generator.next( 2 ); // 什么都不打印 generator.next( 3 ); // 打印3  | 
					
你可以通过next()调用来启动生成器,并让它在yield一个值(上面的1)后立即暂停。如果生成器当前正因为执行了yield语句而处于暂停中,你再一次调用next()可以让其恢复执行,这时你才可以为next()调用附带参数,此参数作为返回时yield语句的值。
上面的next(2)调用,导致生成器在标记红色的地方暂停:console.log( yield 1),打印语句根本不会执行,因此提供参数2没有任何意义。
| 
					 1 2 3 4 5 6 7 8 9  | 
						function* numbers() {     yield* [ 4, 5, 6 ] } function* generator() {     yield* [ 1, 2, 3 ];  // 逐个yield数组     yield* numbers();    // 逐个yield生成器 } // 展开操作符能够遍历迭代器 console.log( [ ...generator() ] );  // [ 1, 2, 3, 4, 5, 6 ]   | 
					
yield语句可以产生一个值,但是该语句本身的返回值默认是undefined。你可以在next()调用是提供一个参数,这个参数自动成为yield的返回值。带参数的next()调用类似于Python语言生成器的send()方法,这种情况下生成器函数成为协程。
协程是不依赖于线程的、轻量级的异步编程技术,它通过几个例程(函数)相互协作来完成逻辑。下面是一个简单的例子:
| 
					 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  | 
						// 服务器代理 var serverAgent = (function*() {     var clientToRequest = new Map();     while ( true ) {         // 1、服务器运行到yield,放弃控制权,暂停         // 4、服务器从暂停中恢复,从yield的返回值得到客户端ID,并异步发送Ajax请求         clientToRequest.set( yield, sendRequest() );         // 5、遍历客户端请求的列表,判断Ajax请求状态         for ( e of clientToRequest ) {             var cid = e[ 0 ];             var xhr = e[ 1 ];             if ( isDone( xhr ) ) {                 // 6、如果Ajax请求已经完毕,则调用客户端处理,并放弃控制权                 clients[ cid ].next( xhr.responseText );             }         }     } })(); serverAgent.next(); // 0、启动服务器代理 // 很多客户端 var clients = []; for ( i of [ 1, 2, 3, 4, 5 ] ) {     var client = (function*() {         // 3、客户端向服务器代理发送请求,附带自己的ID         // 7、客户端从yield返回,返回值为服务器代理提供的响应对象。异步处理的同步化表达         var response = yield serverAgent.next( i );         // 8、处理响应         console.log( response );     })();     clients.push( client );     client.next(); // 2、启动客户端 }  | 
					
使用协程进行异步处理,代码要比回调简洁的多。
很多JavaScript框架都提供了Promise这种异步编程框架,终于现在可以在语言级别原生的支持了。比起传统的基于回调函数、事件的异步处理,Promise更加合理和强大。
一个Promise保存着一个未来才能完成的事件(通常是异步操作)的结果。Promise所关联的异步操作,其状态只能是Pending(正在进行)、Resolved(已经解决)、Rejected(已经失败)之一。只有异步操作的结果可以决定状态到底是哪一个。状态只能从Pending变为Resolved或者Rejected,而且只能改变一次。
Promise具有一些缺点:
- 一旦建立就立即执行,无法取消
 - 如果不提供回调,其内部抛出的异常无法反应到外部
 - 无法对Pending阶段进行细分
 
Promise是一个构造函数,你可以调用它创建Promise:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						// 创建者代码: var promise = new Promise( function ( resolve, reject ) {     // 执行异步操作...     if ( succeed ) {         resolve( value ); // 将Promise置为已解决     }     else {         reject( error );  // 将Promise置为已失败     } } );  | 
					
一旦Promise被创建,通过构造函数传入的回调就会立即执行。此回调的两个参数resolve、reject,是引擎提供的函数,你可以调用它们而把Promise置为已解决、已失败状态之一。
要知道,构造函数由Promise的创建者调用,但是Promise肯定是提供给其它模块使用的。那么其它模块拿到这个Promise有什么意义呢?
其它模块可以调用它的then方法,注册当Promise状态改变后,需要执行的回调函数:
| 
					 1 2 3 4 5 6  | 
						// 客户代码: promise.then( function ( value ) {     // 成功时执行的回调 }, function ( error ) {     // 失败时执行的回调 } );  | 
					
下面是一个简单的例子:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						function timeout( ms ) {     return new Promise( ( resolve, reject ) => {         // 此回调会立即执行,因此下面的语句立即打印         console.log( 'Starting process...' );         // 异步的设置Promise为已解决状态         setTimeout( resolve, ms, 'Success' );     } ); } // 客户代码 // 由于setTimeout是异步操作,它必然在then调用之后,才可能执行回调 timeout( 1000 ).then( ( value ) => {     // 异步操作完成时,打印Promise创建者提供的值     console.log( value ); } );  | 
					
下面是一个基于Promise处理Ajax的例子:
| 
					 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  | 
						var getJSON = function ( url ) {     var promise = new Promise( function ( resolve, reject ) {         var client = new XMLHttpRequest();         client.open( "GET", url );         client.onreadystatechange = handler;         client.responseType = "json";         client.setRequestHeader( "Accept", "application/json" );         client.send();         function handler() {             if ( this.readyState !== 4 ) {                 return;             }             if ( this.status === 200 ) {                 resolve( this.response );             }             else {                 reject( new Error( this.statusText ) );             }         };     } );     return promise; }; // 客户代码 getJSON( "/posts.json" ).then( function ( json ) {     // 处理JSON }, function ( error ) { } );  | 
					
总之,都是这样的套路:服务方执行异步操作,并给客户一个承诺,客户拿到承诺后注册期望的操作,服务方完成异步操作后,调用客户端注册的操作,完成处理。
Promise的创建者,可以向resolve函数传递另外一个Promise对象:
| 
					 1 2 3 4 5 6 7 8  | 
						var p1 = new Promise(function (resolve, reject) {     setTimeout(() => reject(new Error('Failed')), 3000); }); var p2 = new Promise(function (resolve, reject) {     // 向resolve传递另外一个Promise p1     setTimeout(() => resolve(p1), 100); });  | 
					
P2没Resolve为一个“依然未Resolve”的Promise,此时JS引擎不会简单的直接调用客户通过then注册的回调函数(即100ms后回调不会被调用)。而是另外的那个Promise(P1)的状态决定当前Promise(P2)的状态——如果P1处于Pending则P2会等待,直到P1变为Resolved/Rejected,才调用客户回调:
| 
					 1 2  | 
						// 于3秒后失败,而不是100ms后成功 p2.then(result => console.log(result), error => console.log(error));  | 
					
基于这种用法,可以很容易的管理多个异步操作,例如只在所有这些操作都成功的时候,才认为整个事务是成功的。
前面的例子已经使用过此方法,它的基本作用是注册当Promise状态改变后,需要执行的回调函数。该方法可以有两个入参,它们都必须是函数,第一个作为Promise变为Resolved时的回调,第二个可选,作为Promise变为Rejected时的回调。
then()方法的返回值是一个新的Promise实例,因此可以使用链式调用。链式调用时,前面then的回调函数的返回值,将会传递给后面then的回调函数:
| 
					 1 2 3 4 5 6 7  | 
						new Promise((resolve, reject) => {     setTimeout(resolve, 1000, 'Succeeded'); }).then(result => {     console.log(result);     return 'OK'; }).then(result => console.log(result)); // 1秒后连续打印Succeeded、OK  | 
					
如果前面then的回调函数的返回值是一个Promise,则后面then的回调函数直到此Promise状态改变后才被调用:
| 
					 1 2 3 4 5 6 7  | 
						// getJSON函数返回一个Promise getJSON("/post/1.json").then(function (post) {     // 继续调用getJSON,返回一个Promise     return getJSON(post.commentURL); }).then(result=>{     // 此回调在第二次getJSON返回的Promised被Resolved时才调用 });  | 
					
此方法等价于 then(null, rejection) 调用,用于指定Rejected时的回调函数。链式调用时,前面then方法抛出的异常,也经由后面的catch方法捕获。
注意,当Promise状态已经变成Resolved后,再抛出异常是无效的:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						var promise = new Promise(function (resolve, reject) {     resolve('OK');     throw new Error(); // 无效 }); promise.then(function (value) {     console.log(value) }).catch(function (error) {     console.log(error) }); // 打印OK  | 
					
一般情况下,都使用catch方法,而不是then的第二回调。
该静态方法用于将多个Promise实例包装为单个新的Promise实例: var p = Promise.all([p1, p2, p3]) ,当:
- 所有成员Promise都变为Resolved后,整体Promise变为Resolved。成员Promise传递给resolve回调的参数,构成一个数组,传递给整体Promise的then回调
 - 一旦任何一个Promise变为Rejected,整体Promise立即变为Rejected,此Rejected Promise传递给reject回调的参数,传递给整体Promise的then第二回调或者catch回调
 
该静态方法也是用于将多个Promise实例包装为单个新的Promise实例。但是,只要有一个成员Promise的状态发生改变,则整体Promise的状态也发生改变,并且与前面成员Promise的状态一致。
该静态方法用于把一个对象转换为Promise:
| 
					 1 2  | 
						// 把jQuery生成的deferred对象转换为Promise var p = Promise.resolve($.ajax('/data.json'));  | 
					
该方法的参数,有四种情形:
- 参数是一个Promise对象,这种情况下resolve不对其进行任何修改,直接将其作为返回值
 - 是一个thenable对象,例如:
12345let thenable = {then: function(resolve, reject) {resolve(true);}};新建的Promise对象会立即调用这里的then方法
 - 如果参数不具有then方法, 或者不是对象,则该方法返回一个已经Resolved的Promise,resolve的入参作为此Promise的then回调的入参
 - 不传递参数,返回一个已经Resolved的Promise
 
返回一个Rejected的Promise实例。
Promise的链式调用,不管以then还是catch结尾,其内部的异常都不会抛出到外部。为了确保未处理的异常能够抛出,可以在调用链的结尾添加一个done()调用。
该方法指定一个无论什么情况下,都会执行的回调。示例:
| 
					 1 2 3 4 5 6 7 8  | 
						// 使用垫片库获得finally支持 require( 'promise.prototype.finally' ).shim(); var assert = require('assert'); // 使用finally: Promise.resolve(42).finally(function () {     assert.equal(arguments.length, 0); });  | 
					
该对象提供以下功能:
| 
 语言内部功能相关方法 Object对象某些方法,例如Object.defineProperty(),现在同时在Reflect上部署,未来的新方法统一部署到Reflect上 注意,Reflect改进了Object某些方法的行为,例如defineProperty失败时,Object返回异常,而Reflect返回false  | 
| 
 与Proxy配合 Reflect提供了与Proxy的Handler一一对应的方法,调用这些方法,可以执行“默认行为”  | 
ES6引入了代理(Proxy),利用它可以实现类似于JDK动态代理、Python装饰器/元类的功能。
Proxy在被代理对象上设置一个拦截器,对此对象的访问必须经由此拦截器,拦截器可以改变方法调用、属性读写的行为。例如下面的拦截器改写了属性读写的默认行为:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						let target = {}; // 改变属性读写的行为,打印调试信息 var proxy = new Proxy( target, {     get: function ( target, key, receiver ) {         console.log( `getting ${key}` );         return Reflect.get( target, key, receiver );     },     set: function ( target, key, value, receiver ) {         console.log( `setting ${key}` );         return Reflect.set( target, key, value, receiver );     } } ); proxy.age = 1;  // setting age proxy.age++;    // getting age,  setting age  | 
					
Proxy构造函数接受两个参数,第一个是被代理对象,第二个是处理器(Handler)。需要拦截的操作不同,则处理器写法也不同:
| Handler的键 | 拦截的操作 | ||
| get | 
 签名:get(target, propKey, receiver)。拦截对象属性、数组元素的读取操作 将对象的原型设置为提供了get处理器的代理,对象自动获得代理的get行为: 
  | 
||
| set | 签名:set(target, propKey, value, receiver)。拦截对象属性、数组元素的设置操作 | ||
| apply | 
 仅当代理的目标是函数时使用 示例: 
  | 
||
| construct | 仅当代理目标是作为构造器的函数时使用 签名:construct(target, args) 拦截构造函数调用操作:new proxy(...args)  | 
||
| has | 签名:has(target, propKey)。拦截 prop in obj、hasOwnProperty操作,返回布尔值 | ||
| deleteProperty | 签名:deleteProperty(target, propKey)。拦截delete obj[prop]操作,返回布尔值 | ||
| ownKeys | 签名:ownKeys(target) 拦截:Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy) 返回对象自身所有属性的数组,注意Object.keys仅返回可枚举属性  | 
||
| getOwnPropertyDescriptor | 签名:getOwnPropertyDescriptor(target, propKey) 拦截:Object.getOwnPropertyDescriptor(proxy, propKey),返回属性描述符  | 
||
| defineProperty | 签名:defineProperty(target, propKey, propDesc) 拦截:Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回布尔值  | 
||
| preventExtensions | 签名:preventExtensions(target)。拦截Object.preventExtensions(proxy),返回布尔值 | ||
| getPrototypeOf | 签名:getPrototypeOf(target)。拦截Object.getPrototypeOf(proxy),返回原型对象 | ||
| isExtensible | 签名:isExtensible(target)。返回布尔值 | ||
| setPrototypeOf | 签名:setPrototypeOf(proxy, proto)。拦截Object.setPrototypeOf(proxy, proto),返回布尔值 | 
考虑到浏览器的兼容性问题,不能直接在其中运行ES6代码。即使你仅仅需要支持最新版本的现代浏览器,模ES6模块之类的特性也不被支持。
因此,必须要进行转码(transpile),将ES6语法转换为通用的ES5语法,并通过垫片(Polyfill)库来适配ES5中缺失的ES6 API。
这是一个广泛使用的JavaScript编译器,请参考:使用Babel进行JS转码
Google提供的JavaScript转码器。Traceur也允许直接在网页中使用ES6代码:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						<!-- Traceur库 --> <script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script> <!-- 这两个脚本用于支持浏览器环境 --> <script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script> <script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script> <!-- 注意type设置为module--> <script type="module">     // 导入用户模块     import './Greeter.js';     // 可以包含任意ES6代码 </script>  | 
					
Traceur可以对支持的ES6特性进行细粒度的控制:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  | 
						<script>     // 创建一个全局变量,引用Traceur加载器     window.System = new traceur.runtime.BrowserTraceurLoader();     // 进行细粒度控制     var metadata = {         traceurOptions: {             experimental: true,             properTailCalls: true,             symbols: true,             arrayComprehension: true,             asyncFunctions: true,             asyncGenerators: exponentiation,             forOn: true,             generatorComprehension: true         }     };     // 加载自己的模块     System.import( './module.js', { metadata: metadata } ).catch( function ( ex ) {         // 加载失败时的处理     } ); </script>   | 
					
| 属性/方法 | 说明 | 
| is() | 
 该方法补充了既有的相等比较运算符: 
 当然,对于 {} == {} 三种运算符都返回false,因为复杂对象比较的是地址  | 
| assign() | 
 类似于Ext.apply,可以将1-N个源对象的属性覆盖到目标对象,声明在后面的源对象同名属性,优先级高 该方法进行的是浅拷贝  | 
| __proto__ | 
 用于读取、设置当前对象的原型对象,作用类似于setPrototypeOf()/getPrototypeOf()静态方法  | 
ES6支持多种遍历对象属性的方法:
| for...in 遍历对象自身定义和继承得到的可枚举属性,不包含Symbol属性 | 
| Object.keys(obj) 返回对象自身定义的可枚举属性的名称的数组,不包含Symbol属性 | 
| Object.values(obj) 返回对象自身定义的可枚举属性的值的数组,不包含Symbol属性 | 
| Object.entries(obj) 类似上面,但是数组的元素是[key, value] | 
| Object.getOwnPropertyNames(obj) 返回对象自身定义的全部属性的数组,不包含Symbol属性,包含不可枚举属性 | 
| Reflect.ownKeys(obj) 返回自身所有属性的数组,包括不可能枚举属性,包括Symbol属性 | 
| 方法 | 说明 | ||||
| for() | 
 根据键查找已经通过Symbol.for登记的Symbol,如果不存在,则定义一个并登记、返回: 
 注意,Symbol()调用生成的Symbol不参与查找,其值总是独特的: 
  | 
||||
| keyFor() | 返回一个已经登记的Symbol的键 | 
| 方法 | 说明 | ||||
| codePointAt() | 
 返回字符串目标位置上(以2字节为单位划分位置)的字符对应的代码点 在JavaScript内部,字符使用UTF-16表示(二字节),对于需要4字节粗存储的Unicode字符,会被认为是两个字符。charAt、charCodeAt不能正确的识别4字节的Unicode字符 codePointAt方法解决此问题,它可以正确处理4字节字符,并返回正确的10进制代码点的值: 
 使用for ... of循环可以获得字符串中所有字符的代码点: 
  | 
||||
| fromCodePoint() | 该静态函数用于把代码点转换为字符 | ||||
| includes() startsWith() endsWith()  | 
 这些子串搜索函数补充了indexOf() 这三个函数都支持第二个参数,用于指定搜索的起始位置  | 
||||
| repeat() | 
 返回字符串重复N遍的副本  | 
||||
| padStart() padEnd()  | 
 使用指定的字符串左侧/右侧补全字符串,确保它到达一定的长度  | 
||||
| raw() | 
 一个标签模板,处理字符串,将其中的反斜杠替换为双反斜杠: 
  | 
| 方法 | 说明 | ||
| isFinite() | 
 判断一个数字是否为有限的: 
  | 
||
| isNaN() | 判断参数能否转换为数字 | ||
| parseFloat() | 解析为浮点数 | ||
| parseFloat() | 解析为整数 | ||
| isInteger() | 
 判断是否整数,由于JavaScript以相同的方式存储整数和浮点数,因此6.0和6被作为同一个值看待: 
  | 
||
| isSafeInteger() | 判断是否安全整数,即在-2^53到2^53之间,超过此范围的整数,其值无法精确表示 | 
| 方法 | 说明 | 
| trunc() | 取整数,不进行舍入 | 
| sign() | 判断值的正负 | 
| cbrt() | 求立方根 | 
| clz32() | JavaScript中整数使用4字节表示,该函数判断整数的二进制形式,具有多少个前导的0 | 
| imul() | 返回两个整数以32位带符号整数的方式的乘积 | 
| fround() | 返回一个树的单精度浮点数形式 | 
| hypot() | 返回所有参数平方和的平方根 | 
| expm1(x) | 计算e^x - 1 | 
| log1p(x) | 计算 1+ x的自然对数,即Math.log(1 + x) | 
| log10(x) | 计算以10为底的对数 | 
| log2(x) | 计算以2为底的对数 | 
| sinh(x) cosh(x) tanh(x) asinh(x) acosh(x) atanh(x)  | 
双曲正弦 双曲余弦 双曲正切 反双曲正弦 反双曲余弦 反双曲正切  | 
| 方法 | 说明 | ||
| from() | 
 用于将两类对转换为真正的数组: 
  | 
||
| of() | 将一组对象转换为数组: Array.of( 'Hello', 'World' ); // [ 'Hello', 'World' ] | ||
| copyWithin() | 
 从数组内部替换元素: 
 
  | 
||
| find() | 返回第一个匹配回调的成员: [ -1, 0, 1, 2, 3 ].find( n => n > 0 ) | ||
| findIndex() | 返回第一个匹配回调的成员的索引,该函数与find()都接收第二个参数,绑定回调的this | ||
| fill() | 使用指定的值填充数组 | ||
| entries() | 
 返回数组键值对的迭代器: 
 
  | 
||
| keys() | 返回索引数字的迭代器 | ||
| values() | 返回数组元素的迭代器 | ||
| includes() | 判断数组是否包含指定的元素 | 
| 属性/方法 | 说明 | 
| size | 返回成员总数 | 
| add() | 添加一个元素,返回集合本身 | 
| delete() | 删除一个元素,返回是否成功 | 
| has() | 判断是否包含某个成员 | 
| clear() | 清除所有成员 | 
| keys() values() | 返回遍历值的迭代器,遍历顺序与插入顺序一致 | 
| entries() | 返回条目的迭代器,其key/value一直 | 
| forEach() | 对每个成员执行一个回调 | 
| filter() | 返回通过回调的元素构成的子集 | 
| 属性/方法 | 说明 | 
| size | 返回成员总数 | 
| set() | 设置键值对,返回Map本身 | 
| get() | 根据键获得值,如果找不到key则返回undefined | 
| delete() | 删除一个键,返回是否成功 | 
| has() | 判断是否包含某个键 | 
| clear() | 清除所有成员 | 
| keys() | 返回遍历键的迭代器,遍历顺序与插入顺序一致 | 
| values() | 返回遍历值的迭代器,遍历顺序与插入顺序一致 | 
| entries() | 返回条目的迭代器,条目是[key,value]数组 | 
| forEach() | 对每个成员执行一个回调 | 
            
Leave a Reply