《Understanding ECMAScript 6》笔记 =============================== > 在线免费阅读:https://leanpub.com/understandinges6/read/ > 部分代码使用原书,代码版权归原书所有 1. [块级绑定(Block Bindings)](#block-bindings) 1. [字符串](#string) 1. [正则](#regex) 1. [字符串模板(template strings)](#template-strings) 1. [标签模板(tagged templates)](#tagged-templates) 1. [函数](#function) 1. [对象](#object) 1. [解构(Destructuring)](#destructuring) 1. [Symbols](#symbols) 1. [生成器(Generators)](#generators) 1. [迭代器(Iterators)](#iterators) 1. [类](#class) 1. [Promises](#promises) 1. [模块(Modules)](#modules) 1. [杂七杂八](#miscellaneous) ##块级绑定(Block Bindings)[↑](#catalogue) ###let 块级{}中有效 同块级不可重复声明 没有变量提升 > 块级会形成暂时性死区(TDZ,Temporal Dead Zone) ###const 基本和 `let` 相同,值不可修改 > `let` 和 `const` 最好不要在全局下使用 ##字符串[↑](#catalogue) ###unicode 支持更好 ###新增部分函数,支持双字节 `codePointAt`,双字节版的 `charCodeAt`,得到字符 unicode `fromCodePoint`,双字节版的 `fromCharCode`,从 unicode 得出字符 `includes`,包含某字符串 `startsWith`,以某字符串开始 `endsWith`,以某字符串结束 `repeat`,重复字符串 `normalize`,unicode 正规化,举个例子:两个 unicode 字符合成一个 ##正则[↑](#catalogue) ###新增标志 `u` 正则识别 unicode 字符 ###新增标志 `y` sticky,部分浏览器早就实现了 ##字符串模板(template strings)[↑](#catalogue) ```javascript let a = 1 let b = 2 let s = `${a} ${a + b}` // '1 3' ``` ##标签模板(tagged templates)[↑](#catalogue) ```javascript let a = 1 function tag ( strings, ...values ) { console.log( strings ) console.log( values ) return values[0] } let s = tag`a ${a}` // 'a 1' // ["a ", "", raw: Array[2]] // [1] ``` ##函数[↑](#catalogue) ###默认参数 ```javascript function foo ( bar = 1 ) { console.log( bar ) } ``` ###剩余参数 ```javascript function foo ( bar, ...rest ) { // ✓ ; } function foo ( bar, ...rest, last ) { // × ; } ``` ###函数属性 name 各种例子 ```javascript function doSomething() { // ... } console.log( doSomething.name ); // "doSomething" var doAnotherThing = function () { // ... }; console.log( doAnotherThing.name ); // "doAnotherThing" var doSomethingAgain = function doSomethingElse () { // ... }; console.log( doSomethingAgain.name ); // "doSomethingElse" var person = { get firstName () { return "Nicholas" }, sayName: function () { console.log( this.name ); } } console.log( person.sayName.name ); // "sayName" console.log( person.firstName.name ); // "get firstName" console.log( doSomething.bind().name ); // "bound doSomething" console.log( ( new Function() ).name ); // "anonymous" ``` ###new.target 避免了很多使用 `new` 的坑 ```javascript function Foo () { if ( typeof new.target !== "undefined" ) { console.log( 'good' ); // using new } else { throw new Error( 'You must use new with Person.' ) // not using new } } var foo = new Foo(); // good foo = Foo.call( foo ); // error! ``` ###块级函数 块级中可定义函数 ###箭头函数 `this`, `super`, `arguments` 和 `new.target` 的值都在定义函数时绑定而非运行时绑定 不可 `new` 不可改变 `this` 的值 没有 `arguments` > 跟普通函数一样拥有 name 属性 ```javascript var foo = value => value; // input value, output value var foo = () => {}; var foo = ( x, y ) => x + y; var foo = id => ({ x: 'x' }); ``` ```javascript // this 的绑定 var foo = { init: function () { document.addEventListener( 'click', (function ( e ) { console.log( e.type ); }).bind( this ), false); } }; // ------------------------ var foo = { init: function () { document.addEventListener( 'click', e => {console.log( e.type )}, false); } }; ``` ###立即调用函数表达式(Immediately-Invoked Function Expressions (IIFEs)) ```javascript let foo = function ( s ) { console.log( s ); }( 'text' ) // text // ------------------------- let foo = ( s => { console.log( s ); })( 'text' ) // text ``` ###新增尾递归优化 ##对象[↑](#catalogue) ###对象字面属性值简写(Property Initializer Shorthand) ```javascript function foo ( text ) { return { name // name: name } } ``` ###对象方法简写(Method Initializer Shorthand) ```javascript var foo = { bar () {} } ``` ###计算属性名语法 对象的属性可以使用中括号 `[]` 表示需要「被计算」,结果转换为字符串作为属性名使用。 ```javascript let a = function () {} let foo = { a: 'text a', [a]: 'function a' } console.log( foo['a'] ) // text a console.log( foo[a] ) // function a ``` ###Object.is() 和经典的 `===` 几乎一样,区别在于: ```javascript console.log( +0 === -0); // true console.log( Object.is( +0, -0 ) ); // false console.log( NaN === NaN ); // false console.log( Object.is( NaN, NaN ) ); // true ``` ###Object.assign() ```javascript Object.assign( target, ...source ) ``` 读取源对象可列举的、自身的属性,将其赋值到目标对象上,覆盖旧属性,并非通常意义的复制。 ###复制存取器属性 > 此小节查询 MDN 后补充上 使用 `Object.getOwnPropertyDescriptor(source, key)` 读取,使用 `Object.defineProperties` 定义。 ###属性允许重复定义 属性以最后一个定义的值为准 ###修改原型 `Object.getPrototypeOf`,得到原型 `Object.setPrototypeOf`,设置原型 ###super 用以访问对象的 prototype ##解构(Destructuring)[↑](#catalogue) ```javascript var {a, b: { c, d }} = c ( {a, b: { c, d }} = c ) var [a, [b, c]] = d var [a, , [b, c]] = d // 跳过一个 function foo ( { bar1, bar2 } = {} ) { ; } ``` 解构可以有默认值,但只会在需要的时候求值。 [2ality](http://www.2ality.com/2015/01/es6-destructuring.html) 有更详细清晰的解释: ```javascript let {prop: y=someFunc()} = someValue; let x, y; [x=3, y=x] = []; // x=3; y=3 [x=3, y=x] = [7]; // x=7; y=7 [x=3, y=x] = [7, 2]; // x=7; y=2 ``` ##Symbols(不知道如何翻译,是第七种原始类型)[↑](#catalogue) ```javascript var foo = Symbol() var foo = Symbol( 'bar' ) ``` `Symbol( 'description' )` 生成局部 Symbol,即使 `description` 相同生成的 Symbol 也不一样 `Symbol.for( 'description' )` 生成全局 Symbol,`description` 相同则 Symbol 相同 ###获取对象的 Symbol 数组 `Object.getOwnPropertySymbols( object )` ###强制转换 Symbol 为 String > 原书本节未完成 ###有名的预定义 Symbol > 原书本节大部分未完成 ##生成器(Generators)[↑](#catalogue) 生成迭代器的函数 ```javascript function *createIterator() { yield 1; yield 2; yield 3; } var o = { *createIterator ( items ) { for ( let i=0; i < items.length; i++ ) { yield items[i]; } } }; let iterator = o.createIterator( [1, 2, 3] ); ``` ##迭代器(Iterators)[↑](#catalogue) ###for-of 语法 数组、字符串、映射(Map)、集合(Set)和元素数组(NodeList)都可迭代(iterable),可使用 for-of 语法 ###得到内置迭代器 Symbol.iterator 指向得到迭代器的函数 ```javascript let values = [1, 2, 3]; let iterator = values[Symbol.iterator](); iterator.next(); // 1 ``` ###自定义迭代器 ```javascript let collection = { items: [], *[Symbol.iterator]() { yield *this.items.values(); // `yield *` 语法,委托了数组 `items` 的内置迭代器 } }; ``` ###对象、数组、映射、集合都具有的默认迭代器 `ertries()`,返回键值对迭代器 `keys()`,返回键迭代器 `values()`,返回值迭代器 ###字符串迭代器 通过 `[]` 的访问是 code unit 方式 通过迭代器则是字符方式(几乎是,某些 unicode 支持不足) ###元素数组(NodeList)迭代器 返回的是数组中的单个元素 ###向迭代器传参数 ```javascript function *foo () { let bar = yield 1; } let it = foo() console.log( it.next() ) // Object {value: 1, done: false},执行语句 `yield 1` 然后暂停 console.log( it.next( 2 ) ) // Object {value: undefined, done: true},将 2 作为 `yield 1` 的返回值, // 迭代器内部继续执行语句 `let bar = 2`, // 之后执行完毕,无返回值,`value` 为 `undefined`,`done` 为 `true` console.log( it.next() ) // Object {value: undefined, done: true} ``` ###生成器使用 return 提前返回 ```javascript function *createIterator() { yield 1; return 42; yield 2; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 42, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }" ``` ###委托生成器 使用 `yield *` ```javascript function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for (let i=0; i < count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" ``` 可以 `yield *"string"`,会调用字符串的默认迭代器 ```javascript function *foo () { yield * "hello" } let it = foo() console.log( it.next() ) // Object {value: "h", done: false} console.log( it.next() ) // Object {value: "e", done: false} ``` ###异步任务调度 以下是书中的例子,写得并不好,变量 `task` 的管理容易出问题: ```javascript var fs = require("fs"); var task; function readConfigFile() { fs.readFile("config.json", function(err, contents) { if (err) { task.throw(err); } else { task.next(contents); } }); } function *init() { var contents = yield readConfigFile(); doSomethingWith(contents); console.log("Done"); } task = init(); task.next(); ``` ##类[↑](#catalogue) ###类声明 ```javascript class foo { // 相当于构造函数 constructor ( name ) { this.name = name; } // 相当于 foo.prototype.bar bar () { console.log( this.name ); } } ``` 类的属性最好都在构造函数里面创建。 类声明本质上就是以前的函数声明,除了以下有所不同: 1. 类声明不会像函数声明那样被提升 1. 类内部的代码全部以 `strict mode` 运行 1. 所有方法都是不可列举的,相当于使用了 `Object.defineProperty()` 1. 不使用 `new` 会抛异常 1. 以类名命名方法来覆盖类名会抛异常(类名对于类内部来说是以 `const` 定义的,对于外部则不是) ###类表达式 ```javascript let foo = class {} ``` ```javascript let foo = class foo2 {} // foo === foo2 ``` 匿名类作为参数 ```javascript function createFoo ( c ) { return new c() } createFoo( class { constructor () { ; } }) ``` 立即调用类表达式(有点像立即调用函数表达式) ```javascript let foo = new class { constructor ( name ) { this.name = name } }( 'foo' ) ``` ###存取器属性 ```javascript class foo { constructor ( name ) { this.name = name } get className () { return 'class ' + this.name } set className ( value ) { this.name = 'class' + value } } ``` ###静态成员 ```javascript class foo { constructor ( name ) { this.name = name } // 相当于 foo.prototype.bar bar () { console.log( this.name ) } // 相当于 foo.staticBar static staticBar () { console.log( this.name ) } // get / set 也可以用 static get barName () { return 'bar' } } ``` > 静态成员同样不可列举 ###派生类 比起 ECMAScript5,ECMAScript6 的派生方便了很多 ```javascript class Rectangle { constructor ( length, width ) { this.length = length; this.width = width; } getArea () { return this.length * this.width; } } class Square extends Rectangle { constructor ( length ) { super( length, length ); } } ``` 在派生类的构造函数中,调用 `super` 是必须的。如果连构造函数都没有,则: ```javascript class Square extends Rectangle { // 无构造函数 } // 相当于 class Square extends Rectangle { constructor ( ...args ) { super( ...args ) } } ``` > 1. 只能在派生类中用 `super()` > 1. 使用 `this` 前必先调用 `super()` 来**初始化** `this` > 1. 只有在构造函数返回一个对象的时候才可以不用 `super()` ###类方法 覆盖、隐藏父类方法 ```javascript class Square extends Rectangle { constructor ( length ) { super( length, length ); } getArea () { return this.length * this.length; } } ``` 仍然可以使用 `super` 调用父类方法 ```javascript class Square extends Rectangle { constructor ( length ) { super( length, length ); } getArea () { return super.getArea(); } } ``` 类方法没有 `[[Construct]]` 这个内部方法,所以不能被 `new`。([什么是`[[Construct]]`](http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2)) ###静态成员 相当于 ES5 中定义在构造函数上的方法(注意不是定义在构造函数的原型上),派生类显然也能调用 ###extends 关键字后面可以使用表达式 除了 `null` 和生成器函数外 ```javascript // 使用函数 function base () {} class foo extends base {} // 使用表达式 function base () {} function getBase () { return base } class foo extends getBase() {} // 混合模式(多继承?!) let AMinin = { aF = function () {} } let BMinin = { bF = function () {} } function mixin ( ...mixins ) { var base = function () {} Object.assign( base.prototype, ...mixins ) return base } class foo extends mixin( AMinin, BMinin ) {} // 内置类型 class foo extends Array {} class foo extends String {} ``` ###new.target 能够得知类的调用状态,应用例如:阻止抽象类被实例化 ```javascript class Shape { constructor() { if (new.target === Shape) { throw new Error("This class cannot be instantiated directly.") } } } ``` ##Promises[↑](#catalogue) Promise 是老朋友了,所以没有什么好记录的,就记一下语法。 ```javascript let p1 = new Promise( function ( resolve, reject ) { resolve( 42 ); }); let p2 = Promise.resolve( 42 ); let p3 = Promise.reject( 43 ); let p4 = Promise.all( [p1, p2, p3] ); // 等待所有 Promise 返回 let p5 = Promise.race( [p1, p2, p3] ); // 最快的一个 Promise 返回就返回 p4.then( function ( value ) { console.log( value ); }).catch( function ( value ) { console.log( value ); }) ``` ##模块(Modules)[↑](#catalogue) > 注:本章的代码似乎有一些问题,基本参考 MDN 为准 1. 模块中的代码自动以严格模式运行 1. 模块中的顶层变量只是模块中顶层,并非全局顶层 1. 顶层中的 `this` 的值为 `undefined` 1. 代码中不允许 HTML 风格的注释 1. 模块必须有导出的东西 ###基本导入导出 直接使用原书代码: ```javascript // 导出数据 export var color = "red"; export let name = "Nicholas"; export const magicNumber = 7; // 导出函数 export function sum(num1, num2) { return num1 + num1; } // 导出类 export class Rectangle { constructor(length, width) { this.length = length; this.width = width; } } // 导出引用 function multiply(num1, num2) { return num1 * num2; } export multiply; // 默认导出 export default function () { return; } // export as export { multiply as foo } ``` 1. 除非使用 `default` 语法,否则函数和类都不能使用匿名 1. `export` 只能用在顶层中 `as` 和 `default` 语法的情况,给出一个来自 [2ality](http://www.2ality.com/2015/07/es6-module-exports.html) 的表格 |Statement |Local name |Export name| |-------------------------------|------------|-----------| |export {v as x}; | 'v' | 'x' | |export default function f() {} | 'f' | 'default' | |export default function () {} | '*default*'| 'default' | |export default 123; | '*default*'| 'default' | 可以看出,所谓的默认导出其实就是用了 `default` 作为名字罢了。 还能够将其他模块重新导出 |Statement | Module|Import name|Export name| |---------------------------|-------|-----------|-----------| |export {v} from 'mod'; |'mod' |'v' |'v' | |export {v as x} from 'mod';|'mod' |'v' |'x' | |export * from 'mod'; |'mod' |'*' |null | 导入有很多方法,基本使用到的其实只有几种,以下来自 MDN: ```javascript import name from "module-name"; import * as name from "module-name"; import { member } from "module-name"; import { member as alias } from "module-name"; import { member1 , member2 } from "module-name"; import { member1 , member2 as alias2 , [...] } from "module-name"; import defaultMember, { member [ , [...] ] } from "module-name"; import defaultMember, * as alias from "module-name"; import defaultMember from "module-name"; import "module-name"; ``` 最后那种导入是相当于将代码执行了一次。通常可以用来做 `polyfills` 和 `shims`。 ##杂七杂八[↑](#catalogue) `Number.isInteger`,判断整数 `Number.isSafeInteger`,判断是否是有效整数 Math 中加入很多函数,例如双曲正弦、双曲余弦之类的