ES6 学习笔记

let 和 const

  1. 循环语句中,每次循环都会创建一个新的代码块作用域

var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
}
}
a[6] (); // 6

以上代码,每次循环都会生成一个新的代码块,使用 let 声明的的变量只属于本次循环的代码块。

  1. for 循环中,设置循环变量的那部分是一个父作用域,循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) { // 这里的 i 属于父作用域,每次循环都会生成一个新的父作用域
let i = 'abc'; // 这里的 i 属于子作用域,每次循环都会生成一个新的子作用域
console.log(i); // abc
}

以上代码,不存在重复声明,因为每次循环的时候,两次声明 let i 在不同的作用域。

  1. let const 不存在变量提升

必须先声明后使用,否则报错 ReferenceError。

  1. 暂时性死区 TDZ

代码块内,只要通过 let 或 const 声明变量或常量,则在 let 和 const 声明之前,该变量或常量都不可以使用,否则报错。

typeof x; // ReferenceError
let x;

typeof undeclared_variable // 'undefined'

function foo(x = y, y = 2) {
return [x, y];
}
foo(); // ReferenceError

函数调用的时候,函数的参数区域形成一个单独的词法作用域,等到参数初始化结束,这个作用域就消失,之后执行函数体的代码。
只要函数参数指定默认值,就看做 let 声明该参数,并且在函数调用的时候声明并初始化该参数。
以上代码相当于在函数调用的时候,执行 let x = y; let y = 2; 由于暂时性死区,let x = y; 会报错。

let x = x; // ReferenceError

  1. let const 不允许在相同的作用域内重复声明同一个变量

var foo = 100;
let foo = 100; // 报错

let bar = 200;
let bar = 200; // 报错

let baz = 300;
var baz = 300; // 报错

const tar = 400;
let tar = 400; // 报错

const jqz = 500;
const jqz = 500; //报错

不能在函数内部重新声明参数

function func(arg) {
let arg; // 报错
}

函数体执行的时候,函数的参数看做和函数体处于同一个作用域

  1. 尽量不要在块级作用域内声明函数,可以使用函数表达式的形式代替

  2. 在 if while 等块语句中用 let const 声明变量、常量,必须有大括号,不能使用单行语句,否则报错

if (true)
let foo = 100; // 报错

while (true)
const bar = 100; // 报错

  1. const 声明常量的时候必须进行初始化,一旦声明则不允许修改删除

const 声明的常量,不允许修改,是指常量指向的内存地址不允许修改,如果常量值是复合类型,则常量指向的数据结构是可以改变的。

  1. ES6 六种声明变量的方法 var function let const import class

var function 命令声明的全局变量仍然是顶层对象的属性。
let const class 声明的全局变量,不属于顶层对象的属性。

解构赋值

解构赋值用于一次性声明多个变量的简写形式,声明的多个变量处于当前的作用域

  1. 解构赋值只能解构成 数组 或 对象

[] = arr;
{} = obj;

通过 let 声明解构

let [foo, bar] = arr;
let {foo, bar} = obj;

通过 var 声明解构

var [foo, bar] = arr;
var {foo, bar} = obj;

通过 const 声明解构

const [foo, bar] = arr;
const {foo, bar} = obj;

如果 foo, bar 没有声明过,默认采用 var 声明解构。如果已经声明过 foo 和 bar,则对变量 foo 和 bar 执行赋值操作

[foo, bar] = arr;
{foo, bar} = obj;

函数参数解构

function func([foo, bar]) {}
func(arr);

function func({foo, bar}) {}
func(obj);

匹配不成功,则变量值为 undefined

var [foo, bar] = [100];
foo; // 100;
bar; // undefined;

var {foo, bar} = {foo: 100};
foo; // 100;
bar; // undefined;

  1. 解构成数组 [foo, bar] = iterator;

只要等号右边的数据结构具有 Iterator 接口,就可以解构成变量数组

let [foo = true] = iterator;

默认值是表达式,表达式惰性求值,只有在使用时 (undefined) 才求值。是否使用默认值,需要使用 === 与 undefined 进行比较。

默认值表达式可以包括其他已经声明的变量。

let [x = 1, y = x] = [];
let [x = y, y = 100] = []; // 报错 ReferenceError
let [x = y, y = 100] = ['hello', 'world']; // 不报错,没有触发默认值的条件,不会进行默认值表达式求值 x = y,因此不报错

  1. 解构成对象 {foo, bar} = obj;

let { foo: f, bar: b} = { foo: ‘hello’, bar: 100 };

以上代码,等号左侧的对象中,foo 和 bar 是模式(属性名),f, b 是变量(属性值)。解构赋值是对变量 f, b 进行赋值。

对象解构赋值的简写形式

let { foo, bar } = { foo: ‘hello’, bar: 100 };
相当于
let { foo: foo, bar: bar } = { foo: ‘hello’, bar: 100 };

同名属性可以为多个变量重复赋值

let { foo: foo, foo: bar } = { foo: 100 }; // foo = 100, bar = 100

如果 obj 是 boolean number string 等基本类型,则转换为基本包装类型。如果 obj 是 undefined 或 null,则报错。

会解构 obj 的所有实例属性和原型属性,不可枚举的属性也会解构。

var pro = {year: 200};
var obj = Object.defineProperty(Object.create(pro), 'language', {
value: 'javascript',
enumerable: false
});
let {year, language} = obj; // 看做是 let year = obj.year; let language = obj.language; 的简写
console.log(year, language); // 200 "javascript"

解构赋值的默认值使用 === 与 undefined 进行判断,默认值表达式进行惰性求值

let {foo: f = 100, bar = 200} = {};

  1. 应用

凡是能够应用默认赋值的地方,都可以用解构赋值,比如 = 赋值,函数的参数赋值,for-in for-of 循环等
for (let [key, value] of iterator.entries()) {}

交换变量
[x, y] = [y, x];

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量
let { log, sin, con } = Math // 看做是 let log = Math.log; let sin = Math.sin; let con = Math.con; 的简写形式

从函数返回多个值
let [a, b, c] = example();

函数参数的默认值
function func({
async = true,
global = true
}) {

}

字符串

  1. '\u{码点}' 码点可以大于 0xFFFF,若码点大于 0xFFFF 仍然视为两个字符

'\u{1F680}' === '\uD83D\uDE80'

  1. 字符串可以用 for of 循环,能够识别码点大于 0xFFFF 的字符串

for (var ch of '?') {
console.log(ch); // '?'
}

以上代码只执行一次循环

  1. 支持码点大于 0xFFFF 字符的 API

charAt() -> at();
charCodeAt() -> codePointAt();
String.fromCharCode() -> String.fromCodePoint();

  1. 模板字符串 ``

可以当做普通字符串,可以当做多行字符串,可以嵌入变量

a b === 'a\nb' === "a\nb" === a\nb

${表达式},表达式可以是变量、常量、字面量、数组元素、对象属性、函数调用、运算符表达式等等

表达式的值如果不是字符串,会进行自动类型转换

${hello} 可以嵌套,内层的 `` 必须在 ${} 之内

  1. 标签模板

funchello

相当于调用函数 func(args, ...values);

args 和 args.raw 都是数组

标签模板可以用于过滤用户输入,网站国际化,自定义模板,内嵌其他语言等作用

函数

  1. 函数调用的时候,函数的参数区域形成一个单独的词法作用域。等到参数初始化结束,这个作用域就消失

函数参数的默认值相当于 let 声明;

function foo(x = 100, y = x) {

}

以上代码相当于 let x = 100, y = x;

function bar(x = x) {

}
bar(); // 报错,let x = x; 暂时性死区

函数参数的默认值表达式惰性求值,函数调用的时候,将参数与 undefined 进行 === 比较

函数参数的默认值和解构变量的默认值是两个概念,下面的函数只有参数的默认值,没有解构变量的默认值

function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

  1. 箭头函数

没有自己的 this, arguments,根据词法作用域引用外层函数的 this, arguments。
箭头函数的 apply call bind 都无法改变 this 的指向
箭头函数不能当做构造函数,不能当做 Generator 函数。

数组

  1. rest 参数 (出现在赋值操作的左侧)

函数定义的时候,参数可以是 rest 参数

function foo(...rest) { // 函数的 rest 参数一定是数组
rest 一定是数组
}
foo([100, 200]);

function foo(...obj) {
obj 是数组 [{language: 'javascript', year: 1995}]
}
foo({language: 'javascript', year: 1995});

解构赋值的时候,等号左边的可以是 rest 参数

let [...arr] = [100, 200]; // arr 是数组
let {...obj} = {language: 'javascript', year: 1995}; // obj 是对象

rest 参数只能是最后一个参数

function foo(...rest, name); // 错误
let [...arr, name] = [1, 2, 3, 'javascript']; // 错误
let {...x, y, z} = {x: 1, y: 2, z: 3}; // 错误

  1. 扩展运算符 ... (出现在赋值操作的右侧)

函数调用的时候,实参可以是扩展运算符

foo(...iterable); // 将具有 Iterator 接口的对象的成员一个一个传入函数
bar(...obj); // 不可以这样,函数调用的时候,只能够扩展具有 Iterator 接口的对象(包括数组)

赋值的时候,等号右边可以是扩展运算符

foo = [...iterable]; // 将具有 Iterator 接口的对象的成员展开成一个数组
bar = {...obj}; // bar 包括 obj 对象的所有可枚举的实例属性

扩展运算符可以不是最后一个参数

foo(...iterator, name);
bar(...obj, name); // 不可以这样
foo = [...iterator, 100];
bar = {...obj, language: 'javascript'};
arr = [...foo, ...bar];
obj = {...foo, ...bar};

扩展空数组或空对象没有任何效果

[...[]]
{...{}}

