《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. 类
  13. Promises
  14. 模块(Modules)
  15. 杂七杂八

块级绑定(Block Bindings)↑

let

块级{}中有效

同块级不可重复声明

没有变量提升

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

const

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

let 和 const 最好不要在全局下使用

字符串↑

unicode 支持更好

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

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

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

includes,包含某字符串

startsWith,以某字符串开始

endsWith,以某字符串结束

repeat,重复字符串

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

正则↑

新增标志 u

正则识别 unicode 字符

新增标志 y

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

字符串模板(template strings)↑

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

标签模板(tagged templates)↑

1
2
3
4
5
6
7
8
9
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]

函数↑

默认参数

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

剩余参数

1
2
3
4
5
6
7
function foo ( bar, ...rest ) {  // ✓
;
}

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

函数属性 name

各种例子

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

1
2
3
4
5
6
7
8
9
10
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 属性

1
2
3
4
var foo = value => value;  // input value, output value
var foo = () => {};
var foo = ( x, y ) => x + y;
var foo = id => ({ x: 'x' });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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))

1
2
3
4
5
6
7
8
9
let foo = function ( s ) {
console.log( s );
}( 'text' ) // text

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

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

新增尾递归优化

对象↑

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

1
2
3
4
5
function foo ( text ) {
return {
name // name: name
}
}

对象方法简写(Method Initializer Shorthand)

1
2
3
var foo = {
bar () {}
}

计算属性名语法

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

1
2
3
4
5
6
7
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()

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

1
2
3
4
5
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()

1
Object.assign( target, ...source )

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

复制存取器属性

此小节查询 MDN 后补充上

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

属性允许重复定义

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

修改原型

Object.getPrototypeOf,得到原型

Object.setPrototypeOf,设置原型

super

用以访问对象的 prototype

解构(Destructuring)↑

1
2
3
4
5
6
7
8
9
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 有更详细清晰的解释:

1
2
3
4
5
6
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(不知道如何翻译,是第七种原始类型)↑

1
2
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)↑

生成迭代器的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 指向得到迭代器的函数

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

自定义迭代器

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

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

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

keys(),返回键迭代器

values(),返回值迭代器

字符串迭代器

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

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

元素数组(NodeList)迭代器

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

向迭代器传参数

1
2
3
4
5
6
7
8
9
10
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 提前返回

1
2
3
4
5
6
7
8
9
10
11
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 *

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

1
2
3
4
5
6
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 的管理容易出问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();

类↑

类声明

1
2
3
4
5
6
7
8
9
10
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 定义的,对于外部则不是)

类表达式

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

匿名类作为参数

1
2
3
4
5
6
7
8
9
function createFoo ( c ) {
return new c()
}

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

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

1
2
3
4
5
let foo = new class {
constructor ( name ) {
this.name = name
}
}( 'foo' )

存取器属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class foo {
constructor ( name ) {
this.name = name
}

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

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

静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 的派生方便了很多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 是必须的。如果连构造函数都没有,则:

1
2
3
4
5
6
7
8
9
10
class Square extends Rectangle {
// 无构造函数
}

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

类方法

覆盖、隐藏父类方法

1
2
3
4
5
6
7
8
class Square extends Rectangle {
constructor ( length ) {
super( length, length );
}
getArea () {
return this.length * this.length;
}
}

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

1
2
3
4
5
6
7
8
class Square extends Rectangle {
constructor ( length ) {
super( length, length );
}
getArea () {
return super.getArea();
}
}

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

静态成员

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

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

除了 null 和生成器函数外

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

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

1
2
3
4
5
6
7
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error("This class cannot be instantiated directly.")
}
}
}

Promises↑

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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. 模块必须有导出的东西

基本导入导出

直接使用原书代码:

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
// 导出数据
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 只能用在顶层中

as 和 default 语法的情况,给出一个来自 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:

1
2
3
4
5
6
7
8
9
10
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。

杂七杂八↑

Number.isInteger,判断整数

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

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

exoticknight wechat
扫描关注公众号
Or buy me a coffee ☕ ?
  • 本文作者: exoticknight
  • 本文链接: https://blog.e10t.net/understanding-ecmascript6-note/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。图片以及多媒体资料,均属原作者或本人所有。禁止任何形式的转载!
  • 授权转载务必保留以上声明和二维码。