Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

exoticknight's blog


年寿有时而尽,荣乐止乎其身,二者必至之常期,未若文章之无穷。

《Understanding ECMAScript 6》笔记

在线免费阅读:https://leanpub.com/understandinges6/read/

部分代码使用原书,代码版权归原书所有

  1. 块级绑定(Block Bindings)
  2. 字符串
  3. 正则
  4. 字符串模板(template strings)
  5. 标签模板(tagged templates)
  6. 函数
  7. 对象
  8. 解构(Destructuring)
  9. Symbols
  10. 生成器(Generators)
  11. 迭代器(Iterators)
  12. Promises
  13. 模块(Modules)
  14. 杂七杂八

块级绑定(Block Bindings)

let

块级{}中有效

同块级不可重复声明

没有变量提升

块级会形成暂时性死区(TDZ,Temporal Dead Zone)

const

基本和 let 相同,值不可修改

letconst 最好不要在全局下使用

字符串

unicode 支持更好

新增部分函数,支持双字节

codePointAt,双字节版的 charCodeAt,得到字符 unicode

fromCodePoint,双字节版的 fromCharCode,从 unicode 得出字符

includes,包含某字符串

startsWith,以某字符串开始

endsWith,以某字符串结束

repeat,重复字符串

normalize,unicode 正规化,举个例子:两个 unicode 字符合成一个

正则

新增标志 u

正则识别 unicode 字符

新增标志 y

sticky,部分浏览器早就实现了

字符串模板(template strings)

let a = 1  
let b = 2  
let s = `${a} ${a + b}`  // '1 3'  

标签模板(tagged templates)

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]

函数

默认参数

function foo ( bar = 1 ) {  
  console.log( bar )
}

剩余参数

function foo ( bar, ...rest ) {  // ✓  
  ;
}

function foo ( bar, ...rest, last ) {  // ×  
  ;
}

函数属性 name

各种例子

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 的坑

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, argumentsnew.target 的值都在定义函数时绑定而非运行时绑定

不可 new

不可改变 this 的值

没有 arguments

跟普通函数一样拥有 name 属性

var foo = value => value;  // input value, output value  
var foo = () => {};  
var foo = ( x, y ) => x + y;  
var foo = id => ({ x: 'x' });  
// 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))

let foo = function ( s ) {  
  console.log( s );
}( 'text' )  // text

// -------------------------

let foo = ( s => {  
  console.log( s );
})( 'text' )  // text

新增尾递归优化

对象

对象字面属性值简写(Property Initializer Shorthand)

function foo ( text ) {  
  return {
    name  // name: name
  }
}

对象方法简写(Method Initializer Shorthand)

var foo = {  
  bar () {}
}

计算属性名语法

对象的属性可以使用中括号 [] 表示需要「被计算」,结果转换为字符串作为属性名使用。

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()

和经典的 === 几乎一样,区别在于:

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()

Object.assign( target, ...source )  

读取源对象可列举的、自身的属性,将其赋值到目标对象上,覆盖旧属性,并非通常意义的复制。

复制存取器属性

此小节查询 MDN 后补充上

使用 Object.getOwnPropertyDescriptor(source, key) 读取,使用 Object.defineProperties 定义。

属性允许重复定义

属性以最后一个定义的值为准

修改原型

Object.getPrototypeOf,得到原型

Object.setPrototypeOf,设置原型

super

用以访问对象的 prototype

解构(Destructuring)

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 有更详细清晰的解释:

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(不知道如何翻译,是第七种原始类型)

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)

生成迭代器的函数

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)

for-of 语法

数组、字符串、映射(Map)、集合(Set)和元素数组(NodeList)都可迭代(iterable),可使用 for-of 语法

得到内置迭代器

Symbol.iterator 指向得到迭代器的函数

let values = [1, 2, 3];  
let iterator = values[Symbol.iterator]();  
iterator.next();  // 1  

自定义迭代器

let collection = {  
  items: [],
  *[Symbol.iterator]() {
    yield *this.items.values();  // `yield *` 语法,委托了数组 `items` 的内置迭代器
  }
};