func(...[]); // 相当于 func()
arr = [...[]]; // 相当于 arr = [];
[foo, bar] = [...[], 100]; // 相当于 [foo, bar] = [100];
obj = {...{}}; // 相当于 obj = {};
{foo, bar} = {...{}, foo: 100}; // 相当于 {foo, bar} = {foo: 100};

  1. Array.from

...iterable 可以扩展具有 Iterator 接口的对象,但是不能扩展没有 Iterator 接口的类数组对象
Array.from(obj); 可以转换具有 Iterator 的对象,也可以转换没有 Iterator 接口的类数组对象
当 obj 为具有 Iterator 接口的对象时候,let arr = Array.from(obj) 与 let arr = [...obj] 作用一样

  1. 字符串包装类对象是具有 Iterator 接口的对象,因此可以用 [...string] 或者 Array.from(string),来生成数组,可以解析 unicode 码位大于 0xFFFF 的值

对象

  1. 属性简洁表示法

{ foo, bar }
{ method() {} }
{ * generator() {} }
{ get foo() {} }
{ set foo() {} }

  1. 属性名表达式

{ [表达式]: value }
{ 表达式 {} }

表达式的值为字符串或 Symbol,如果不是字符串或 Symbol,自动转换为字符串

  1. Object.is()

Object.is(NaN, NaN); // true
Object.is(+0, -0); // false

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

只复制 source 对象自身的可枚举属性,后面对象的属性覆盖前面对象的属性
assign 的本质是使用 = 号进行赋值,所以是浅复制,target.obj = source.obj
对于 getter 属性的复制,只能复制值,因为 target.prop = source.prop,调用 prop 的 getter 函数

let obj = Object.assign({}, source); // 相当于 let obj = {...source};
let obj = Object.assign({}, source1, source2); // 相当于 let obj = {...source1, ...source2};

  1. 其它 API

Object.create();
Object.getPrototypeOf();
Object.setPrototypeOf();
Object.defineProperty();
Object.defineProperties();
Object.getOwnPropertyDescriptor();
Object.getOwnPropertyDescriptors();
Object.keys();
Object.values();
Object.entries();
Object.getOwnPropertyNames();
Object.getOwnPropertySymbols();

  1. 属性的可枚举

以下 6 个操作会忽略 enumerable 为 false 的属性

for...in
Object.keys();
JSON.stringify();
Object.assign();
{x, y, ...z} = obj; // z 只包括 obj 自身的可枚举属性; x 和 y 可以是 obj 的原型属性,也可以是 obj 自身的不可枚举属性
obj = {x, y, ...z}; // obj 只包括 z 对象自身的可枚举属性

  1. 属性的遍历

for...in
Object.keys();
Object.getOwnPropertyNames();
Object.getOwnPropertySymbols();
Reflect.ownKeys();

  1. 应用

复制对象 (包括 get set 属性)

var target = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source)); // 不包括 source 的原型属性

克隆对象

var clone = Object.assign(Object.create(Object.getPrototypeOf(origin)), origin); // 无法克隆 get 和 set 属性,另外 origin 的 enumerable 为 false 的属性也无法克隆
var clone = Object.create(Object.getPrototypeOf(origin), Object.getOwnPropertyDescriptors(origin)); // 完美克隆

Symbol

  1. 基本用法

Symbol 是新的数据类型,和 Boolean Number String Undefined Null Object 一样
Symbol 是基本数据类型,本质上和字符串类似

创建 Symbol 值

let s = Symbol();
let s = Symbol('foo');
let s = Symbol.for('foo');
new Symbol(); // 报错

Symbol('foo'); // 不进行全局登记
Symbol.for('foo'); // 进行全局登记

Symbol('foo') === Symbol('foo'); // false
Symbol.for('foo') === Symbol.for('foo'); // true
Symbol('foo') === Symbol.for('foo'); // false

Symbol 值不能与其它类型进行运算,否则报错

var sym = Symbol('My symbol');
sym.toString(); // Symbol('My symbol')
String(sym); // Symbol('My symbol')
Boolean(sym); // true
Number(sym); // TypeError
sym * 2; // TypeError
sym + 'string'; // TypeError
hello ${sym}; // TypeError

  1. 属性名

Symbol 用作属性名时候只能用 [] 运算符

var sym = Symbol('My symbol');
var a = {};
a[sym] = 'hello';
a[sym] = function () {};

var b = {
[sym]: 'world',
sym {}
};

var c = Object.defineProperty({}, sym, { value: 'javascript' });

  1. 属性名的遍历

Object.getOwnPropertyNames();
Object.getOwnPropertySymbols();
Reflect.ownKeys();

  1. 内部 Symbol 值

Symbol.iterator

如果一个对象具有 Symbol.iterator 属性,且该属性是一个生成器函数,则该对象具有 Iterator 接口,可以进行 for...of 循环和扩展 ... 操作
obj[Symbol.iterator]

  1. 应用

定义常量,消除魔术字符串

Set 和 Map

  1. Set 集合,类似数组,成员唯一,没有重复

创建 Set 对象

var s = new Set();
var s = new Set(iterable);

API

Set.prototype.size
Set.prototype.add(value)
Set.prototype.delete(value)
Set.prototype.has(value)
Set.prototype.clear()

Set.prorotype.keys()
Set.prorotype.values()
Set.prorotype.entries()
Set.prototype.forEach()

Iterator 接口

Set.prototype[Symbol.iterator] === Set.prototype.values

for...of 和 [...set]

应用

删除数组重复元素

var arr = [1, 2, 3, 3, 2, 4, 5, 5];
arr = [...new Set(arr)];
arr = Array.from(new Set(arr));

  1. Map 类型,类似对象,Hash 解构,键值对映射,各种类型的值都可以当作键

创建 Map

var m = new Map();
var m = new Map(iterable); // iterable 对象每个成员必须是一个双元素的数组

var arr = [[1, 2], [true, 'hello'], [function foo() {}, 100]]
var m = new Map(arr);

var s = new Set();
s.add([1, 2]).add([true, 'hello']).add([function foo() {}, 100]);
var m = new Map(s);

var m = new Map(arr);
var n = new Map(m);

API

Map.prototype.size
Map.prototype.set(key, value)
Map.prototype.get(key)
Map.prototype.has(key)
Map.prototype.delete(key)
Map.prototype.clear()

Map.prototype.keys()
Map.prototype.values()
Map.prototype.entries()
Map.prototype.foreach()

Iterator 接口

Map.prototype[Symbol.iterator] === Map.prototype.entries

for...of 和 [...map]

注意

map.set() 对同一个键多次赋值,后面的值覆盖前面的值
map.get() 读取未知的键,值为 undefined
如果 Map 的键是简单类型,只要值相同,则认为是同一个键
如果 Map 的键是复杂类型,必须指向同一个对象,才认为是同一个键

  1. WeakSet 和 WeakMap

WeakSet 成员必须都是对象,WeakMap 成员的键必须都是对象
WeakSet 成员是该对象的弱引用,不计入垃圾回收机制的引用计数
WeakMap 成员的键是该对象的弱引用,不计入垃圾回收机制的引用计数
WeakMap 成员的值指向的对象是强引用
当 WeakSet 成员指向的对象消失,WeakSet 里面的引用就会自动消失
当 WeakMap 成员键指向的对象消失,WeakMap 成员的键里面的引用就会自动消失,该成员也会自动消失
WeakSet 和 WeakMap 没有 size 属性,不能遍历
WeakSet 和 WeakMap 常用来保存 DOM 元素,当 DOM 元素消失,则 WeakSet 和 WeakMap 相关的成员也会自动消失

Proxy 和 Reflect

Proxy 用于代理某个对象的默认操作
Reflect 用于规定所有对象的默认操作

  1. Proxy 对象

创建 proxy 对象
var proxy = new Proxy(target, handler);
var { proxy, revoke } = Proxy.revocable(target, handler);

proxy 对象代理了 target 对象,任何对 proxy 对象的操作,实际上都是在操作 target 对象
handler 对象可以拦截 proxy 对象的默认操作,但是不会拦截直接对 target 对象的所有操作
如果没有 handler 对象,或者有 handler 对象,但是没有设置相应的拦截操作,则对 proxy 对象的操作则直接落在 target 对象上,相当于直接操作 target 对象
Proxy.revocable 方法返回一个对象,其 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例对 target 对象的代理
Proxy.revocable 的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就调用 revoke(); 收回代理,不允许再次访问 proxy.foo // TypeError

var person = {
name: '张三'
};

var handler = {
get(target, name) {
console.log('get');
return target[name];
return Reflect.get(target, name);
}
};

var proxy = new Proxy(person, handler);
console.log(proxy.name); // get \n '张三'
proxy.name = '李四';
console.log(proxy.name); // get \n '李四'

以上代码拦截了对 proxy 对象的 get 操作,修改了 get 操作的默认行为,但是没有拦截 set 操作,因此 set 操作直接落在 target 对象上

当使用 proxy 代理 target 对象时,target 对象内部的 this 会指向 proxy 对象

var target = {
m: function () {
console.log(this === proxy);
}
};

var handler = {};

var proxy = new Proxy(target, handler);
target.m(); // false
proxy.m(); // true

拦截操作

get(target, name, receiver)
set(target, name, value, receiver)
has(target, name)
deleteProperty(target, name)
construct(target, args)
apply(target, context, args)
getPrototypeOf(target)
setPrototypeOf(target, proto)
defineProperty(target, name, attributes)
getOwnPropertyDescriptor(target, name)
isExetensible(target)
preventExtensions(target)
ownKeys(target)

  1. Reflect 对象

将 Object 对象上的一些明显属于语言内部的方法(比如 Object.defineProperty)放到 Reflect 对象上
修改某些 Object 方法的返回结果,让其变得更合理
让某些命令式的 Object 操作都变成函数行为,比如 name in obj 和 delete obj[name]
Reflect 对象的方法与 Proxy 对象的方法一一对应,使 Proxy 对象的拦截器不管怎么修改默认行为,都可以方便地调用对应的 Reflect 方法来完成默认行为

