说明
系统记录 ES6 知识,方便学习查看~
块级作用域绑定
let / const
- const 一旦声明变量,就必须立即初始化,不能留到以后赋值
- const 命令只是保证变量名指向的地址不变,并不保证该地址的数据不变。如果真的想将对象冻结,应该使用 Object.freeze 方法
- 不能重复声明,如果作用域已经存在某个声明过的标识符,则不能再使用 let / const 关键字声明它
- 在
for-in
或for-of
循环中使用const
时的行为与使用let
一致 - var 命令和 function 命令声明的全局变量,依旧是全局对象(window,不适用于 node 环境)的属性;另一方面规定, let 命令、 const 命令、 class 命令声明的全局变量,不属于全局对象的属性。变量没有声明就赋值,则自动添加到全局对象上(包括 node 环境)
1 | var a = 1; |
- 花括号内形成块级作用域,花括号外无法访问
1 | { |
- 临时死区
1 | console.log(typeof value); // ReferenceError: value is not defined |
1 | console.log(typeof value); // undefined |
在 let / const 声明的作用域外对该变量使用 typeof 不会报错
字符串和正则表达式
Unicode
UTF-16
在 UTF-16 中,前 $2^{16}$ 个码位均以 16 位的编码单元表示,这个范围被称作 基本多文种平面 (BMP) 。超出这个范围的码位使用 _代理对_,用两个 16 位编码单元表示一个码位
1 | var a = '𠮷'; |
normalize() 方法
- 在对比字符串之前,一定先把他们标准化为同一种形式
1 | values.sort((first, second) => { |
正则表达式 u 修饰符
- 添加 u 修饰符后,正则表达式就不会视代理对为两个字符
1 | let a = '𠮷'; |
- 可以通过正则来检测字符串的码位数量
1 | function codePointLen(text) { |
- 检测是否支持 u 修饰符,对于 y 修饰符也是同理
1 | function hasRegExpU() { |
其他字符串变更
repeat()
方法
1 | console.log('x'.repeat(3)); // xxx |
其他正则表达式变更
正则表达式 y 修饰符
- y 修饰符:影响正则表达式搜索过程中的 sticky 属性,当在字符串中开始字符匹配时,它会通知搜索从正则表达式的
lastIndex
属性开始进行,如果在指定位置没能成功匹配,则停止继续匹配。
1 | let text = 'hello1 hello2 hello3', |
只有调用
exec()
和test()
这些正则表达式对象的方法才会涉及lastIndex
属性。
正则表达式的复制
- 在 ES5 中抛出错误,在 ES6 中正常运行。可以通过第二个参数修改其修饰符
1 | var re1 = /ab/i; |
flags 属性
- ES5:通过 source 属性获取正则表达式的文本
- ES6 新增,新增 flags 属性,它与 source 属性都是只读的
1 | let re = /ab/g; |
模板字符串
- 字符串可以被 for…of 循环遍历,最大的优点是可以识别大于 0xFFFF 的码点,传统的 for 循环无法识别这样的码点
- 字符串中嵌入变量,需要将变量名写在 ${} 之中
- 多行模板字符串会在每一行的最后添加一个“\n”字面量,相当于使用 LF 换行符,所以在读取多行字符串的长度时,除最后一行以外,每一行的长度都会加 1,即增加了”\n”
1 | const str = `A |
如要打印出反勾号可以在反勾号前面加上一个反斜杠
标签模板
- 定义标签
1 | function passthru(literals, ...substitutions) { |
标签函数的第一个参数是一个数组,它还有一个额外的属性 raw。
literals.raw[i]
String.raw()
1 | let msg1 = 'a\nb'; |
函数
函数默认参数值
1 | function fn(arg = 'foo') { |
与解构赋值默认值结合
- 默认参数值
1 | function foo({ x, y = 5 }) { |
- 指定了默认值以后,函数的
length
属性,将返回没有指定默认值的参数个数。
1 | (function (a) {} |
- 在 ES6 中,
arguments
对象的行为都将与 ES5 严格模式下保持一致。默认参数值的存在使得arguments
对象保持与命名参数分离
1 | function mixArg(first, second = 'b') { |
默认参数表达式
1 | let value = 0; |
初次解析函数声明时不会调用
getValue()
方法,只有当调用add()
函数且不传入第二个参数时(或者为undefined
)才会调用
不定参数
- 每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾,否则会抛出错误
- 不定参数不能用于对象字面量
setter
之中,否则报错
1 | function fn1(...rest) { /* ... */ } // Correct |
函数的多重用途
函数有两个不同的内部方法:[[Call]] 和 [[Contruct]]。当通过 new
关键字调用函数时,执行的是 [[Contruct]] 函数,当通过 call
和 apply
调用时,则执行 [[Call]] 函数。具有 [[Contruct]] 方法的函数被称为构造函数。不是所有函数都有 [[Construct]] 方法,因此不是所有函数都可以通过 new 来调用,如箭头函数
- ES5 判定函数被调用的方法
1 | function Person(name) { |
无法区分是通过
Person.call()
(或者是Person.apply()
)还是new
关键字调用得到的 Person 的实例
元属性 new.target
当调用函数的 [[Construct]] 方法时,new.target
被赋值为 new 操作符的目标,通常是新创建对象实例,也就是函数体内 this 的构造函数;如果调用 [[Call]] 方法,则 new.target
的值为 undefined
1 | function Person(name) { |
箭头函数
this
没有 this、super、arguments 和 new.target 绑定,这些值由外围最近一层非箭头函数决定
在箭头函数出现之前,每个新定义的函数都有它自己的
this
值(在构造函数的情况下是一个新对象,在严格模式的函数调用中为 undefined)箭头函数不会创建自己的
this
;它使用封闭执行上下文的this
值。因此,在下面的代码中,传递给setInterval
的函数内的this
与封闭函数中的this
值相同:1
2
3
4
5
6
7
8
9function Person() {
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向person 对象
}, 1000);
}
var p = new Person();鉴于
this
是词法层面上的,严格模式中与this
相关的规则都将被忽略不能通过 new 关键字调用,没有原型,不支持 arguments 对象,但可以访问外围函数的 arguments 对象,不支持重复的命名参数
this
已经在词法层面完成了绑定,通过call()
或apply()
方法调用一个函数时,只是传入了参数而已,对this
并没有什么影响。箭头函数的 this 一旦绑定后就无法被修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const a = {
init() {
this.bar = () => this.dam;
},
dam: 'hei',
foo() {
return this.dam;
},
};
const b = {
dam: 'ha',
};
a.init(); // 箭头函数中的this执行时绑定了,为a
console.log(a.bar()); // hei
console.log(a.bar.call(b)); // hei。无法修改其this
注意事项
箭头函数不能用作构造器,和
new
一起用会抛出错误箭头函数没有
prototype
属性,其值为undefined
不能在箭头函数中使用
yield
关键字(除非是嵌套在允许使用的函数内)记得用圆括号把返回的对象字面量包起来
1
var func = () => ({ foo: 1 });
箭头函数在参数和箭头之间不能换行,否则报错
箭头函数具有与常规函数不同的特殊运算符优先级解析规则。
1
2
3
4
5
6
7
8let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok箭头函数中也没有 arguments、callee,可以使用后续参数代替arguments
1
2
3
4const fn = (...args) => {
console.log(args[0]);
};
fn(1, 2); // 1
尾调用优化
尾调用指的是函数作为另一个函数的最后一条语句被调用
1 | function doSomething() { |
ES6 尾调用优化
ES6 严格模式下缩减了尾调用栈的大小(非严格模式下不受影响),如果满足以下条件,尾调用不再创建新的栈帧,而是清除并重用当前栈帧
- 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包)
- 在函数内部,尾调用是最后一条语句
- 尾调用的结果作为函数值返回
1 | // 以下情况均无法优化 |
1 | // 优化前 |
扩展对象的功能性
对象字面量扩展语法
可计算属性名
ES6 引入的新语法允许我们直接使用一个表达式来表达一个属性名用法:{ [statement]: value}
1 | const prefix = 'ES6'; |
属性初始值简写
变量名和属性名都是相同的,我们可以对属性名定义进行省略。
1 | const foo = 123; |
Object.assign()
Object.assign()
方法不能将提供者的访问器属性复制到接收对象中,由于 Object.assign()
方法执行了赋值操作,因此提供者的访问器属性最终会转变为接收对象中的一个数据属性
1 | var receiver = {}, |
自有属性枚举顺序
ES5 没有定义对象属性的枚举顺序,ES6 规定了,这会影响到 Object.getOwnPropertyNames()
方法及 Reflect.ownKeys
返回属性的方式
自有属性枚举顺序的基本规则是:
- 所有数字键按升序排序
- 所有字符串键按照它们被加入对象的
- 所有 symbol 键按照它们被加入对象的顺序排序
1 | var obj = { |
增强对象原型
简化原型访问的 Super 引用
Super 引用相当于指向对象原型的指针,实际也就是 Object.getPrototypeOf(this)
的值
1 | let person = { |
- 多重继承时,
Object.getPrototypeOf()
方法会出现问题
1 | let person = { |
Super 引用不是动态变化的,它总是指向正确的对象,在这个示例中,无论有多少其他方法继承了 getGreeting 方法,super.getGreeting() 始终指向 person.getGreeting() 方法
解构
数组解构
- 解构赋值中,如果希望跳过数组中某些元素,可以通过空开一个元素的方式实现
1 | // 用法:[ arg1, , arg2 ] = [ value1, value2, value3] |
- 解构赋值中,获取指定位置的元素以外,也可以不定项地获取后续的元素,那么可以用
…
语句来实现,在被结构的数组中,不定元素必须为最后一个条目,否则报错:
1 | // 用法:[ arg1, arg2, ...rest ] = [ value1, value2, value3, value4] |
1 | function fetchData() { |
1 | // 变量值交换 |
- 对
null
或undefined
进行解构,会报错
对象解构
设置默认值,当无法匹配时使用
1 | const { foo = 1 } = { bar: 1 }; |
深度匹配
1 | // Object |
1 | const arr = ['Mike', 'Peter', 'Ben', 'William', 'John']; |
forEach
无法像for
、while
等循环语句一样被break
等控制语句终止,所以可以使用for-of
循环语法
结构参数
必须传值的解构参数
1 | function setCookie(name, value, { secure, path, domain, expires }) { |
如果不传第 3 个参数,会导致其值为 undefined,等同于对 undefined 进行解构,可以通过如下方法解决
1 | function setCookie(name, value, { secure, path, domain, expires } = {}) { |
- 默认值
1 | const setCookieDefaults = { |
补充
不只是声明
赋值表达式并不必须是变量标识符。任何合法的赋值表达式都可以
1 | function foo() { |
重复赋值
对象解构形式允许多次列出同一个源属性(持有值类型任意)
1 | var { a: X, a: Y } = { a: 1 }; |
解构赋值表达式
对象或者数组解构的赋值表达式的完成值是所有右侧对象 / 数组的值
1 | let o = { a: 1, b: 2, c: 3 }; |
p 赋值为对象 o 的引用
Symbol
- Symbol 是一种值类型而非引用类型
- Symbol 与一个字符串拼接,会报错
用法
Symbol 值通过
Symbol
函数生成。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。1
2
3
4
5
6
7
8
9let s = Symbol();
console.log(typeof s);
// "symbol"
let s1 = Symbol('foo');
console.log(s1); // Symbol(foo)
let s2 = Symbol('foo');
console.log(s1 === s2); // false
Symbol
函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分1
2
3
4
5
6
7
8let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1); // Symbol(foo)
console.log(s2); // Symbol(bar)
console.log(s1.toString()); // "Symbol(foo)"
console.log(s2.toString()); // "Symbol(bar)"Symbol 的参数是一个对象,就会调用该对象的
toString
方法,将其转为字符串,然后才生成一个 Symbol 值
注册全局可重用 Symbol
开发者可以通过一个 key 向当前运行时注册一个需要在其他程序中使用的 Symbol:Symbol.for([key]) : Symbol
1 | cosnt symbol = Symbol.for('foo'); |
Symbol.for() 会根据传入的 key 在全局作用域中注册一个 Symbol 值,如果某一个 key 从未被注册到全局作用域中,便会创建一个 Symbol 值并根据 key 注册到全局环境中,如果该 key 已被注册,就会返回一个与第一次使用创建的 Symbol 的值等价的 Symbol 值
1 | let sy = Symbol.for('key'); |
Object.getOwnPropertySymbols
该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
注意:Symbol 作为属性名,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回
1 | const obj = {}; |
获取全局 Symbol 的 key
使用同一个 Symbol 值,Symbol.for
方法可以做到这一点,它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值
用法:Symbol.keyFor(someSymbol) : String
1 | const symbol = Symbol.for('foo'); |
通过 well-known Symbol 暴露内部操作
- Symbol.hasInstance:用于确定对象是否为函数的实例,用于重新定义 instanceof 操作符返回结果。不可写,不可配置,不可枚举
- Symbol.isConcatSpreadable:一个布尔值,用于表示当传递一个集合作为
Array.prototype.concat()
方法的参数时,是否应该将集合内的元素规整到同一层级 - Symbol.iterator:返回迭代器的方法
- Symbol.match: 在调用
String.prototype.match()
方法时调用的方法 - Symbol.replace:在调用
String.prototype.replace()
方法时调用的方法 - Symbol.search:在调用
String.prototype.search()
方法时调用的方法 - Symbol.species:用于创建派生对象的构造函数
- Symbol.split:在调用
String.prototype.split()
方法时调用的方法 - Symbol.toPrimitive:返回对象原始值的方法
- Symbol.toStringTag:调用
Object.prototype.toString()
方法时使用的字符串 - Symbol.unscopables:定义一些不可被 with 语句引用的对属性名称的对象集合
Symbol.hasInstance
obj instanceof Array
等价于 Array[Symbol.hasInstance](obj)
Symbol.hasInstance
为开发者提供了可以用于扩展 instanceof 语句内部逻辑的权限,开发者可以将其作为属性键,或用于为一个类定义静态方法,该方法的第一个形参便是被检测的对象,而该方法的返回值便是决定了 instanceof 语句的返回结果。
1 | // 用法1 |
1 | // 用法2 |
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable 属性是一个布尔值,如果该布尔值为 true,则表示对象有 length 属性和数字键,故它的数值型属性值应该被独立添加到 concat() 调用的结果中
1 | let collection = { |
Symbol.match, Symbol.replace, Symbol.search, Symbol.split
1 | // 实际上等价于 /^.{10}$/ |
Symbol.toPrimitive
JS 执行特定操作时,会尝试将对象转换到相应的原始值。例如,使用 == 运算符比较对象和字符串,对象会在比较操作执行前被转换为一个原始值。
Symbol.toPrimitive
方法被定义在每一个标准类型的原型上,并且规定了当对象呗转换为原始值时应当执行的操作。当执行原始值转换时,总会调用 Symbol.toPrimitive
方法并传入一个参数,这个参数值被称为类型提示(hint)。
类型提示参数的值只有 3 种:**’number’, ‘string’ 或 ‘default’**。传递这些参数时, Symbol.toPrimitive
返回的分别是 数字、字符串或无类型偏好的值
1 | function Temperature(degrees) { |
Symbol.toStringTag
1 | function Person() {} |
Set & Map
Set
- 初始化:Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化
- 通过 add、delete 和 clear 方法来添加、删除和清空集合内的元素。
- 通过 has 方法用于检查某集合中是否包含某一个元素。
- size 属性获取数量
- 引擎内部使用
Object.is()
方法检测两个值是否一致。唯一的例外是,Set 集合中的 +0 和 -0 被认为是相等的 - 如果多次调用 add() 方法传入相同的值作为参数,那么后续的调用实际上会被忽略
- forEach 的回调函数接受 3 个参数:值,与第一个参数一样的值,被遍历的 Set 集合
1 | let set = new Set([1, 2]); |
WeakSet
与 Set 的差别:
- 如果 add() 方法传入非对象参数会导致程序报错,向 has() 和 delete() 方法传入非对象参数则会返回 false
- 不可迭代,不能使用 for-of 循环,不能使用 forEach 方法
- 不暴露任何迭代器(如 keys() 和 values())
- 不支持 size
实现将字符串等值类型加入到 WeakSet 数据结构中:
1 | var ws = new WeakSet(); |
被加入到 WeakSet 中的字符串和数字等不能被修改,因为一旦进行修改其引用便会丢失,甚至导致被移除出集合。
WeakSet 不能包含无引用的对象,否则会自动清除出集合
1 | const weakset = new WeakSet(); |
Map
- set(key, value) :添加键值对到映射中
- get(key):获取映射中某一个键的对应值
- delete(key):将某一键值对移除出映射中
- clear(): 清空映射中所有的键值对
- has(key):检查映射中是否包含某一键值对
- entries():返回一个以二元数组(键值对)作为元素的数组
- keys(): 返回一个一当前映射中所有键作为元素的可迭代对象
- values():返回一个一当前映射中所有值作为元素的可迭代对象
- map.size:键值对数量
- forEach 的回调函数接受 3 个参数:值,值对应的键名,被遍历的 Map 集合
1 | let myMap = new Map([ |
WeakMap
- 键名必须是一个对象,否则报错
迭代器(Iterator) & 生成器(Generator)
https://sluggishpj.github.io/blog/2018/08/28/es6-iterator-generator/
类(Class)
类的声明
类属性不可被重新赋值,只可读
类声明与 let 声明类似,不能被提升
类声明中的所有代码自动运行在严格模式下,而且无法强行让代码脱离严格模式执行
类中所有方法都是不可枚举的
Class 的取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用 get
和 set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。存值函数和取值函数会被子类继承
1 | class MyClass { |
类表达式
1 | let Person = class { |
命名类表达式
1 | let Person = class PersonClass { |
作为一等公民的类
1 | let pj = new (class { |
Class 继承
- 如果一个子类继承了一个父类,**子类在构造函数中访问 this 前一定要调用 super()**,否则新建实例时会报错。
- 如果不调用 super 方法,子类就得不到 this 对象。如果子类不写 constructor 方法,默认调用父类的 constructor 方法
1 | class Square extends Rectangle { |
1 | class Square extends Rectangle { |
静态成员继承
1 | class Rectangle { |
继承自表达式的类
- ES2015 的继承语法同样可以将以前使用的构造函数模拟的类作为父类来继承
1 | function Rectangle(length, width) { |
内建对象继承
ES5 之前无法通过继承的方式创建 特殊数组
1 | class MyArray extends Array {} |
Symbol.species
用于定义子类的方法返回实例时,应当返回的值的类型。该属性被返回的函数是一个构造函数,每当要在实例的方法中(不是构造函数中)创建类的实例时必须使用这个构造函数。
1 | class MyClass { |
MyDerivedClass1
继承MyClass
时未改变Symbol.species
属性,this.constructor[Symbol.species]
的返回值是MyDerivedClass1
,clone()
返回的是MyDerivedClass1
的实例MyDerivedClass2
继承MyClass
时重写了Symbol.species
让其返回MyClass
,clone()
返回的是一个MyClass
实例
在类构造函数中使用 new.target
1 | class Rectngle { |
改进的数组
新增方法
- Array.of()
- Array.from()
- Array.prototype.find()
- Array.prototype.findIndex()
- Array.prototype.fill()
- Array.prototype.copyWithin()
定型数组
https://sluggishpj.github.io/blog/2018/07/28/es6-typed-array/
Promise
使用方法
1 | let myPromise = new Promise(function (resolve, reject) { |
resolve
函数的参数除了正常的值以外,还可能是另一个 Promise 实例
1 | const p1 = new Promise(function (resolve, reject) { |
p1
的状态决定了p2
的状态。如果p1
的状态是pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是resolved
或者rejected
,那么p2
的回调函数将会立刻执行由
p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对p1
then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例),因此可以采用链式写法一般来说,不要在
then
方法里面定义 Reject 状态的回调函数(即then
的第二个参数),总是使用catch
方法。
捕获错误
跟传统的try/catch
代码块不同的是,如果没有使用catch
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会阻止其他代码的执行
1 | const someAsyncThing = function () { |
注意事项
- 如果一个 Promise 处于已处理状态,在这之后添加到任务队列中的处理程序仍将执行。
1 | const fs = require('fs'); |
- Promise 的执行器会立即执行,然后才执行后续流程中的代码
1 | let promise = new Promise(function (resolve, reject) { |
全局的 Promise 拒绝处理
Node.js 环境的拒绝处理
处理 Promise 拒绝时会触发 process 对象上的两个事件
unhandledRejection
: 在一个事件循环中,当 Promise 被拒绝,并且没有提供拒绝处理程序时,触发该事件rejectionHandled
:在一个事件循环后,当 Promise 被拒绝时,若拒绝处理程序被调用,触发该事件
1 | // unhandledRejection |
1 | // rejectionHandled |
Promise 链的返回值
在完成处理程序中指定一个返回值,就可以沿着这条链继续传递数据
1 | let p1 = new Promise(function (resolve, reject) { |
1 | let p1 = new Promise(function (resolve, reject) { |
Promise.all(iterable)
该方法传入一个可迭代对象(如数组),并返回一个 Promise 对象,该 Promise 对象会在当可迭代对象中的所有 Promise 对象都进入完成状态(包括成功和失败)后被激活。
如果可迭代对象中的所有 Promise 对象都进入了成功状态,那么该方法返回的 Promise 对象也会进入成功状态,并以一个可迭代对象来承载其中一个的所有返回值。
如果可迭代对象中 Promise 对象的其中一个进入了失败状态,那么该方法返回的 Promise 对象也会进入失败状态,并以那个进入失败状态的错误信息作为自己的错误信息。
1 | const promise = [async(1), async(2), async(3), async(4)]; |
Promise.race(iterable)
这个方法会监听所有的 Promise 对象,并等待其中的第一个进入完成状态的 Promise 对象。一旦有第一个 Promise 对象进入了完成状态,该方法返回的 Promise 对象便会根据这第一个完成的 Promsie 对象的状态而改变。
1 | const promise = [async(1), async(2), async(3), async(4)]; |
Promise.resolve()
将现有对象转为 Promise 对象
1 | const jsPromise = Promise.resolve($.ajax('/whatever.json')); |
如果参数是 Promise 实例,那么
Promise.resolve
将不做任何修改参数是一个 thenable 对象(thenable 对象指的是具有
then
方法的对象,Promise.resolve
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then
方法1
2
3
4
5
6
7
8
9
10let thenable = {
then: function (resolve, reject) {
resolve(42);
},
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});thenable
对象的then
方法执行后,对象p1
的状态就变为resolved
,从而立即执行最后那个then
方法指定的回调函数,输出 42如果参数是一个原始值,或者是一个不具有
then
方法的对象,则Promise.resolve
方法返回一个新的 Promise 对象,状态为resolved
1
2
3
4
5
6const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s);
});
// Hello不带有任何参数
Promise.resolve
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象1
2
3
4
5const p = Promise.resolve();
p.then(function () {
// ...
});
立即
resolve
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时1
2
3
4
5
6
7
8
9
10
11
12
13setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数
代理(Proxy)和反射(Reflection)
https://sluggishpj.github.io/blog/2018/09/25/es6-proxy-reflection/
模块
导入 import
注意:当从模块导入一个绑定时,它相当于使用 const 定义的一样,因此无法定义同名变量,也无法改变绑定的值
1 | // 择需加载 |
导出 export
注意:不能单独使用 export 导出匿名函数或类,除非结合使用 default 关键字
1 | export function isInteger(num) { |
导出默认值
1 | // 为模块指定默认输出。 |
重新导出一个绑定
1 | import { sum } from './example.js'; |
无绑定导入
用来执行模块代码
1 | import './example.js'; |
Web 浏览器中使用模块
在 <script> 中使用模块
将 type 设置为 ‘module’ 即可。
注意:执行时自动应用 defer 属性。文件名必须带上后缀(可以为 js 或 mjs)
1 | <script type="module" src="./module.js"></script> |
将模块作为 Worker 加载
1 | let worker = new Worker('test2.js', { type: 'module' }); |
浏览器模块说明符解析
- 以 / 开头的解析为从根目录开始
- 以 ./ 开头的解析为从当前目录开始
注意点
- export 和 import 的一个重要的限制是,它们必须在其他语句和函数之外使用,必须在最外层作用域
- 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见
- 模块脚本自动采用严格模式,不管有没有声明
use strict
- 模块之中,可以使用
import
命令加载其他模块,也可以使用export
命令输出对外接口 - 模块之中,顶层的
this
关键字返回undefined
,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无意义的 - 模块是单例,同一个模块如果加载多次,将只执行一次
- ES6 模块的 API 是静态的。也就是说,需要在模块的公开 API 中静态定义所有最高层导出,之后无法补充。
- 模块的公开 API 中暴露的属性和方法并不仅仅是普通的值或引用的赋值。它们是到内部模块定义中的标识符的实际绑定(几乎类似于指针)。导出一个局部私有变量,即使当前它持有一个原生字符串 / 数字等,导出的都是到这个变量的绑定。如果模块修改了这个变量的值,外部导入绑定现在会决议到新的值。eg.
1 | // count.mjs |
1 | // index.mjs |
- 导入模块和静态请求加载(如果还没加载的话)这个模块是一样的。如果是在浏览器环境中,这意味着通过网络阻塞加载;如果是在服务器上(比如 Node.js),则是从文件系统的阻塞加载。但是,不要惊慌于这里的性能暗示。因为 ES6 模块具有静态定义,导入需求可以静态扫描预先加载,甚至是在使用这个模块之前。
与 CommonJS 模块差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
- JS 引擎对脚本静态分析的时候,遇到模块加载命令
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值,不能重新赋值(好比是 const 变量)
1 | // util.js |
1 | // index.js |
模块依赖环
模块 A
1 | import bar from 'B'; |
模块 B
1 | import foo from 'A'; |
ES6 模块声明在完全不同的作用域,所以 ES6 需要额外的工作来支持这样的循环引用。
下面是从粗略概念的意义上循环的 import 依赖如何生效和解析的过程。
- 如果先加载模块 “A”,第一步是扫描这个文件分析所有的导出,这样就可以注册所有可以导入的绑定。然后处理
import .. from "B"
,这表示它需要取得 “B”。 - 引擎加载 “B” 之后,会对它的导出绑定进行同样的分析。当看到
import .. from "A"
,它已经了解 “A” 的 API,所以可以验证 import 是否有效。现在它了解 “B” 的 API,就可以验证等待的 “A” 模块中import .. from "B"
的有效性。
1 | import foo from 'foo'; |
1 | import bar from 'bar'; |
在
foo(25)
或bar(25)
调用执行的时候,所有模块的所有分析 / 编译都已经完成。这意味着foo(..)
内部已经直接了解bar(..)
,而bar(..)
内部也已经直接了解foo(..)
。
import 语句的静态加载语义意味着可以确保通过 import 相互依赖的 “foo” 和 “bar” 在其中任何一个运行之前,二者都会被加载、解析和编译。
REF
参考链接 1:http://blog.csdn.net/kaelyn_X
参考链接 2:http://es6.ruanyifeng.com/
参考书籍:《UNDERESTANDING ECMASCRIPT 6》&《You Don’t Know JS》