对象、数组、映射、集合都具有的默认迭代器

ertries(),返回键值对迭代器

keys(),返回键迭代器

values(),返回值迭代器

字符串迭代器

通过 [] 的访问是 code unit 方式

通过迭代器则是字符方式(几乎是,某些 unicode 支持不足)

元素数组(NodeList)迭代器

返回的是数组中的单个元素

向迭代器传参数

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 提前返回

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 *

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",会调用字符串的默认迭代器

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 的管理容易出问题:

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();  

类声明

class foo {  
  // 相当于构造函数
  constructor ( name ) {
    this.name = name;
  }
  // 相当于 foo.prototype.bar
  bar () {
    console.log( this.name );
  }
}

类的属性最好都在构造函数里面创建。

类声明本质上就是以前的函数声明,除了以下有所不同:

  1. 类声明不会像函数声明那样被提升
  2. 类内部的代码全部以 strict mode 运行
  3. 所有方法都是不可列举的,相当于使用了 Object.defineProperty()
  4. 不使用 new 会抛异常
  5. 以类名命名方法来覆盖类名会抛异常(类名对于类内部来说是以 const 定义的,对于外部则不是)

类表达式

let foo = class {}  
let foo = class foo2 {}  // foo === foo2  

匿名类作为参数

function createFoo ( c ) {  
  return new c()
}

createFoo( class {  
  constructor () {
    ;
  }
})

立即调用类表达式(有点像立即调用函数表达式)

let foo = new class {  
  constructor ( name ) {
    this.name = name
  }
}( 'foo' )

存取器属性

class foo {  
  constructor ( name ) {
    this.name = name
  }

  get className () {
    return 'class ' + this.name
  }

  set className ( value ) {
    this.name = 'class' + value
  }
}

静态成员

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 的派生方便了很多

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 是必须的。如果连构造函数都没有,则:

class Square extends Rectangle {  
  // 无构造函数
}

// 相当于
class Square extends Rectangle {  
  constructor ( ...args ) {
    super( ...args )
  }
}
  1. 只能在派生类中用 super()
  2. 使用 this 前必先调用 super()初始化 this
  3. 只有在构造函数返回一个对象的时候才可以不用 super()

类方法

覆盖、隐藏父类方法

class Square extends Rectangle {  
  constructor ( length ) {
    super( length, length );
  }
  getArea () {
    return this.length * this.length;
  }
}

仍然可以使用 super 调用父类方法

class Square extends Rectangle {  
    constructor ( length ) {
        super( length, length );
    }
    getArea () {
        return super.getArea();
    }
}

类方法没有 [[Construct]] 这个内部方法,所以不能被 new。(什么是[[Construct]]

静态成员

相当于 ES5 中定义在构造函数上的方法(注意不是定义在构造函数的原型上),派生类显然也能调用

extends 关键字后面可以使用表达式

除了 null 和生成器函数外

// 使用函数
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

能够得知类的调用状态,应用例如:阻止抽象类被实例化

class Shape {  
  constructor() {
    if (new.target === Shape) {
      throw new Error("This class cannot be instantiated directly.")
    }
  }
}

Promises

Promise 是老朋友了,所以没有什么好记录的,就记一下语法。

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)

注:本章的代码似乎有一些问题,基本参考 MDN 为准

  1. 模块中的代码自动以严格模式运行
  2. 模块中的顶层变量只是模块中顶层,并非全局顶层
  3. 顶层中的 this 的值为 undefined
  4. 代码中不允许 HTML 风格的注释
  5. 模块必须有导出的东西

基本导入导出

直接使用原书代码:

// 导出数据
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 语法,否则函数和类都不能使用匿名
  2. export 只能用在顶层中

asdefault 语法的情况,给出一个来自 2ality 的表格

|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:

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";  

最后那种导入是相当于将代码执行了一次。通常可以用来做 polyfillsshims

杂七杂八

Number.isInteger,判断整数

Number.isSafeInteger,判断是否是有效整数

Math 中加入很多函数,例如双曲正弦、双曲余弦之类的


About the author

exoticknight


Discussions

comments powered by Disqus