API

Reflect.get(target, name, receiver) // 如果 name 是 get 取值函数,则 receiver 是取值函数内部的 this 值
Reflect.set(target, name, value, receiver) // 如果 name 是 set 存值函数,则 receiver 是存值函数内部的 this 值
Reflect.has(target, name)
Reflect.deleteProperty(target, name)
Reflect.construct(target, args)
Reflect.apply(target, context, args)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, proto)
Reflect.defineProperty(target, name, attributes)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.isExetensible(target)
Reflect.preventExtensions(target)
Reflect.ownKeys(target)

  1. 应用

使用 Proxy 和 Reflect 实现观察者模式

const queuedObservers = new Set(); // 观察者函数存放在该集合

const observe = fn => queuedObservers.add(fn); // 添加观察者函数
const observable = obj => new Proxy(obj, {set}); // 代理原始数据对象,拦截代理对象的 set 行为

// 拦截代理对象的 set 行为
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver); // 执行数据对象的默认 set 行为
queuedObservers.forEach(observer => observer()); // 调用所有观察者函数
return result;
}

凡是在读取或写入对象的属性的同时,需要进行额外的操作(Ajax 请求、类型检验、写入日志...),就可以使用 proxy 对象

Promise

优点:

将异步操作流程用同步方式表达
统一异步操作接口

缺点:

无法取消 Promise,一旦新建立即执行
Promise 内部抛出的错误不会反映到外部
当处于 Pending 状态时,无法得知进展到哪一个阶段

状态:

Pending Fulfilled(Resolved) Rejected

一旦状态改变就不会再变,只能是由 Pending -> Fulfilled 或者 Pending -> Rejected
就算先改变 Promise 对象的状态,然后添加回调函数,该回调函数仍然会执行,与事件机制 (Event) 不同

  1. 基本用法:

var promise = new Promise(function (resolve, reject) {
// ... some code

if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
});
promise.then(function success(value) {}, function error(error) {});

用 Promise 封装异步操作,在参数函数体内执行异步操作,在异步操作的回调函数中执行 resolve() 或 reject() 改变 Promise 的状态。
参数函数体内本身的代码是同步执行的,先同步执行参数函数,再返回 promise 对象
then 中的回调函数会在本轮 EventsLoop (同一段 <script></script>) 结束之后,下轮 EventsLoop (所有 <script></script> 都执行之后) 执行之前执行
一般 then 中的回调函数会在 resolve 或 reject 函数执行时(也就是 promise 对象的状态改变时)所在的 EventsLoop 末尾执行

如果 resolve 或者 reject 函数带有参数,该参数会传递给 then 中的回调函数
resolve 的参数如果是另一个 promise 对象,则当前 promise 对象的状态与另一个 promise 对象的状态绑定

var p1 = new Promise((resolve, reject) => {
// ...
});

var p2 = new Promise((resolve, reject) => {
// ...
resolve(p1);
});

p2 的状态与 p1 的状态绑定,如果 p1 的状态是 Pendding,则 p2 的状态也是 Pendding,等待 p1 的状态发生变化
p1 的状态变为 Resolved,则 p2 的状态立即变为 Resolved,p1 的状态变为 Rejected,则 p2 的状态立即变为 Rejected

虽然调用 resolve 或 reject 函数不会结束 Promise 参数函数的执行,但是一般来说,调用 resolve 或 reject 以后,Promise 的使命就完成了,后继操作应该放到 then 方法里面
then 方法的回调函数会在调用 resolve 或 reject 函数所在的 EventsLoop 末尾执行

  1. Promise.prototype.then(function success() {}, function error() {}) 和 Promise.prototype.catch(function error() {})

then 和 catch 返回一个新的 Promise 实例
then 和 catch 方法的回调函数中(不管是 Resolved 回调函数,还是 Rejected 回调函数),return value; 则 then 和 catch 方法返回的 Promise 实例变为 Resolved,相当于调用 resolve(value);
then 和 catch 方法的回调函数中(不管是 Resolved 回调函数,还是 Rejected 回调函数),throw error; 则 then 和 catch 方法返回的 Promise 实例变为 Rejected,相当于调用 reject(error);
then 和 catch 方法的回调函数中(不管是 Resolved 回调函数,还是 Rejected 回调函数),return anOtherPromise; 则 then 和 catch 方法返回的 Promise 实例与 anOtherPromise 的状态绑定

Promise.reject().then(() => {});
如果 promise 对象变为 Rejected,并且 then 中没有指定 Rejected 回调函数,则 then 返回的 Promise 实例的状态也变为 Rejected
Promise.resolve().catch(() => {});
如果 promise 对象变为 Resolved,由于 catch 中无法指定 Resolved 回调函数,所以 catch 返回的 Promise 实例的状态也变为 Resolved

Promise.reject().then(() => {}, () => {});
如果 promise 对象变为 Rejected,并且 then 指定了 Rejected 回调函数,则根据 Rejected 回调函数来决定 then 返回的 Promise 实例的状态
Promise.reject().then(() => {}, () => true);
如果 Rejected 回调函数有返回值(默认返回 undefined),则 then 返回的 Promise 实例的状态变为 Resolved
Promise.reject().then(() => {}, () => {throw error});
如果 Rejected 回调函数抛出错误,则 then 返回的 Promise 实例的状态变为 Rejected
Promise.reject().then(() => {}, () => anOtherPromise);
如果 Rejected 回调函数有返回值,且返回了另一个 Promise 对象,则 then 返回的 Promise 实例的状态与 anOtherPromise 的状态绑定

  1. throw

在 Promise 参数函数中同步执行时抛出的错误,或者在 then 或 catch 方法中的回调函数(不管是 Resolved 回调函数还是 Rejected 回调函数)中同步执行时抛出的错误,当错误没有被捕获时,不会传递到外层代码
在 Promise 参数函数中异步执行时抛出的错误,或者在 then 或 catch 方法中的回调函数(不管是 Resolved 回调函数还是 Rejected 回调函数)中异步执行时抛出的错误,不管是否有 catch 捕获,该错误都不会被捕获,并且会传递到外层代码

var promise = new Promise(function (resolve, reject) {
// 同步执行时抛出的错误,相当于调用 reject 函数,该 promise 对象的状态立即变为 Rejected
throw error;
});
// promise 对象没有指定捕获错误的函数(then 中的 Rejected 回调函数或 catch 方法),该错误不会传递到外层代码,但是浏览器环境会提示错误信息,服务器中退出码是0(表示执行成功)
promise.then(value => value);

var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
// 异步执行时抛出的错误,错误会传递到外层代码,成为未捕获的错误
throw error;
// 此时 promise 对象的状态为 Pending
}, 1000);
});
// promise 对象指定了捕获错误的 catch 方法,但是异步执行时抛出的错误不会被 catch 方法捕获,错误会传递到外层代码
promise.catch(error => error);

var promise = Promise.resolve();
var p1 = promise.then(() => {
console.log('p1 then');
// 同步执行时抛出的错误,该错误不会传递到外层代码
throw 'error';
// 此时 p1 的状态是 Rejected
});

var p2 = promise.then(() => {
console.log('p2 then');
setTimeout(function () {
// 异步执行时抛出的错误,该错误不会被捕获,会传递到外层代码,成为未捕获的错误
throw 'error';
// 此时 p2 的状态是 Resolved,因为 then 中的 Resolved 回调函数没有返回值,则默认 return undefined,所以 p2 的状态在 then 方法的 Resolved 回调函数调用结束之后就变为 Resolved 了
}, 1000);
});

  1. API

Promise.all(iterable); // iterable 的每个成员必须是 promise 对象
Promise.race(iterable); // iterable 的每个成员必须是 promise 对象
Promise.resolve();
Promise.reject();

  1. 应用

function wrapper() {
// ... 异步操作的包装函数的代码

// 返回一个 Promise 对象,来统一不同异步操作的接口
return new Promise(function (resolve, reject) {
// 真正的异步操作封装在参数函数中

// 开启一个线程来进行异步操作
var obj = doAsync();

// 异步操作成功的回调函数
obj.onsuccess = function (value) {
  // 该回调函数会在下一轮或者下几轮 EventsLoop 执行,具体要看消息队列里排队的消息有多少
  resolve(value);
};

// 异步操作失败的回调函数
obj.onerror = function (error) {
  // 该回调函数会在下一轮或者下几轮 EventsLoop 执行,具体要看消息队列里排队的消息有多少
  reject(error);
};

});
}

var option = wrapper();
option.then(value => { //... }).catch(error => { //... });
//这里的 then 或 catch 中的回调函数一定是在 resolve 或 reject 函数调用的那轮 EventsLoop 的末尾执行的

wrapper 函数中的错误属于同步错误,可以被外层 try..catch 捕获

function wrapper() {
throw 'error';
// ...
}

try {
var option = wrapper();
options.then(value => { //... }).catch(error => { //... }); // 该行代码不会执行
} catch (error) {
// 可以捕获 wrapper 函数中的同步错误
}

参数函数中的错误有两种情况,一种是在参数函数中同步执行时抛出的错误,一种是在参数函数中异步执行时抛出的错误,但是这两种错误广义上都属于异步错误

参数函数中同步执行时抛出的错误,不会传递到外层的 try...catch,如果指定了 promise 对象的 catch 方法,会被 catch 方法捕获,如果没有指定 catch 方法,也不会传递到外层

try {
var option = wrapper();
option.then(value => { //... }).catch(error => { // 可以捕获到参数函数中同步执行时抛出的错误 });
} catch (error) {
// 无法捕获到参数函数中同步执行时抛出的错误
}

参数函数中异步执行时抛出的错误

function wrapper() {
// ... 异步操作的包装函数的代码

// 返回一个 Promise 对象,来统一不同异步操作的接口
return new Promise(function (resolve, reject) {
// 真正的异步操作封装在参数函数中

// 开启一个线程来进行异步操作
var obj = doAsync();

// 异步操作成功的回调函数
obj.onsuccess = function (value) {
  EventsLoop2:
  try {
    // 参数函数中异步执行时抛出的错误
    // 当前代码所处的 EventsLoop (也就是 EventsLoop2 标签所处的 EventsLoop) 已经不是参数函数执行时所处的 EventsLoop 了 (也就是 EventsLoop1 标签所处的 EventsLoop),此时 EventsLoop1 中的所有代码早就执行完毕了
    // 因此这里抛出的错误不能够被 EventsLoop1 中的 try...catch 捕获,只能被 EventsLoop2 中的 try...catch 捕获
    throw 'error';
    resolve(value);
  } catch (error) {
    // 可以捕获到参数函数中异步执行时抛出的错误
  }
};

// 异步操作失败的回调函数
obj.onerror = function (error) {
  reject(error);
};

});
}

EventsLoop1:
try {
var option = wrapper();
option.then(value => { //... }).catch(error => { // 可以捕获到参数函数中同步执行时抛出的错误 });
} catch (error) {
// 无法捕获到参数函数中同步执行时抛出的错误,也无法捕获到参数函数中异步执行时抛出的错误,这两种错误广义上都属于异步错误
// 总之只能捕获同步错误,无法捕获异步错误
}

Iterator 和 for...of 循环

目的

为所有数据结构 (Array, Object, Set, Map) 提供统一的遍历接口

概念

生成器函数 Symbol.iterator{},遍历器对象 iterator,可遍历对象 iterable

如果一个对象有 [Symbol.iterator] 属性 (实例属性或原型属性都可以),则该对象是 iterable 对象
[Symbol.iterator] 属性是一个生成器函数,调用该函数会生成一个 iterator 对象
iterator 对象有 next 方法,每次调用 next 方法来移动指针并返回数据,每次调用 next 返回 { value: data, done: boolean }

自定义遍历器接口

let iterable = {
Symbol.iterator {
// 这里可以定义 next 闭包需要的状态变量

// 遍历器对象的 next 方法
function next() {
  // 移动指针,判断是否遍历结束,返回数据
  return {
    value: value,
    done: boolean
  }
}

let iterator = { next };
// 返回遍历器对象
return iterator;

}
};

iterable 对象可以遍历
for(let value of iterable) {...}
[...iterable]

自定义类数组对象的遍历器接口有一种简便的方法

let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};

利用 Generator 函数自定义遍历器接口

let iterable = {

原生遍历器接口

有些原生数据结构部署了 [Symbol.iterator] 属性(该属性是生成器函数),因此这些数据结构的对象是 iterable 对象
原生具备 Iterator 接口的数据结构如下:

  • Array
  • Set
  • Map
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

计算生成的数据结构

Array Set Map 都有 keys values entries 三个方法,这三个方法是生成器函数,返回遍历器对象

应用

let [foo, bar] = iterable; // 解构赋值
let arr = [...iterable]; // 扩展运算符
func(...iterable); // 扩展运算符
yield* iterable;
for (let value of iterable) {}
Array.from(iterable);
TypedArray.from(iterable);
new Set(iterable);
new Map(iterable);
new WeakSet(iterable);
new WeakMap(iterable);
Promise.all(iterable);
Promise.race(iterable);

凡是可以使用 iterable 对象的地方,都可以使用 iterator 对象

for (let value of iterable) {

}
// 以上代码本质上是调用如下代码
for (let value of iterableSymbol.iterator) {

}
// for...of 循环的本质是调用 while 循环
let iterator = iterableSymbol.iterator;
let result = iterator.next();
while(!result.done) {
let value = result.value;
// do with value
result = iterator.next();
}

几种遍历方法的比较

for (let i = 0; i < arr.length; i++) {} // 古老的方法
arr.forEach((item, index) => {}); // 无法中途退出
for (let pro in obj) {} // 适合遍历对象的可枚举属性,包括原型属性,不适合遍历数组等 iterable 对象
for (let value of iterable) {} // 最好的遍历方法,可以 break, continue

Generator 函数

简介

Generator 函数是一个生成器函数,调用 Generator 函数的时候,该函数并不执行,而是返回一个 iterator 对象,通过调用 iterator.next() 方法来分段执行 Generator 函数
第一次调用 iterator.next(),Generator 函数才开始执行,直到遇到第一条 yield 语句,函数便暂停执行
再次调用 iterator.next(),Generator 函数会从上次暂停的地方继续执行,直到遇到下一条 yield 语句 (或 return 语句)
iterator.next() 方法返回一个对象,该对象有 value 和 done 两个属性,value 属性是 yield 后面的表达式的值 (或 return 的值),done 是一个布尔值,表示遍历是否结束

function* gen() {
yield 'hello';
yield 'world';
return 'ending'; // 如果函数最后没有 return 语句,则默认为 return undefined。gen() 生成的 iterator 被遍历时,不包括 return 的值
}

let iterator = gen(); // 生成一个 iterator 对象
let { value, done } = iterator.next(); // 从头开始执行 gen 函数,遇到 yield 'hello' 则停止执行,此时 value 为 'hello', done 为 false
let { value, done } = iterator.next(); // 从上次暂停处继续执行 gen 函数,遇到 yield 'world' 则停止执行,此时 value 为 'world', done 为 false
let { value, done } = iterator.next(); // 从上次暂停处继续执行 gen 函数,遇到 return 'ending' 则停止执行,此时 value 为 'ending', done 为 true
let { value, done } = iterator.next(); // gen 函数已经执行完毕了,不会再执行了,此时 value 为 undefined, done 为 true
let { value, done } = iterator.next(); // 以后再调用 iterator.next(),总是会返回 { value: undefined, done: true }

yield 表达式只能用在 Generator 函数中,用在其他函数中会报错

function* gen() {
var arr = [];
arr.forEach(item => {
yield item; // 报错,yield 用在了 item => {} 函数中,该函数不是 Generator 函数
});
}

yield 表达式用在赋值表达式的右边或者用作函数参数时不需要加括号,用在其他表达式中,则必须加小括号,否则报错

let foo = yield 'hello';
bar(yield 'world', 100);
'hello' + (yield 'world');

通过调用 Generator 函数生成的 iterator 对象可以看作是该 Generator 类的实例

function* gen() {

}
let iterator = gen();
console.log(Object.getPrototypeOf(iterator) === gen.prototype); // true

API

Generator.prototype.next()

/* iterator.next() */

默认 yield 表达式的值为 undefined,通过 iterator.next(value) 可以赋予上一条 yield 表达式的值为 value

function* gen() {
let foo = yield 'hello'; // 第一次调用 iterator.next() 时,执行到 yield 'hello' 就停止,此时 let 赋值语句没有完成,下一次调用 iterator.next() 时才完成 let foo 的赋值
console.log(foo); // undefined
let bar = yield 'world';
console.log(bar); // 100
}
let iterator = gen();
iterator.next(100); // 第一次调用 iterator.next() 时参数会被忽略,因为没有上一条 yield 表达式
iterator.next(); // yield 'hello' 表达式的值为 undefined
iterator.next(100); // yield 'world' 表达式的值为 100

Generator.prototype.throw()

/* iterator.throw() */

  1. gen 函数中 throw 一个错误,如果没有捕获到该错误,则整个脚本停止执行

function* gen() {
yield 'hello';
throw 100;
yield 'world'; // 这条语句不会执行
}
let iterator = gen();
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.next(); // 从上次暂停的地方继续执行,遇见 throw 100,则结束 gen 函数的执行,并且抛出一个错误,由于 gen 函数内部和外部都没有任何 catch 捕获错误,所以整个脚本停止执行
iterator.next(); // 这条语句不会执行

  1. iterator.throw(); 的作用相当于在上次暂停的地方开始继续执行,然后立即 throw 一个错误

function* gen() {
yield 'hello';
yield 'world'; // 这条语句不会执行
}
let iterator = gen();
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.throw(100); // 从上次暂停的地方继续执行,然后 throw 100,结束 gen 函数的执行,并且抛出一个错误,由于 gen 函数内部和外部都没有任何 catch 捕获错误,所以整个脚本停止执行
iterator.next(); // 这条语句不会执行

  1. gen 函数中 throw 的错误,可以在 gen 函数体内捕获

function* gen() {
try {
yield 'hello';
throw 100;
yield 'world'; // 这条语句不会执行
// 以上三条语句很可能处于不同的 EventsLoop,但是抛出的错误都可以被同一个词法作用域的 catch 捕获到
} catch (error) {
console.log('内部捕获到' + error);
}
yield 'javascript';
}
let iterator = gen();
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.next(); // 从上次暂停的地方继续执行,遇见 throw 100,立即跳转到 gen 函数体内的 catch 语句,然后继续执行,直到遇见下一条 yield 语句或 return 语句,返回 { value: 'javascript', done: false }
iterator.next(); // 返回 { value: undefined; done: true }

  1. iterator.throw() 抛出的错误,可以在 gen 函数体内捕获

function* gen() {
try {
yield 'hello';
yield 'world'; // 这条语句不会执行
// 以上两条语句很可能处于不同的 EventsLoop,但是抛出的错误都可以被同一个词法作用域的 catch 捕获到
} catch (error) {
console.log('内部捕获到' + error);
}
yield 'javascript';
}
let iterator = gen();
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.throw(100); // 从上次暂停的地方继续执行,然后 throw 100,立即跳转到 gen 函数体内的 catch 语句,然后继续执行,直到遇见下一条 yield 语句或 return 语句,返回 { value: 'javascript', done: false }
iterator.next(); // 返回 { value: undefined; done: true }

  1. gen 函数中 throw 的错误,可以在 gen 函数体外捕获

function* gen() {
yield 'hello';
throw 100;
yield 'world'; // 这条语句不会执行
}
let iterator = gen();
try {
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.next(); // 从上次暂停的地方继续执行,然后 throw 100,由于 gen 函数体内没有 catch 语句,因此立即结束 gen 函数的调用,并且跳转到外部执行 iterator.next() 所在 EventsLoop 的 catch 语句
iterator.next(); // 这条语句不会执行
} catch (error) {
console.log('外部捕获到' + error);
iterator.next(); // 返回 { value: undefined, done: true }
}

  1. iterator.throw() 抛出的错误,可以在 gen 函数体外捕获

function* gen() {
yield 'hello';
yield 'world'; // 这条语句不会执行
}
let iterator = gen();
try {
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.throw(100); // 从上次暂停的地方继续执行,然后 throw 100,由于 gen 函数体内没有 catch 语句,因此立即结束 gen 函数的调用,并且跳转到外部执行 iterator.throw() 所在 EventsLoop 的 catch 语句
iterator.next(); // 这条语句不会执行
} catch (error) {
console.log('外部捕获到' + error);
iterator.next(); // 返回 { value: undefined, done: true }
}

  1. 同时有内部 try...catch 和 外部 try...catch,内部优先捕获

Generator.prototype.return()

/* iterator.return() */

调用 iterator.return(),相当于立即 return

function* gen() {
yield 'hello';
yield 'world'; // 这条语句不会执行
}
let iterator = gen();
iterator.next(); // 遇见 yield 'hello' 停止执行
iterator.return('ending'); // 从上次暂停的地方继续执行,然后 return 'ending',结束 gen 函数的调用,返回 { value: 'ending', done: true }

yield*

yield* iterable;

let foo = yield* gen(); // gen 函数如果有 return,则 return 的值为 yield* 表达式的值,否则 yield* 表达式的值为 undefined

Generator 函数 this

function* gen() {
console.log(this);
}

let iterator = gen(); // 这时还没有执行 gen 函数
iterator.next(); // 开始执行 gen 函数,this 指向全局对象

let obj = {};
let iterator = gen.call(obj); // 这时还没有执行 gen 函数
iterator.next(); // 开始执行 gen 函数,this 指向 obj 对象

let obj = { gen };
let iterator = obj.gen(); // 这时还没有执行 gen 函数
iterator.next(); // 开始执行 gen 函数,this 指向 obj 对象

let iterator = new gen(); // 报错,不能跟 new 命令一起使用

应用

Generator 函数可以看作一个类似数组的数据结构,数据结构的成员就是 Generator 函数内部 yield 命令生成的,该数据结构作为 iterator 可以被遍历

Generator 函数返回 iterator 对象,可以应用 iterable 对象的场景,都可以使用 iterator 对象

let [foo, bar] = gen(); // 解构赋值
let arr = [...gen()]; // 扩展运算符
func(...gen()); // 扩展运算符
yield* gen();
for (let value of gen()) {}
Array.from(gen());
TypedArray.from(gen());
new Set(gen());
new Map(gen());
new WeakSet(gen());
new WeakMap(gen());
Promise.all(gen());
Promise.race(gen());

Generator 函数可以作为对象的 [Symbol.iterator] 属性,使得该对象具有 Iterator 接口

let iterable = {

  • Symbol.iterator {
    yield 'hello';
    yield 'world';
    }
    };
    let arr = [...iterable];

Genrator 函数可以封装一个对象,使得该对象具有 Iterator 接口

function* gen(obj) {
for (let pro in obj) {
yield [pro, obj[pro]];
}
}
let obj = {
name: 'javascript',
year: 1995
};
for (let [key, value] of gen(obj)) {
console.log(${key}: ${value});
}

Generator 函数可以用来进行异步编程

Generator 函数异步编程

  1. 简介

function* gen() {
let f1 = yield asyncTask1();
let f2 = yield asyncTask2();
}
run(gen); // run 是流程管理函数,可以自定义,也可以使用别人开发好的

Generator 函数可以暂停执行和恢复执行,函数体内外可以交换数据、交互处理错误 (函数体内的错误可以在函数体内或体外捕获,函数体外可以 iterator.throw 抛出一个函数体内的错误),因此可以用于异步编程
Generator 函数可以封装异步任务,异步任务的不同阶段通过 yield 来标记,通过 iterator.next 方法分段执行异步任务
Generator 函数需要传递到流程管理函数,调用流程管理函数,则开始自动执行 Generator 函数
Generator 函数自动执行的时候,遇到 yield 就暂停执行,然后继续执行当前 EventsLoop 剩余的同步代码,等到 yield 后面的异步操作返回结果时继续执行 Generator 函数
Generator 函数在分段执行的时候,不同段的代码可能处于不同的 EventsLoop,iterator.next() 在哪个 EventsLoop,那么该段代码就在哪个 EventsLoop
Generator 函数返回一个 iterator 对象,不管 Generator 函数 return 什么,该函数总是返回 iterator 对象,而且是先返回一个 iterator 对象,然后通过 iterator.next 才开始执行第一段函数,直到遇见 yield 或函数结束
yield 后面的异步操作返回结果时,可以在异步操作的回调函数中调用 iterator.next 来继续执行 Generator 函数,也可以在异步操作返回的 promise 对象的 then 方法的回调函数中调用 iterator.next 来继续执行 Generator 函数,前者可以通过使用 Thunkify 模块来实现,后者可以通过使用 co 模块来实现

  1. Thunkify 模块

Thunk 函数:将多参数的函数替换成一个只接受回调函数作为参数的单参数函数

$ npm install thunkify

let fs = require('fs');
let thunkify = require('thunkify');
let readFileThunk = thunkify(fs.readFile);

// gen 函数封装异步任务
function* gen() {
let f1 = yield readFileThunk('/etc/fstab'); // 返回一个 Thunk 函数给外部,这时还没有开始执行异步操作,等外部调用返回的 Thunk 函数时,才开始执行异步操作
let f2 = yield readFileThunk('/etc/shells');
};

// 自定义流程管理函数,在异步操作的回调函数中调用 iterator.next,来恢复 Generator 函数的执行
function run(gen) {
let iterator = gen();
function next(err, data) {
if (err) {
// 异步操作失败,处理异步错误
doWithError(err);
} else {
// 异步操作成功,继续执行 gen 函数,并将异步操作返回的结果传递进 gen 函数
let result = iterator.next(data);
if (result.done) {
// 如果 gen 函数执行完毕,则意味着异步任务已经执行完毕,并且已经处理完异步操作的返回结果
return result.value;
} else {
// 如果 gen 函数没有执行完毕,则继续执行 gen 函数
// result.value 是内部返回的 Thunk 函数,执行该函数,则开始执行异步操作,并且指定异步操作的回调函数为 next 函数,一般异步操作的回调函数都会包括参数 (err, data)
result.value(next);
}
}
}
next();
}
run(gen);

3 co 模块

推荐使用 co 模块,不推荐使用 Thunkify 模块,因为使用 Thunkify 模块需要自己定义流程管理器

3.1 不使用 co 手动进行异步流程管理,在 promise 的 then 方法的回调函数中调用 iterator.next,来恢复 Generator 函数的执行

function* gen() {
let f1 = yield promise1; // 遇见 yield,停止执行 gen 函数,然后继续执行 gen 函数外面的本轮 EventsLoop 剩下的代码。当 promise 对象状态变为 Resolved 的时候,继续执行 gen 函数
let f2 = yield promise2;
}

function run(gen) {
let iterator = gen();
function next(err, data) {
if (err) {
// 异步操作失败,处理异步错误
doWithError(err);
} else {
// 异步操作成功,继续执行 gen 函数,并将异步操作返回的结果传递进 gen 函数
let result = iterator.next(data);
if (result.done) {
// 如果 gen 函数执行完毕,则意味着异步任务已经执行完毕,并且已经处理完异步操作的返回结果
return result.value;
} else {
// 如果 gen 函数没有执行完毕,则继续执行 gen 函数
// result.value 是内部返回的 promise 对象
result.value.then(function (data) {
// 如果异步操作成功了,会执行 then 的回调函数,在回调函数中调用 iterator.next(),从而可以继续执行 gen 函数
next(null, data);
}).catch(function (err) {
// 如果异步操作失败了,会执行 catch 的回调函数
next(err);
});
}
}
}
next();
}
run(gen);

3.2 使用 co 自动进行异步流程管理

function* gen() {
let f1 = yield promise1; // 遇见 yield,停止执行 gen 函数,然后继续执行 gen 函数外面的本轮 EventsLoop 剩下的代码。当 promise 对象状态变为 Resolved 的时候,继续执行 gen 函数
let f2 = yield promise2;
}

let co = require('co');
let promise = co(gen);

3.2.1 co 模块的简介

co 函数返回一个 promise 对象
当 gen 函数 return (遇到函数结束标记,默认 return undefined),该 co 函数返回的 promise 对象状态变为 Resolved,return 的值作为 resolve 回调函数的参数
当 gen 函数 return 另一个 promise 对象 anOtherPromise,则 promise 对象的状态与 anOtherPromise 对象的状态绑定
当 gen 函数 throw 抛出一个错误,则进行错误处理机制 (见下面的错误处理)
当 gen 函数 yield 后面的 value 不是 promise 对象,则相当于 throw 抛出一个错误
当 gen 函数 yield 后面的 promise 对象的状态变为 Rejected 时,相当于 throw 抛出了一个错误,导致 promise 对象状态变为 Rejected 的 reject() 函数的参数作为 throw 后面的参数
当 gen 函数 yield 后面的 promise 对象的状态变为 Resolved 时,恢复 gen 函数的执行,yield 表达式的值为导致 promise 对象状态变为 Resolved 的 resolve() 函数的参数

3.2.2 co 模块的错误处理

(1) 如果 gen 函数内部有 try..catch,则内部可以捕获 throw 抛出的错误

function* gen() {
try {
yield promise1;
throw '出错了!';
// 或者 yield notAPromiseValue;
// 或者 yield Promise.reject();
yield promise2; // 该行不会执行
// 以上几段代码可能处于不同的 EventsLoop,但是不同代码段抛出的错误可以被同一个词法作用域的 catch 捕获到,因为这里的 try...catch 并没有执行结束,该 try...catch 分段执行,分散在好几个 EventsLoop 里
// 只要在一个代码段执行的时候抛出错误,则立即跳转到词法作用域的 catch 语句,不会再执行 try 语句中的剩余代码段了
} catch (error) {
// 捕获到 gen 函数内部抛出的错误,然后继续执行后面的语句,直到遇到 yield 或 return
console.log('内部捕获:' + error);
}
return yield promise3;
}
let promise = co(gen); // promise 的状态变为 Resolved,gen 函数的 return 值为 promise 的 then 方法的回调函数的参数
promise.then(value => value);

(2) 如果 gen 函数内部没有 try...catch,则 co 函数返回的 promise 对象状态变为 Rejected,throw 的值作为 promise 的 catch 方法的回调函数的参数

let promise = co(gen);
promise.catch(error => error);

(3) 如果 gen 函数内部没有 try...catch,并且 co 函数返回的 promise 对象没有指定 catch 方法,则抛出的错误不会传递到外层代码

let promise = co(gen);
promise.then(value => value); // promise 对象的状态变为 Rejected

3.2.3 co 处理并发的异步操作

gen 函数中可以 yield [promise1, promise2],作用相当于 yield Promise.all[promise1, promise2];
等 promise1 和 promise2 全都变为 Resolved 的时候,才恢复执行 gen 函数,yield 表达式的返回值为 [value1, value2],
等 promise1 或 promise2 有一个变为 Rejected 的时候,相当于 throw 一个错误

async 函数

async function func() {
var f1 = await promise1;
var f2 = await promise2;
}
let promise = func();

  1. 简介

async 函数就是 Generator 函数的语法糖

async 函数的执行和普通函数一样,调用函数后自动执行
async 函数执行的时候,遇到 await 就暂停执行,然后继续执行当前 EventsLoop 剩余的同步代码,等到 await 后面的 promise 对象的状态变为 Resolved 时继续执行 async 函数,函数内的不同段的代码在不同的 EventsLoop 执行
async 函数返回一个 promise 对象,不管 async 函数 return 什么,该函数总是返回 promise 对象
调用 async 函数时,先在当前的 EventsLoop 中同步执行第一段代码,直到遇见 await 或 return 时暂停执行,然后返回一个 promise 对象,然后执行当前 EventsLoop 剩余的同步代码

当 async 函数 return (遇到函数结束标记,默认 return undefined),该 async 函数返回的 promise 对象状态变为 Resolved,return 的值作为 resolve 回调函数的参数
当 async 函数 return 另一个 promise 对象 anOtherPromise,则 promise 对象的状态与 anOtherPromise 对象的状态绑定
当 async 函数 throw 抛出一个错误,则进行错误处理机制 (见下面的错误处理)
当 async 函数 await 后面的 promise 对象的状态变为 Rejected 时,相当于 throw 抛出了一个错误,导致 promise 对象状态变为 Rejected 的 reject() 函数的参数作为 throw 后面的参数
当 async 函数 await 后面的 promise 对象的状态变为 Resolved 时,恢复 async 函数的执行,await 表达式的值为导致 promise 对象状态变为 Resolved 的 resolve() 函数的参数
当 async 函数 await 后面的 value 不是 promise 对象,则相当于 await Promise.resolve(value);

async function func() {
console.log('async start');
await 100;
console.log('async hello');
await 200;
console.log('async world');
await 300;
console.log('async stop');
}
console.log('---');
func();
console.log('---');


async start

async hello
async world
async stop

以上代码中,await 后面是基本类型的值,相当于调用 Promise.resolve(value) 来转换为状态为 Resolved 的 promise 对象
当 await 后面的 promise 对象的状态变为 Resolved 时,async 函数会继续执行,本质上是因为在 promise 对象状态变为 Resolved 之后执行的 then 的回调函数中,调用了 iterator.next
由于 then 的回调函数会在 promise 对象状态变为 Resolved 时的那轮 EventsLoop (在以上代码中是当前轮的 EventsLoop) 结束之后才能执行,因此 async 函数中的第二段代码会在当前 EventsLoop 所有同步代码都执行之后再执行

  1. 错误处理

(1) 如果 async 函数内部有 try..catch,则内部可以捕获 throw 抛出的错误

async function func() {
try {
await promise1;
throw new Error('出错了!');
// 或者 await Promise.reject('出错了!'); // await 后面的 promise 对象状态变为 Rejected,相当于在此处 throw 一个错误,本质上是由流程管理器调用了 iterator.throw()
await promise2; // 该行语句不会执行
// 以上几段代码可能处于不同的 EventsLoop,但是不同代码段抛出的错误可以被同一个词法作用域的 catch 捕获到,只要在一个代码段执行的时候抛出错误,则立即跳转到词法作用域的 catch 语句,不会再执行 try 语句中的剩余代码段了
} catch (error) {
// 捕获到 async 函数内部抛出的错误,然后继续执行后面的语句,直到遇到 await 或 return
console.log('内部捕获到:' + error.message);
}
await promise3; // 会执行该行语句
return;
}
let promise = func(); // 当 func 函数执行结束,promise 的状态会变为 Resolved

以上代码中,async 函数中不同段的代码处于不同的 EventsLoop 中,但是可以被相同词法作用域的的同一个 try...catch 语句捕获,这点与 Generator 函数一样

function* gen() {
try {
yield promise1;
throw new Error('出错了!');
// 或者外部调用 iterator.throw('出错了!');
// 或者 yield Promise.reject('出错了!'); 本质上是由流程管理器调用了 iterator.throw()
yield promise2;
} catch (error) {
// 捕获到 Generator 函数内部抛出的错误,然后继续执行后面的语句,直到遇到 yield 或 return
console.log('内部捕获到:' + error.message);
}
yield promise3;
return;
}
let promise = co(gen); //当 gen 函数执行结束,promise 的状态会变为 Resolved

(2) 内部捕获的另一种办法

async function func() {
await promise1.catch(error => {}); // await 后面的 promise 对象是 catch 返回的 promise 对象,由于 catch 的回调函数默认 return undefined,所以 catch 返回的 promise 对象的状态肯定为 Resolved
await promise2.catch(error => {});
return;
}
let promise = func();
func 函数执行结束,promise 的状态会变为 Resolved

(3) 外部捕获

async 函数内部抛出错误,如果 async 函数内没有 try...catch,则 async 函数立即停止执行,函数返回的 promise 对象的状态变为 Rejected
如果 async 函数返回的 promise 对象有 catch 语句,则执行 catch 语句,如果没有 catch 语句,则内部抛出的错误不会传递到外层代码

async function func() {
await promise1;
throw new Error('出错了!');
// 或者 await Promise.reject();
await promise2; // 该行代码不会执行
}
let promise = func();
promise.catch(error => { console.log('外部捕获到:' + error); }); // 如果没有 catch,则 async 函数内部的错误不会传递到外层代码

try {
async function func() {
await promise1;
throw new Error('出错了!');
// 或者 await Promise.reject();
await promise2; // 该行代码不会执行
}
let promise = func();
} catch (error) {
console.log('外层代码中捕获到:' + error); // 不会执行这里的代码
}

异步编程

封装单个异步操作

  1. 回调函数

// EventsLoop1
try {
asyncFunc(options, function (err, data) {
// EventsLoop2
if (err) {
// 不要 throw err,因为这里是 EventsLoop2,所以 EventsLoop1 中的 try...catch 无法捕获到这里抛出的错误
doWithError(err);
} else {
doWithData(data);
}
});
} catch (err) {
// 捕获 asyncFunc 中代码执行时的同步错误
}
// EventsLoop1
doSomething();

asyncFunc(options, (err, data) => {
if (err) doWithError(err);
else doWithData(data);
});

  1. Promise

// EventsLoop1
try {
let promise = asyncWrapperFunc(); // 该函数返回一个 Promise 对象,封装了异步操作
promise.then(function (data) {
// EventsLoop2 的末尾
doWithData(data);
}).catch(function (err) {
// 这里捕获 Promise 参数函数中和前面的 then 的回调函数中同步执行时抛出的错误
doWithError(err);
});
// EventsLoop1
doSomething();
} catch (err) {
// 捕获 asyncWrapperFunc 中代码执行时的同步错误
}
// EventsLoop1
doSomething();

promise.then(doWithData).catch(doWithError);

  1. Generator

function* gen() {
let data = yield promise1;
doWithData(data);
}
co(gen).catch(doWithError);

  1. async

async function() {
let data = await promise1;
doWithData(data);
}
async().catch(doWithError);

继发执行多个异步操作

const read = fs.readFile.bind(fs);
const readp = require('read-as-promise');
const doWithError = (err) => { console.log(err); };
const doWithData = (data) => { console.log(data); };
const files = ['1.txt', '2.txt', '3.txt'];

// 回调函数嵌套

read('1.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
read('2.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
read('3.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
});
});
});

function callbackNestingSequence() {
const file = files.shift();
if (file === undefined) return;
read(file, (err, data) => {
if (err) return doWithError(err);
doWithData(data);
callbackNestingSequence();
});
}

// promise 嵌套

readp('1.txt').then(data => {
doWithData(data);
readp('2.txt').then(data => {
doWithData(data);
readp('3.txt').then(data => {
doWithData(data);
}).catch(doWithError);
}).catch(doWithError);
}).catch(doWithError);

function promiseNestingSequence() {
const file = files.shift();
if (file === undefined) return;
readp(file).then(data => {
doWithData(data);
promiseNestingSequence();
}).catch(err => {
doWithError(err);
});
}

// promise 链式调用

Promise.resolve()
.then(() => readp('1.txt'))
.then(doWithData)
.then(() => readp('2.txt'))
.then(doWithData)
.then(() => readp('3.txt'))
.then(doWithData)
.catch(doWithError);

function promiseChainSequence() {
files.reduce((chain, file) => {
return chain.then(() => readp(file)).then(doWithData);
}, Promise.resolve()).catch(doWithError);
}

// generator

let data = yield readp('1.txt');
doWithData(data);
let data = yield readp('2.txt');
doWithData(data);
let data = yield readp('3.txt');
doWithData(data);

function* generatorSequence() {
for (let file of files) {
let data = yield readp(file);
doWithData(data);
}
}
co(generatorSequence).catch(doWithError);

// async

let data = await readp('1.txt');
doWithData(data);
let data = await readp('2.txt');
doWithData(data);
let data = await readp('3.txt');
doWithData(data);

async function asyncSequence() {
for (let file of files) {
let data = await readp(file);
doWithData(data);
}
}
asyncSequence().catch(doWithError);

并发执行多个异步操作

const read = fs.readFile.bind(fs);
const readp = require('read-as-promise');
const doWithError = (err) => { console.log(err); };
const doWithData = (data) => { console.log(data); };
const files = ['1.txt', '2.txt', '3.txt'];

// callback

read('1.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
});
read('2.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
});
read('3.txt', (err, data) => {
if (err) return doWithError(err);
doWithData(data);
});

function callbackParallel() {
for (let file of files) {
read(file, (err, data) => {
if (err) return doWithError(err);
doWithData(data);
});
}
}

// promise

readp('1.txt').then(doWithData).catch(doWithError);
readp('2.txt').then(doWithData).catch(doWithError);
readp('3.txt').then(doWithData).catch(doWithError);

function promiseParallel1() {
for (let file of files) {
readp(file).then(doWithData).catch(doWithError);
}
}

let promise1 = readp('1.txt');
let promise2 = readp('2.txt');
let promise3 = readp('3.txt');

Promise.resolve()
.then(() => promise1)
.then(doWithData)
.then(() => promise2)
.then(doWithData)
.then(() => promise3)
.then(doWithData)
.catch(doWithError);

function promiseParallel2() {
let promises = files.map(file => readp(file));
let p = Promise.resolve();
for (let promise of promises) {
p = p.then(() => promise).then(doWithData);
}
p.catch(doWithError);
}

// Generator

function* generatorParallel1() {
let promises = files.map(file => readp(file));
let datas = yield promises;
for (let data of datas) {
doWithData(data);
}
}
co(generatorParallel1).catch(doWithError);

function* generatorParallel2() {
let promises = files.map(file => readp(file));
for (let promise of promises) {
let data = yield promise;
doWithData(data);
}
}
co(generatorParallel2).catch(doWithError);

// async

async function asyncParallel1() {
let promises = files.map(file => readp(file));
let datas = await Promise.all(promises);
for (let data of datas) {
doWithData(data);
}
}
asyncParallel1().catch(doWithError);

async function asyncParallel2() {
let promises = files.map(file => readp(file));
for (let promise of promises) {
let data = await promise;
doWithData(data);
}
}
asyncParallel2().catch(doWithError);

async function asyncParallel3() {
let promises = files.map(async (file) => { // 调用匿名 async 函数的时候,返回 promise 对象
let data = await readp(file); // 执行到 await 的时候,暂停执行,返回 promise 对象,执行权交给相同 EventsLoop 的剩余同步代码,也就是继续调用 map 的参数函数,并将 files 数组的下一个元素传递到 map 的参数函数中
doWithData(data);
});
await Promise.all(promises);
}
asyncParallel3().catch(doWithError);

asyncParallel1 asyncParallel2 asyncParallel3 都是并发进行异步操作的请求 (readp(file)),但是对于异步操作返回的结果的处理方式不同 (doWithData(data))

处理方式对比:

asyncParallel1 同步并发处理异步操作的结果,等所有异步操作都接收到结果之后,才一次性地按顺序进行所有异步操作的结果的处理 doWithData(data)
asyncParallel2 异步继发处理异步操作的结果,等第一个异步操作接收到结果之后,处理第一个异步操作的结果,即便第二个异步操作先接受到结果,也要等第一个异步操作处理完结果之后再处理第二个异步操作的结果
asyncParallel3 异步并发处理异步操作的结果,哪个异步操作先接收到结果,先处理哪个异步操作,不会等所有异步操作都接受到结果之后才开始处理

处理速度对比:

asyncParallel1 < asyncParallel2 < asyncParallel3

asyncParallel1 最早需要等到所有异步操作都返回结果,才开始进行结果的处理
asyncParallel2 最先处理第一个异步操作的结果,然后处理第二个,然后第三个...,只要第一个异步操作不是最后一个获得结果的,就会比 asyncParallel1 快
asyncParallel3 谁先获得结果就先处理谁,不用等待其他异步操作获得结果,速度最快

错误处理对比:

asyncParallel1 只要有一个异步操作失败,任何异步操作的结果都不会处理,就算其他异步操作成功也无法处理结果,最严格
asyncParallel2 某一个异步操作失败,则包括该异步操作在内的所有还未进行过结果处理的异步操作都不会再进行结果处理了,已经处理过结果的异步操作不受影响,比较严格
asyncParallel3 某个异步操作失败,只影响自己处理结果,不影响其他异步操作处理结果,最宽松

Class

  1. 定义

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return ${this.x}, ${this.y};
}

methodName {

}

get prop() {

}

set prop(value) {

}

  • foo() {

}

}

static hello() {

}
}
var foo = new Point(100, 200);

Point 是函数名,{} 看做是 Point.prototype 对象
类名不存在变量提升,必须先定义,后使用
类作为函数有 name 属性,属性值为 class 关键字后面的字符串
所有类方法都是定义到 Point.prototype 对象中的,所有类方法都是不可枚举的
constructor 方法看做是 Point.prototype.constructor,指向 Point 函数本身
如果没有指定 constructor,则默认生成一个空的 constructor 函数
只能定义类的原型方法,无法定义原型属性,所有实例属性都可以在 constructor 函数中通过 this 定义
类和模块内部默认使用严格模式,不需要使用 'use strict' 显示指定
Point 只能通过 new Point() 调用,否则报错 Point(); //报错
constructor 函数中可以使用 new.target,new.target 指向 Point

类的属性名可以采取表达式命名 methodName {},表达式的值为 String 或 Symbol 类型
可以定义 get 属性和 set 属性,该属性是通过 Object.defineProperty(Point.prototype, 'prop', descriptor} 来定义的
可以定义 Generator 函数
可以定义静态方法,该静态方法仍然不可枚举,该方法属于 Point 对象,而不属于实例

  1. Class 表达式

类表达式的值为函数对象,该函数对象只能用作构造函数

const MyClass = class { // 匿名表达式

};
new MyClass();
MyClass(); // 报错

const MyClass = class Me { // 具名表达式,Me 只能用在类的内部
getClassName() {
return Me.name; // Me
}
};
new Me(); // 报错,Me 只能用在函数内部

立即执行的 class

let person = new class {
constructor(name) {
this.name = name;
}

sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // 张三

  1. 继承

3.1 extends

class ColorPoint extends Point {

}
ColorPoint.proto === Point; // true
ColorPoint.prototype.proto === Point.prototype; // true

class A {

}
A.proto === Function.prototype; // true
A.prototype.proto === Object.prototype; // true

class B extends null {

}
B.proto === Function.prototype; // true
B.prototype.proto === undefined; // true

3.2 super

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 生成 Point 的实例,this 指向该实例
this.color = color; // 增强该实例
}
}

子类必须在 constructor 方法中调用 super 方法,否则子类没有自己的 this 对象
子类必须先调用 super 方法,然后再使用 this 关键字来增强对象,否则 ReferenceError
如果子类没有 constructor 方法,默认会创建一个 constructor(...args) { super(...args); }

ES5 是先创建子类的实例,然后再在子类实例上借用父类的构造函数来生成子类的属性
function ColorPoint(x, y, color) {
Point.call(this, x, y);
this.color = color;
}
let foo = new ColorPoint(100, 200, 'red');

ES6 是先创建父类的实例,然后增强该实例,最后返回这个实例
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 生成 Point 的实例,this 指向该实例
this.color = color; // 增强该实例
}
}
let bar = new ColorPoint(100, 200, 'red'); 其实返回的是增强之后的 Point 实例

bar.proto === ColorPoint.prototype; // true
bar.proto.proto === Point.prototype; // true
bar instanceof ColorPoint; // true;
bar instanceof Point; // true;

3.2.1 super 作为函数使用

class A {
constructor() {
console.log(new.target.name); // B
}
}
class B extends A {
constructor() {
super(); // 相当于 A.call(this);
}
}
let bar = new B();

作为函数使用的时候,super() 只能用在子类的构造函数中,用在其他地方会报错
作为函数使用的时候,super 指向父类的构造函数,但是会绑定子类的 this,super() 相当于调用 A.call(this)
子类调用 super(),父类的构造函数中的 new.target 会指向子类的构造函数对象

3.2.2 super 作为对象使用

在普通方法中指向父类的原型对象

class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
super.p(); // 相当于 A.prototype.p.call(this);
super.x; // 相当于 A.prototype.x
super.x = 2; // 相当于 this.x = 2;
}
}

通过 super.method() 来调用父类 prototype 中的方法时,会绑定子类的 this,super.method() 相当于 A.prototype.method.call(this)
对 super.property = value 为属性赋值时,super 等于 this
通过 super.property 读取属性值时,super 等于父类的原型

在静态方法中指向父类的函数对象

class A {
static foo(msg) {
console.log('A' + msg);
}
}

class B extends A {
}

B.foo === A.foo; // true
B.foo 不存在,则去 B.proto.foo 查找,B.proto === A

class B extends A {
static foo(msg) {
super.foo(msg); // 相当于 A.foo(msg);
console.log('B' + msg);
}
}

B.foo === A.foo; // false
B.hasOwnProperty('foo'); // true 屏蔽了原型上的同名方法

3.2.3 对象方法中的 super 关键字

super 关键字可以用在对象 obj 的方法中,super 只能作为对象来使用,此时 super 指向 obj 的原型对象

var obj = {
toString() {
return 'MyObject: ' + super.toString(); // super 指向 obj.proto
}
};

3.2.4 总结

super 作为对象使用,本质上是指向词法作用域中当前上下文对象的 proto 对象

class B extends A {
hello() {
super.hello(); // super 定义在 B.prototype 对象的 hello 方法中,B.prototype.proto === A.prototype,所以 super 指向 A.prototype
}
}

class B extends A {
static hello() {
super.hello(); // super 定义在 B 对象的 hello 方法中,B.proto === A,所以 super 指向 A
}
}

var proto = {};
var obj = {
hello() {
super.hello(); // super 定义在 obj 对象的 hello 方法中,obj.proto === proto,所以 super 指向 proto
}
};
Object.setPrototypeOf(obj, proto);

3.3 子类可以继承原生构造函数,在原生数据结构的基础上定义自己的数据结构

ES5 先创建子类的实例,然后借用父类的构造函数来初始化子类的实例属性,原生构造函数的内部属性不会被子类的实例初始化
Array 构造函数有一个内部属性 [[DefineOwnProperty]],用来定义新属性时,更新 length 属性,这个内部属性无法被子类获取

function MyArray() {
Array.apply(this, arguments);
}
var foo = new MyArray();
foo[0] = 100;
foo.length; // 0

ES6 先创建父类的实例,然后增强该实例作为子类的实例,因此子类的实例其实就是父类的实例,可以用来继承原生构造函数

class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var bar = new Myarray();
bar[0] = 100;
bar.length; // 0

修饰器

@decorator
class A {
@readonly
name() {}
}

function decorator(target) {
target 指向 A
}

function readonly(target, property, descriptor) {
// target 指向 A.prototype
// property = 'name'
// descriptor = Object.getOwnPropertyDescriptor(target, property);
return descriptor; // 必须返回一个 descriptor 对象
}

修饰器可以用来修饰类和类的属性
修饰器是一个函数,在编译阶段运行修饰器函数
根据修饰器是修饰类还是修饰属性,修饰器函数的参数会有所不同
如果同一个类或同一个属性有多个修饰器,则该方法会先从外到内进入修饰器,然后由内向外执行

修饰器外面可以封装一层函数

@testable(true) // 相当于编辑阶段调用 var decorator = testable(true); decorator(A);
class A {

}

@testable(false)
class B {

}

function testable(isTestable) {
return function (target) { // 返回的匿名函数是真正的修饰器函数
target.isTestable = isTestable;
}
}

模块

  1. 简介

模块就是文件,一个 js 文件就是一个模块
模块自动采用严格模式
每个模块有自己的模块作用域,模块内部的顶层变量只属于该模块的作用域,在该模块外部不可见
在模块之中,顶层的 this 指向 undefined
模块通过 export 可以暴露本模块的内部变量,模块通过 import 可以导入外部模块的变量
export 和 import 命令都是在编译时候执行的,静态执行的
import 代码提升
import 和 export 命令都不能用在代码块中
JS 引擎在对脚本进行静态分析的时候,遇到 import 命令就会生成一个只读引用。等脚本真正执行的时,在根据这个只读引用到被加载的模块中取值

  1. API

2.1 export

  • 正确的方法:
    export var foo = 100;
    export function bar() {};
    export class baz {};
    export {foo, bar, baz}; // 与本模块内部的变量 foo bar baz 一一对应
    export {foo as f};
    export default 表达式;
    export default 100;
    export default foo;
    export default function() {};
    export default function bar() {};
    export default class {};
    export default class baz {};

  • 错误的方法:
    export 100;
    export foo;
    export default var foo = 100;

export 后面可以跟 变量、函数、类的声明。
export 后面可以跟大括号,大括号里面列举对外接口,必须与模块内部的变量名名字相同,一一对应
一个模块只能有一个 export default
export default value 本质上相当于 export let default = value; 输出了一个 default 变量

2.2 import

import url; // 只执行,不输入,执行的时候是同步执行
import {foo, bar, baz} from url; // foo, bar, baz 都是只读的
import {foo as f} from url; // f 是只读的
import * as obj from url; // obj 对象是 frozen 的,Object.isFrozen(obj); 返回 true
import foo from url;
import foo, {bar, baz} from url; // foo 对应 default 变量,bar baz 对应 bar baz 变量

import 存在提升
import 语句同步执行加载的模块
{} 和 url 里面不能使用表达式
url 是相对路径或者绝对路径,可以省略 .js 扩展名,'./foo' '/www/admin/foo.js'
url 如果只是一个模块名,需要提供配置文件说明模块的查找位置 'foo'
多次加载同一个模块,只执行一次
模块是 Singlon 模式,import {foo} from url; import {bar} from url; 相当于 import {foo, bar} from url;
import foo from url 本质上相当于 import {default as foo} from url; 引入了 url 所在模块的 default 变量

Node.js 的import命令
必须给出完整的文件名,不能省略.js后缀,不管是url里还是package.json中的main字段,都不能省略.js后缀
只支持加载本地模块,不支持加载远程模块
只支持相对路径,不支持绝对路径
可以使用模块路径

export import 复合写法

在一个模块中先输入再输出同一个模块

export {foo, bar} from url;
等同于
import {foo, bar} from url;
export {foo, bar}

export {foo as f} from url;
等同于
import {foo as f} from url;
export {f};

export * from url; // 输出 url 模块 export 的所有属性和方法,但是会忽略 url 模块 export default 的值

export {default} from url;

export { es6 as default } from url;
等同于
import { es6 } from url;
export default es6;

export { default as es6 } from url;
等同于
import { default } from url;
export { default as es6 };

  1. CommonJS 模块

CommonJS 模块是运行时同步加载,输出的是一个值的复制
ES6 模块是编译时输出接口,运行时同步运行模块,输出的是值的引用

CommonJS 模块 require 命令第一次加载脚本时就会执行整个脚本,然后在内存中生成一个对象

{
id: '...',
exports: {...},
loaded: true,
...
}

id 是模块名,exports 是模块输出的各个接口,loaded 表示该模块的脚本是否执行完毕
以后再次执行 require 命令,也不会再次执行该模块,只会到 exports 属性上去取值

ArrayBuffer

ArrayBuffer 对象:代表内存中的一段二进制数据,可以通过视图进行操作
视图对象:读和写 ArrayBuffer 中存储的二进制数据,不同类型的视图按照不同的规则解释二进制数据
视图对象部署了数组接口,可以用数组的方法操作内存,视图对象是类数组对象
TypedArray 视图对象
DataView 视图对象

  1. ArrayBuffer

var buf = new ArrayBuffer(n); // n 是字节数

API:

ArrayBuffer.prototype.byteLength // 字节数
ArrayBuffer.prototype.slice(); // 将 buf 的一部分内存区域的内容复制生成一份新的内存,返回一个新的 ArrayBuffer 对象
ArrayBuffer.isView(view); // view 如果是视图对象,返回 true,否则返回 false

  1. TypedArray (一共有 9 种类型,这里用 TypedArray 来统一代表)

很像数组,有 length 属性,用 [] 获取单个元素,数组的方法都可以使用,数组成员都是同一个数据类型
部署了 Iterator 接口

var view = new TypedArray(buffer, byteOffset, length); // byteOffset 是偏移量,length 是数组长度
var view = new TypedArray(n); // n 数组长度,数组元素的值默认为 0
var view = new TypedArray(iterable); // 参数可以是任何具有 Iterator 接口的对象,包括普通数组,生成新的内存
var view = new TypedArray(typedArray); // 参数可以是另一个 TypedArray 实例,生成新的内存

以上第 2 3 4 种方法创建视图的同时,会自动生成底层的 ArrayBuffer 对象

API:

TypedArray.prototype.buffer // 指向 ArrayBuffer 对象
TypedArray.prototype.byteLength // 字节数
TypedArray.prototype.byteOffset // 偏移量
TypedArray.prototype.length // 数组长度
TypedArray.prototype.set(); // 复制内存
TypedArray.prototype.subarray(); // 生成新的视图,不生成新的内存
TypedArray.prototype.slice(); // 生成新的视图,不生成新的内存
TypedArray.of(); // 类似 Array.of();
TypedArray.from(iterable); // 类似 Array.from();

  1. DataView

支持设定字节序

var view = new DataView(buffer, byteOffset, length);

API:

DataView.prototype.buffer
DataView.prototype.byteLength
DataView.prototype.byteOffset
DataView.prototype.getInt8(byteIndex, isLittle); // byteIndex 是字节序号,isLittle true 是小端字节序,否则是大端字节序
DataView.prototype.setInt8(byteIndex, data, isLittle);
DataView.prototype.getUint8();
DataView.prototype.setUint8();
DataView.prototype.getInt16();
DataView.prototype.setInt16();
DataView.prototype.getUint16();
DataView.prototype.setUint16();
DataView.prototype.getFloat32();
DataView.prototype.setFloat32();
DataView.prototype.getFloat64();
DataView.prototype.setFloat64();

  1. 字节序:

对于多个字节存储内容来说,存在着字节序的问题

16 位整数 255 (0x00FF) 存储在内存中,小端字节序存储为 FF00,大端字节序存储为 00FF
小端字节序顺序相反,大端字节序顺序相同
本机一般都是小端字节序,网络是大端字节序
TypedArray 与本机相同,DataView 默认大端字节序,可以指定大端或小端

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容