ES6 學(xué)習(xí)筆記

let 和 const

  1. 循環(huán)語(yǔ)句中不翩,每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的代碼塊作用域

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

以上代碼醇份,每次循環(huán)都會(huì)生成一個(gè)新的代碼塊屡拨,使用 let 聲明的的變量只屬于本次循環(huán)的代碼塊未荒。

  1. for 循環(huán)中承耿,設(shè)置循環(huán)變量的那部分是一個(gè)父作用域妈倔,循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域

for (let i = 0; i < 3; i++) { // 這里的 i 屬于父作用域博投,每次循環(huán)都會(huì)生成一個(gè)新的父作用域
let i = 'abc'; // 這里的 i 屬于子作用域,每次循環(huán)都會(huì)生成一個(gè)新的子作用域
console.log(i); // abc
}

以上代碼盯蝴,不存在重復(fù)聲明毅哗,因?yàn)槊看窝h(huán)的時(shí)候,兩次聲明 let i 在不同的作用域结洼。

  1. let const 不存在變量提升

必須先聲明后使用黎做,否則報(bào)錯(cuò) ReferenceError。

  1. 暫時(shí)性死區(qū) TDZ

代碼塊內(nèi)松忍,只要通過(guò) let 或 const 聲明變量或常量蒸殿,則在 let 和 const 聲明之前,該變量或常量都不可以使用鸣峭,否則報(bào)錯(cuò)宏所。

typeof x; // ReferenceError
let x;

typeof undeclared_variable // 'undefined'

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

函數(shù)調(diào)用的時(shí)候,函數(shù)的參數(shù)區(qū)域形成一個(gè)單獨(dú)的詞法作用域摊溶,等到參數(shù)初始化結(jié)束爬骤,這個(gè)作用域就消失,之后執(zhí)行函數(shù)體的代碼莫换。
只要函數(shù)參數(shù)指定默認(rèn)值霞玄,就看做 let 聲明該參數(shù)骤铃,并且在函數(shù)調(diào)用的時(shí)候聲明并初始化該參數(shù)。
以上代碼相當(dāng)于在函數(shù)調(diào)用的時(shí)候坷剧,執(zhí)行 let x = y; let y = 2; 由于暫時(shí)性死區(qū)惰爬,let x = y; 會(huì)報(bào)錯(cuò)。

let x = x; // ReferenceError

  1. let const 不允許在相同的作用域內(nèi)重復(fù)聲明同一個(gè)變量

var foo = 100;
let foo = 100; // 報(bào)錯(cuò)

let bar = 200;
let bar = 200; // 報(bào)錯(cuò)

let baz = 300;
var baz = 300; // 報(bào)錯(cuò)

const tar = 400;
let tar = 400; // 報(bào)錯(cuò)

const jqz = 500;
const jqz = 500; //報(bào)錯(cuò)

不能在函數(shù)內(nèi)部重新聲明參數(shù)

function func(arg) {
let arg; // 報(bào)錯(cuò)
}

函數(shù)體執(zhí)行的時(shí)候惫企,函數(shù)的參數(shù)看做和函數(shù)體處于同一個(gè)作用域

  1. 盡量不要在塊級(jí)作用域內(nèi)聲明函數(shù)撕瞧,可以使用函數(shù)表達(dá)式的形式代替

  2. 在 if while 等塊語(yǔ)句中用 let const 聲明變量、常量狞尔,必須有大括號(hào)丛版,不能使用單行語(yǔ)句,否則報(bào)錯(cuò)

if (true)
let foo = 100; // 報(bào)錯(cuò)

while (true)
const bar = 100; // 報(bào)錯(cuò)

  1. const 聲明常量的時(shí)候必須進(jìn)行初始化偏序,一旦聲明則不允許修改刪除

const 聲明的常量页畦,不允許修改,是指常量指向的內(nèi)存地址不允許修改禽车,如果常量值是復(fù)合類型寇漫,則常量指向的數(shù)據(jù)結(jié)構(gòu)是可以改變的。

  1. ES6 六種聲明變量的方法 var function let const import class

var function 命令聲明的全局變量仍然是頂層對(duì)象的屬性殉摔。
let const class 聲明的全局變量,不屬于頂層對(duì)象的屬性逸月。

解構(gòu)賦值

解構(gòu)賦值用于一次性聲明多個(gè)變量的簡(jiǎn)寫形式栓撞,聲明的多個(gè)變量處于當(dāng)前的作用域

  1. 解構(gòu)賦值只能解構(gòu)成 數(shù)組 或 對(duì)象

[] = arr;
{} = obj;

通過(guò) let 聲明解構(gòu)

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

通過(guò) var 聲明解構(gòu)

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

通過(guò) const 聲明解構(gòu)

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

如果 foo, bar 沒(méi)有聲明過(guò),默認(rèn)采用 var 聲明解構(gòu)碗硬。如果已經(jīng)聲明過(guò) foo 和 bar瓤湘,則對(duì)變量 foo 和 bar 執(zhí)行賦值操作

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

函數(shù)參數(shù)解構(gòu)

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. 解構(gòu)成數(shù)組 [foo, bar] = iterator;

只要等號(hào)右邊的數(shù)據(jù)結(jié)構(gòu)具有 Iterator 接口恩尾,就可以解構(gòu)成變量數(shù)組

let [foo = true] = iterator;

默認(rèn)值是表達(dá)式弛说,表達(dá)式惰性求值,只有在使用時(shí) (undefined) 才求值翰意。是否使用默認(rèn)值木人,需要使用 === 與 undefined 進(jìn)行比較。

默認(rèn)值表達(dá)式可以包括其他已經(jīng)聲明的變量冀偶。

let [x = 1, y = x] = [];
let [x = y, y = 100] = []; // 報(bào)錯(cuò) ReferenceError
let [x = y, y = 100] = ['hello', 'world']; // 不報(bào)錯(cuò)醒第,沒(méi)有觸發(fā)默認(rèn)值的條件,不會(huì)進(jìn)行默認(rèn)值表達(dá)式求值 x = y进鸠,因此不報(bào)錯(cuò)

  1. 解構(gòu)成對(duì)象 {foo, bar} = obj;

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

以上代碼稠曼,等號(hào)左側(cè)的對(duì)象中,foo 和 bar 是模式(屬性名)客年,f, b 是變量(屬性值)霞幅。解構(gòu)賦值是對(duì)變量 f, b 進(jìn)行賦值漠吻。

對(duì)象解構(gòu)賦值的簡(jiǎn)寫形式

let { foo, bar } = { foo: ‘hello’, bar: 100 };
相當(dāng)于
let { foo: foo, bar: bar } = { foo: ‘hello’, bar: 100 };

同名屬性可以為多個(gè)變量重復(fù)賦值

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

如果 obj 是 boolean number string 等基本類型,則轉(zhuǎn)換為基本包裝類型蝗岖。如果 obj 是 undefined 或 null侥猩,則報(bào)錯(cuò)。

會(huì)解構(gòu) obj 的所有實(shí)例屬性和原型屬性抵赢,不可枚舉的屬性也會(huì)解構(gòu)。

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; 的簡(jiǎn)寫
console.log(year, language); // 200 "javascript"

解構(gòu)賦值的默認(rèn)值使用 === 與 undefined 進(jìn)行判斷唧取,默認(rèn)值表達(dá)式進(jìn)行惰性求值

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

  1. 應(yīng)用

凡是能夠應(yīng)用默認(rèn)賦值的地方铅鲤,都可以用解構(gòu)賦值,比如 = 賦值枫弟,函數(shù)的參數(shù)賦值邢享,for-in for-of 循環(huán)等
for (let [key, value] of iterator.entries()) {}

交換變量
[x, y] = [y, x];

對(duì)象的解構(gòu)賦值,可以很方便地將現(xiàn)有對(duì)象的方法淡诗,賦值到某個(gè)變量
let { log, sin, con } = Math // 看做是 let log = Math.log; let sin = Math.sin; let con = Math.con; 的簡(jiǎn)寫形式

從函數(shù)返回多個(gè)值
let [a, b, c] = example();

函數(shù)參數(shù)的默認(rèn)值
function func({
async = true,
global = true
}) {

}

字符串

  1. '\u{碼點(diǎn)}' 碼點(diǎn)可以大于 0xFFFF骇塘,若碼點(diǎn)大于 0xFFFF 仍然視為兩個(gè)字符

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

  1. 字符串可以用 for of 循環(huán),能夠識(shí)別碼點(diǎn)大于 0xFFFF 的字符串

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

以上代碼只執(zhí)行一次循環(huán)

  1. 支持碼點(diǎn)大于 0xFFFF 字符的 API

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

  1. 模板字符串 ``

可以當(dāng)做普通字符串韩容,可以當(dāng)做多行字符串款违,可以嵌入變量

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

${表達(dá)式},表達(dá)式可以是變量群凶、常量插爹、字面量、數(shù)組元素请梢、對(duì)象屬性赠尾、函數(shù)調(diào)用、運(yùn)算符表達(dá)式等等

表達(dá)式的值如果不是字符串毅弧,會(huì)進(jìn)行自動(dòng)類型轉(zhuǎn)換

${hello} 可以嵌套气嫁,內(nèi)層的 `` 必須在 ${} 之內(nèi)

  1. 標(biāo)簽?zāi)0?/li>

funchello

相當(dāng)于調(diào)用函數(shù) func(args, ...values);

args 和 args.raw 都是數(shù)組

標(biāo)簽?zāi)0蹇梢杂糜谶^(guò)濾用戶輸入,網(wǎng)站國(guó)際化够坐,自定義模板寸宵,內(nèi)嵌其他語(yǔ)言等作用

函數(shù)

  1. 函數(shù)調(diào)用的時(shí)候,函數(shù)的參數(shù)區(qū)域形成一個(gè)單獨(dú)的詞法作用域咆霜。等到參數(shù)初始化結(jié)束邓馒,這個(gè)作用域就消失

函數(shù)參數(shù)的默認(rèn)值相當(dāng)于 let 聲明;

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

}

以上代碼相當(dāng)于 let x = 100, y = x;

function bar(x = x) {

}
bar(); // 報(bào)錯(cuò),let x = x; 暫時(shí)性死區(qū)

函數(shù)參數(shù)的默認(rèn)值表達(dá)式惰性求值蛾坯,函數(shù)調(diào)用的時(shí)候光酣,將參數(shù)與 undefined 進(jìn)行 === 比較

函數(shù)參數(shù)的默認(rèn)值和解構(gòu)變量的默認(rèn)值是兩個(gè)概念,下面的函數(shù)只有參數(shù)的默認(rèn)值脉课,沒(méi)有解構(gòu)變量的默認(rèn)值

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. 箭頭函數(shù)

沒(méi)有自己的 this, arguments救军,根據(jù)詞法作用域引用外層函數(shù)的 this, arguments财异。
箭頭函數(shù)的 apply call bind 都無(wú)法改變 this 的指向
箭頭函數(shù)不能當(dāng)做構(gòu)造函數(shù),不能當(dāng)做 Generator 函數(shù)唱遭。

數(shù)組

  1. rest 參數(shù) (出現(xiàn)在賦值操作的左側(cè))

函數(shù)定義的時(shí)候戳寸,參數(shù)可以是 rest 參數(shù)

function foo(...rest) { // 函數(shù)的 rest 參數(shù)一定是數(shù)組
rest 一定是數(shù)組
}
foo([100, 200]);

function foo(...obj) {
obj 是數(shù)組 [{language: 'javascript', year: 1995}]
}
foo({language: 'javascript', year: 1995});

解構(gòu)賦值的時(shí)候,等號(hào)左邊的可以是 rest 參數(shù)

let [...arr] = [100, 200]; // arr 是數(shù)組
let {...obj} = {language: 'javascript', year: 1995}; // obj 是對(duì)象

rest 參數(shù)只能是最后一個(gè)參數(shù)

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

  1. 擴(kuò)展運(yùn)算符 ... (出現(xiàn)在賦值操作的右側(cè))

函數(shù)調(diào)用的時(shí)候拷泽,實(shí)參可以是擴(kuò)展運(yùn)算符

foo(...iterable); // 將具有 Iterator 接口的對(duì)象的成員一個(gè)一個(gè)傳入函數(shù)
bar(...obj); // 不可以這樣疫鹊,函數(shù)調(diào)用的時(shí)候,只能夠擴(kuò)展具有 Iterator 接口的對(duì)象(包括數(shù)組)

賦值的時(shí)候司致,等號(hào)右邊可以是擴(kuò)展運(yùn)算符

foo = [...iterable]; // 將具有 Iterator 接口的對(duì)象的成員展開(kāi)成一個(gè)數(shù)組
bar = {...obj}; // bar 包括 obj 對(duì)象的所有可枚舉的實(shí)例屬性

擴(kuò)展運(yùn)算符可以不是最后一個(gè)參數(shù)

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

擴(kuò)展空數(shù)組或空對(duì)象沒(méi)有任何效果

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

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

  1. Array.from

...iterable 可以擴(kuò)展具有 Iterator 接口的對(duì)象拆吆,但是不能擴(kuò)展沒(méi)有 Iterator 接口的類數(shù)組對(duì)象
Array.from(obj); 可以轉(zhuǎn)換具有 Iterator 的對(duì)象,也可以轉(zhuǎn)換沒(méi)有 Iterator 接口的類數(shù)組對(duì)象
當(dāng) obj 為具有 Iterator 接口的對(duì)象時(shí)候脂矫,let arr = Array.from(obj) 與 let arr = [...obj] 作用一樣

  1. 字符串包裝類對(duì)象是具有 Iterator 接口的對(duì)象枣耀,因此可以用 [...string] 或者 Array.from(string),來(lái)生成數(shù)組庭再,可以解析 unicode 碼位大于 0xFFFF 的值

對(duì)象

  1. 屬性簡(jiǎn)潔表示法

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

  1. 屬性名表達(dá)式

{ [表達(dá)式]: value }
{ 表達(dá)式 {} }

表達(dá)式的值為字符串或 Symbol捞奕,如果不是字符串或 Symbol,自動(dòng)轉(zhuǎn)換為字符串

  1. Object.is()

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

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

只復(fù)制 source 對(duì)象自身的可枚舉屬性拄轻,后面對(duì)象的屬性覆蓋前面對(duì)象的屬性
assign 的本質(zhì)是使用 = 號(hào)進(jìn)行賦值颅围,所以是淺復(fù)制,target.obj = source.obj
對(duì)于 getter 屬性的復(fù)制哺眯,只能復(fù)制值谷浅,因?yàn)?target.prop = source.prop,調(diào)用 prop 的 getter 函數(shù)

let obj = Object.assign({}, source); // 相當(dāng)于 let obj = {...source};
let obj = Object.assign({}, source1, source2); // 相當(dāng)于 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 個(gè)操作會(huì)忽略 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 對(duì)象自身的可枚舉屬性

  1. 屬性的遍歷

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

  1. 應(yīng)用

復(fù)制對(duì)象 (包括 get set 屬性)

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

克隆對(duì)象

var clone = Object.assign(Object.create(Object.getPrototypeOf(origin)), origin); // 無(wú)法克隆 get 和 set 屬性一疯,另外 origin 的 enumerable 為 false 的屬性也無(wú)法克隆
var clone = Object.create(Object.getPrototypeOf(origin), Object.getOwnPropertyDescriptors(origin)); // 完美克隆

Symbol

  1. 基本用法

Symbol 是新的數(shù)據(jù)類型,和 Boolean Number String Undefined Null Object 一樣
Symbol 是基本數(shù)據(jù)類型夺姑,本質(zhì)上和字符串類似

創(chuàng)建 Symbol 值

let s = Symbol();
let s = Symbol('foo');
let s = Symbol.for('foo');
new Symbol(); // 報(bào)錯(cuò)

Symbol('foo'); // 不進(jìn)行全局登記
Symbol.for('foo'); // 進(jìn)行全局登記

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

Symbol 值不能與其它類型進(jìn)行運(yùn)算墩邀,否則報(bào)錯(cuò)

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 用作屬性名時(shí)候只能用 [] 運(yùn)算符

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. 內(nèi)部 Symbol 值

Symbol.iterator

如果一個(gè)對(duì)象具有 Symbol.iterator 屬性,且該屬性是一個(gè)生成器函數(shù)盏浙,則該對(duì)象具有 Iterator 接口眉睹,可以進(jìn)行 for...of 循環(huán)和擴(kuò)展 ... 操作
obj[Symbol.iterator]

  1. 應(yīng)用

定義常量,消除魔術(shù)字符串

Set 和 Map

  1. Set 集合废膘,類似數(shù)組竹海,成員唯一,沒(méi)有重復(fù)

創(chuàng)建 Set 對(duì)象

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]

應(yīng)用

刪除數(shù)組重復(fù)元素

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

  1. Map 類型丐黄,類似對(duì)象斋配,Hash 解構(gòu),鍵值對(duì)映射,各種類型的值都可以當(dāng)作鍵

創(chuàng)建 Map

var m = new Map();
var m = new Map(iterable); // iterable 對(duì)象每個(gè)成員必須是一個(gè)雙元素的數(shù)組

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() 對(duì)同一個(gè)鍵多次賦值艰争,后面的值覆蓋前面的值
map.get() 讀取未知的鍵坏瞄,值為 undefined
如果 Map 的鍵是簡(jiǎn)單類型,只要值相同甩卓,則認(rèn)為是同一個(gè)鍵
如果 Map 的鍵是復(fù)雜類型鸠匀,必須指向同一個(gè)對(duì)象,才認(rèn)為是同一個(gè)鍵

  1. WeakSet 和 WeakMap

WeakSet 成員必須都是對(duì)象逾柿,WeakMap 成員的鍵必須都是對(duì)象
WeakSet 成員是該對(duì)象的弱引用缀棍,不計(jì)入垃圾回收機(jī)制的引用計(jì)數(shù)
WeakMap 成員的鍵是該對(duì)象的弱引用,不計(jì)入垃圾回收機(jī)制的引用計(jì)數(shù)
WeakMap 成員的值指向的對(duì)象是強(qiáng)引用
當(dāng) WeakSet 成員指向的對(duì)象消失机错,WeakSet 里面的引用就會(huì)自動(dòng)消失
當(dāng) WeakMap 成員鍵指向的對(duì)象消失睦柴,WeakMap 成員的鍵里面的引用就會(huì)自動(dòng)消失,該成員也會(huì)自動(dòng)消失
WeakSet 和 WeakMap 沒(méi)有 size 屬性毡熏,不能遍歷
WeakSet 和 WeakMap 常用來(lái)保存 DOM 元素,當(dāng) DOM 元素消失侣诵,則 WeakSet 和 WeakMap 相關(guān)的成員也會(huì)自動(dòng)消失

Proxy 和 Reflect

Proxy 用于代理某個(gè)對(duì)象的默認(rèn)操作
Reflect 用于規(guī)定所有對(duì)象的默認(rèn)操作

  1. Proxy 對(duì)象

創(chuàng)建 proxy 對(duì)象
var proxy = new Proxy(target, handler);
var { proxy, revoke } = Proxy.revocable(target, handler);

proxy 對(duì)象代理了 target 對(duì)象痢法,任何對(duì) proxy 對(duì)象的操作,實(shí)際上都是在操作 target 對(duì)象
handler 對(duì)象可以攔截 proxy 對(duì)象的默認(rèn)操作杜顺,但是不會(huì)攔截直接對(duì) target 對(duì)象的所有操作
如果沒(méi)有 handler 對(duì)象财搁,或者有 handler 對(duì)象,但是沒(méi)有設(shè)置相應(yīng)的攔截操作躬络,則對(duì) proxy 對(duì)象的操作則直接落在 target 對(duì)象上尖奔,相當(dāng)于直接操作 target 對(duì)象
Proxy.revocable 方法返回一個(gè)對(duì)象,其 proxy 屬性是 Proxy 實(shí)例穷当,revoke 屬性是一個(gè)函數(shù)提茁,可以取消 Proxy 實(shí)例對(duì) target 對(duì)象的代理
Proxy.revocable 的一個(gè)使用場(chǎng)景是,目標(biāo)對(duì)象不允許直接訪問(wèn)馁菜,必須通過(guò)代理訪問(wèn)茴扁,一旦訪問(wèn)結(jié)束,就調(diào)用 revoke(); 收回代理汪疮,不允許再次訪問(wèn) 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 '李四'

以上代碼攔截了對(duì) proxy 對(duì)象的 get 操作峭火,修改了 get 操作的默認(rèn)行為,但是沒(méi)有攔截 set 操作智嚷,因此 set 操作直接落在 target 對(duì)象上

當(dāng)使用 proxy 代理 target 對(duì)象時(shí)卖丸,target 對(duì)象內(nèi)部的 this 會(huì)指向 proxy 對(duì)象

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 對(duì)象

將 Object 對(duì)象上的一些明顯屬于語(yǔ)言內(nèi)部的方法(比如 Object.defineProperty)放到 Reflect 對(duì)象上
修改某些 Object 方法的返回結(jié)果,讓其變得更合理
讓某些命令式的 Object 操作都變成函數(shù)行為盏道,比如 name in obj 和 delete obj[name]
Reflect 對(duì)象的方法與 Proxy 對(duì)象的方法一一對(duì)應(yīng)稍浆,使 Proxy 對(duì)象的攔截器不管怎么修改默認(rèn)行為,都可以方便地調(diào)用對(duì)應(yīng)的 Reflect 方法來(lái)完成默認(rèn)行為

API

Reflect.get(target, name, receiver) // 如果 name 是 get 取值函數(shù),則 receiver 是取值函數(shù)內(nèi)部的 this 值
Reflect.set(target, name, value, receiver) // 如果 name 是 set 存值函數(shù)粹湃,則 receiver 是存值函數(shù)內(nèi)部的 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. 應(yīng)用

使用 Proxy 和 Reflect 實(shí)現(xiàn)觀察者模式

const queuedObservers = new Set(); // 觀察者函數(shù)存放在該集合

const observe = fn => queuedObservers.add(fn); // 添加觀察者函數(shù)
const observable = obj => new Proxy(obj, {set}); // 代理原始數(shù)據(jù)對(duì)象恐仑,攔截代理對(duì)象的 set 行為

// 攔截代理對(duì)象的 set 行為
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver); // 執(zhí)行數(shù)據(jù)對(duì)象的默認(rèn) set 行為
queuedObservers.forEach(observer => observer()); // 調(diào)用所有觀察者函數(shù)
return result;
}

凡是在讀取或?qū)懭雽?duì)象的屬性的同時(shí),需要進(jìn)行額外的操作(Ajax 請(qǐng)求为鳄、類型檢驗(yàn)裳仆、寫入日志...),就可以使用 proxy 對(duì)象

Promise

優(yōu)點(diǎn):

將異步操作流程用同步方式表達(dá)
統(tǒng)一異步操作接口

缺點(diǎn):

無(wú)法取消 Promise孤钦,一旦新建立即執(zhí)行
Promise 內(nèi)部拋出的錯(cuò)誤不會(huì)反映到外部
當(dāng)處于 Pending 狀態(tài)時(shí)歧斟,無(wú)法得知進(jìn)展到哪一個(gè)階段

狀態(tài):

Pending Fulfilled(Resolved) Rejected

一旦狀態(tài)改變就不會(huì)再變,只能是由 Pending -> Fulfilled 或者 Pending -> Rejected
就算先改變 Promise 對(duì)象的狀態(tài)偏形,然后添加回調(diào)函數(shù)静袖,該回調(diào)函數(shù)仍然會(huì)執(zhí)行,與事件機(jī)制 (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 封裝異步操作俊扭,在參數(shù)函數(shù)體內(nèi)執(zhí)行異步操作队橙,在異步操作的回調(diào)函數(shù)中執(zhí)行 resolve() 或 reject() 改變 Promise 的狀態(tài)。
參數(shù)函數(shù)體內(nèi)本身的代碼是同步執(zhí)行的萨惑,先同步執(zhí)行參數(shù)函數(shù)捐康,再返回 promise 對(duì)象
then 中的回調(diào)函數(shù)會(huì)在本輪 EventsLoop (同一段 <script></script>) 結(jié)束之后该抒,下輪 EventsLoop (所有 <script></script> 都執(zhí)行之后) 執(zhí)行之前執(zhí)行
一般 then 中的回調(diào)函數(shù)會(huì)在 resolve 或 reject 函數(shù)執(zhí)行時(shí)(也就是 promise 對(duì)象的狀態(tài)改變時(shí))所在的 EventsLoop 末尾執(zhí)行

如果 resolve 或者 reject 函數(shù)帶有參數(shù)盘榨,該參數(shù)會(huì)傳遞給 then 中的回調(diào)函數(shù)
resolve 的參數(shù)如果是另一個(gè) promise 對(duì)象,則當(dāng)前 promise 對(duì)象的狀態(tài)與另一個(gè) promise 對(duì)象的狀態(tài)綁定

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

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

p2 的狀態(tài)與 p1 的狀態(tài)綁定勺良,如果 p1 的狀態(tài)是 Pendding姐仅,則 p2 的狀態(tài)也是 Pendding花枫,等待 p1 的狀態(tài)發(fā)生變化
p1 的狀態(tài)變?yōu)?Resolved,則 p2 的狀態(tài)立即變?yōu)?Resolved掏膏,p1 的狀態(tài)變?yōu)?Rejected劳翰,則 p2 的狀態(tài)立即變?yōu)?Rejected

雖然調(diào)用 resolve 或 reject 函數(shù)不會(huì)結(jié)束 Promise 參數(shù)函數(shù)的執(zhí)行,但是一般來(lái)說(shuō)壤追,調(diào)用 resolve 或 reject 以后磕道,Promise 的使命就完成了,后繼操作應(yīng)該放到 then 方法里面
then 方法的回調(diào)函數(shù)會(huì)在調(diào)用 resolve 或 reject 函數(shù)所在的 EventsLoop 末尾執(zhí)行

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

then 和 catch 返回一個(gè)新的 Promise 實(shí)例
then 和 catch 方法的回調(diào)函數(shù)中(不管是 Resolved 回調(diào)函數(shù)行冰,還是 Rejected 回調(diào)函數(shù))溺蕉,return value; 則 then 和 catch 方法返回的 Promise 實(shí)例變?yōu)?Resolved,相當(dāng)于調(diào)用 resolve(value);
then 和 catch 方法的回調(diào)函數(shù)中(不管是 Resolved 回調(diào)函數(shù)悼做,還是 Rejected 回調(diào)函數(shù))疯特,throw error; 則 then 和 catch 方法返回的 Promise 實(shí)例變?yōu)?Rejected,相當(dāng)于調(diào)用 reject(error);
then 和 catch 方法的回調(diào)函數(shù)中(不管是 Resolved 回調(diào)函數(shù)肛走,還是 Rejected 回調(diào)函數(shù))漓雅,return anOtherPromise; 則 then 和 catch 方法返回的 Promise 實(shí)例與 anOtherPromise 的狀態(tài)綁定

Promise.reject().then(() => {});
如果 promise 對(duì)象變?yōu)?Rejected,并且 then 中沒(méi)有指定 Rejected 回調(diào)函數(shù),則 then 返回的 Promise 實(shí)例的狀態(tài)也變?yōu)?Rejected
Promise.resolve().catch(() => {});
如果 promise 對(duì)象變?yōu)?Resolved邻吞,由于 catch 中無(wú)法指定 Resolved 回調(diào)函數(shù)组题,所以 catch 返回的 Promise 實(shí)例的狀態(tài)也變?yōu)?Resolved

Promise.reject().then(() => {}, () => {});
如果 promise 對(duì)象變?yōu)?Rejected,并且 then 指定了 Rejected 回調(diào)函數(shù)抱冷,則根據(jù) Rejected 回調(diào)函數(shù)來(lái)決定 then 返回的 Promise 實(shí)例的狀態(tài)
Promise.reject().then(() => {}, () => true);
如果 Rejected 回調(diào)函數(shù)有返回值(默認(rèn)返回 undefined)崔列,則 then 返回的 Promise 實(shí)例的狀態(tài)變?yōu)?Resolved
Promise.reject().then(() => {}, () => {throw error});
如果 Rejected 回調(diào)函數(shù)拋出錯(cuò)誤,則 then 返回的 Promise 實(shí)例的狀態(tài)變?yōu)?Rejected
Promise.reject().then(() => {}, () => anOtherPromise);
如果 Rejected 回調(diào)函數(shù)有返回值旺遮,且返回了另一個(gè) Promise 對(duì)象赵讯,則 then 返回的 Promise 實(shí)例的狀態(tài)與 anOtherPromise 的狀態(tài)綁定

  1. throw

在 Promise 參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤,或者在 then 或 catch 方法中的回調(diào)函數(shù)(不管是 Resolved 回調(diào)函數(shù)還是 Rejected 回調(diào)函數(shù))中同步執(zhí)行時(shí)拋出的錯(cuò)誤耿眉,當(dāng)錯(cuò)誤沒(méi)有被捕獲時(shí)边翼,不會(huì)傳遞到外層代碼
在 Promise 參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤,或者在 then 或 catch 方法中的回調(diào)函數(shù)(不管是 Resolved 回調(diào)函數(shù)還是 Rejected 回調(diào)函數(shù))中異步執(zhí)行時(shí)拋出的錯(cuò)誤鸣剪,不管是否有 catch 捕獲组底,該錯(cuò)誤都不會(huì)被捕獲,并且會(huì)傳遞到外層代碼

var promise = new Promise(function (resolve, reject) {
// 同步執(zhí)行時(shí)拋出的錯(cuò)誤筐骇,相當(dāng)于調(diào)用 reject 函數(shù)斤寇,該 promise 對(duì)象的狀態(tài)立即變?yōu)?Rejected
throw error;
});
// promise 對(duì)象沒(méi)有指定捕獲錯(cuò)誤的函數(shù)(then 中的 Rejected 回調(diào)函數(shù)或 catch 方法),該錯(cuò)誤不會(huì)傳遞到外層代碼拥褂,但是瀏覽器環(huán)境會(huì)提示錯(cuò)誤信息,服務(wù)器中退出碼是0(表示執(zhí)行成功)
promise.then(value => value);

var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
// 異步執(zhí)行時(shí)拋出的錯(cuò)誤牙寞,錯(cuò)誤會(huì)傳遞到外層代碼饺鹃,成為未捕獲的錯(cuò)誤
throw error;
// 此時(shí) promise 對(duì)象的狀態(tài)為 Pending
}, 1000);
});
// promise 對(duì)象指定了捕獲錯(cuò)誤的 catch 方法,但是異步執(zhí)行時(shí)拋出的錯(cuò)誤不會(huì)被 catch 方法捕獲间雀,錯(cuò)誤會(huì)傳遞到外層代碼
promise.catch(error => error);

var promise = Promise.resolve();
var p1 = promise.then(() => {
console.log('p1 then');
// 同步執(zhí)行時(shí)拋出的錯(cuò)誤悔详,該錯(cuò)誤不會(huì)傳遞到外層代碼
throw 'error';
// 此時(shí) p1 的狀態(tài)是 Rejected
});

var p2 = promise.then(() => {
console.log('p2 then');
setTimeout(function () {
// 異步執(zhí)行時(shí)拋出的錯(cuò)誤,該錯(cuò)誤不會(huì)被捕獲惹挟,會(huì)傳遞到外層代碼茄螃,成為未捕獲的錯(cuò)誤
throw 'error';
// 此時(shí) p2 的狀態(tài)是 Resolved,因?yàn)?then 中的 Resolved 回調(diào)函數(shù)沒(méi)有返回值连锯,則默認(rèn) return undefined归苍,所以 p2 的狀態(tài)在 then 方法的 Resolved 回調(diào)函數(shù)調(diào)用結(jié)束之后就變?yōu)?Resolved 了
}, 1000);
});

  1. API

Promise.all(iterable); // iterable 的每個(gè)成員必須是 promise 對(duì)象
Promise.race(iterable); // iterable 的每個(gè)成員必須是 promise 對(duì)象
Promise.resolve();
Promise.reject();

  1. 應(yīng)用

function wrapper() {
// ... 異步操作的包裝函數(shù)的代碼

// 返回一個(gè) Promise 對(duì)象,來(lái)統(tǒng)一不同異步操作的接口
return new Promise(function (resolve, reject) {
// 真正的異步操作封裝在參數(shù)函數(shù)中

// 開(kāi)啟一個(gè)線程來(lái)進(jìn)行異步操作
var obj = doAsync();

// 異步操作成功的回調(diào)函數(shù)
obj.onsuccess = function (value) {
  // 該回調(diào)函數(shù)會(huì)在下一輪或者下幾輪 EventsLoop 執(zhí)行运怖,具體要看消息隊(duì)列里排隊(duì)的消息有多少
  resolve(value);
};

// 異步操作失敗的回調(diào)函數(shù)
obj.onerror = function (error) {
  // 該回調(diào)函數(shù)會(huì)在下一輪或者下幾輪 EventsLoop 執(zhí)行拼弃,具體要看消息隊(duì)列里排隊(duì)的消息有多少
  reject(error);
};

});
}

var option = wrapper();
option.then(value => { //... }).catch(error => { //... });
//這里的 then 或 catch 中的回調(diào)函數(shù)一定是在 resolve 或 reject 函數(shù)調(diào)用的那輪 EventsLoop 的末尾執(zhí)行的

wrapper 函數(shù)中的錯(cuò)誤屬于同步錯(cuò)誤,可以被外層 try..catch 捕獲

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

try {
var option = wrapper();
options.then(value => { //... }).catch(error => { //... }); // 該行代碼不會(huì)執(zhí)行
} catch (error) {
// 可以捕獲 wrapper 函數(shù)中的同步錯(cuò)誤
}

參數(shù)函數(shù)中的錯(cuò)誤有兩種情況摇展,一種是在參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤吻氧,一種是在參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤,但是這兩種錯(cuò)誤廣義上都屬于異步錯(cuò)誤

參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤,不會(huì)傳遞到外層的 try...catch盯孙,如果指定了 promise 對(duì)象的 catch 方法鲁森,會(huì)被 catch 方法捕獲,如果沒(méi)有指定 catch 方法振惰,也不會(huì)傳遞到外層

try {
var option = wrapper();
option.then(value => { //... }).catch(error => { // 可以捕獲到參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤 });
} catch (error) {
// 無(wú)法捕獲到參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤
}

參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤

function wrapper() {
// ... 異步操作的包裝函數(shù)的代碼

// 返回一個(gè) Promise 對(duì)象歌溉,來(lái)統(tǒng)一不同異步操作的接口
return new Promise(function (resolve, reject) {
// 真正的異步操作封裝在參數(shù)函數(shù)中

// 開(kāi)啟一個(gè)線程來(lái)進(jìn)行異步操作
var obj = doAsync();

// 異步操作成功的回調(diào)函數(shù)
obj.onsuccess = function (value) {
  EventsLoop2:
  try {
    // 參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤
    // 當(dāng)前代碼所處的 EventsLoop (也就是 EventsLoop2 標(biāo)簽所處的 EventsLoop) 已經(jīng)不是參數(shù)函數(shù)執(zhí)行時(shí)所處的 EventsLoop 了 (也就是 EventsLoop1 標(biāo)簽所處的 EventsLoop),此時(shí) EventsLoop1 中的所有代碼早就執(zhí)行完畢了
    // 因此這里拋出的錯(cuò)誤不能夠被 EventsLoop1 中的 try...catch 捕獲报账,只能被 EventsLoop2 中的 try...catch 捕獲
    throw 'error';
    resolve(value);
  } catch (error) {
    // 可以捕獲到參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤
  }
};

// 異步操作失敗的回調(diào)函數(shù)
obj.onerror = function (error) {
  reject(error);
};

});
}

EventsLoop1:
try {
var option = wrapper();
option.then(value => { //... }).catch(error => { // 可以捕獲到參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤 });
} catch (error) {
// 無(wú)法捕獲到參數(shù)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤研底,也無(wú)法捕獲到參數(shù)函數(shù)中異步執(zhí)行時(shí)拋出的錯(cuò)誤,這兩種錯(cuò)誤廣義上都屬于異步錯(cuò)誤
// 總之只能捕獲同步錯(cuò)誤透罢,無(wú)法捕獲異步錯(cuò)誤
}

Iterator 和 for...of 循環(huán)

目的

為所有數(shù)據(jù)結(jié)構(gòu) (Array, Object, Set, Map) 提供統(tǒng)一的遍歷接口

概念

生成器函數(shù) Symbol.iterator{}榜晦,遍歷器對(duì)象 iterator,可遍歷對(duì)象 iterable

如果一個(gè)對(duì)象有 [Symbol.iterator] 屬性 (實(shí)例屬性或原型屬性都可以)羽圃,則該對(duì)象是 iterable 對(duì)象
[Symbol.iterator] 屬性是一個(gè)生成器函數(shù)乾胶,調(diào)用該函數(shù)會(huì)生成一個(gè) iterator 對(duì)象
iterator 對(duì)象有 next 方法,每次調(diào)用 next 方法來(lái)移動(dòng)指針并返回?cái)?shù)據(jù)朽寞,每次調(diào)用 next 返回 { value: data, done: boolean }

自定義遍歷器接口

let iterable = {
Symbol.iterator {
// 這里可以定義 next 閉包需要的狀態(tài)變量

// 遍歷器對(duì)象的 next 方法
function next() {
  // 移動(dòng)指針识窿,判斷是否遍歷結(jié)束,返回?cái)?shù)據(jù)
  return {
    value: value,
    done: boolean
  }
}

let iterator = { next };
// 返回遍歷器對(duì)象
return iterator;

}
};

iterable 對(duì)象可以遍歷
for(let value of iterable) {...}
[...iterable]

自定義類數(shù)組對(duì)象的遍歷器接口有一種簡(jiǎn)便的方法

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

利用 Generator 函數(shù)自定義遍歷器接口

let iterable = {

原生遍歷器接口

有些原生數(shù)據(jù)結(jié)構(gòu)部署了 [Symbol.iterator] 屬性(該屬性是生成器函數(shù))脑融,因此這些數(shù)據(jù)結(jié)構(gòu)的對(duì)象是 iterable 對(duì)象
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)如下:

  • Array
  • Set
  • Map
  • String
  • TypedArray
  • 函數(shù)的 arguments 對(duì)象
  • NodeList 對(duì)象

計(jì)算生成的數(shù)據(jù)結(jié)構(gòu)

Array Set Map 都有 keys values entries 三個(gè)方法喻频,這三個(gè)方法是生成器函數(shù),返回遍歷器對(duì)象

應(yīng)用

let [foo, bar] = iterable; // 解構(gòu)賦值
let arr = [...iterable]; // 擴(kuò)展運(yùn)算符
func(...iterable); // 擴(kuò)展運(yùn)算符
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 對(duì)象的地方肘迎,都可以使用 iterator 對(duì)象

for (let value of iterable) {

}
// 以上代碼本質(zhì)上是調(diào)用如下代碼
for (let value of iterableSymbol.iterator) {

}
// for...of 循環(huán)的本質(zhì)是調(diào)用 while 循環(huán)
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) => {}); // 無(wú)法中途退出
for (let pro in obj) {} // 適合遍歷對(duì)象的可枚舉屬性甥温,包括原型屬性,不適合遍歷數(shù)組等 iterable 對(duì)象
for (let value of iterable) {} // 最好的遍歷方法妓布,可以 break, continue

Generator 函數(shù)

簡(jiǎn)介

Generator 函數(shù)是一個(gè)生成器函數(shù)姻蚓,調(diào)用 Generator 函數(shù)的時(shí)候,該函數(shù)并不執(zhí)行匣沼,而是返回一個(gè) iterator 對(duì)象狰挡,通過(guò)調(diào)用 iterator.next() 方法來(lái)分段執(zhí)行 Generator 函數(shù)
第一次調(diào)用 iterator.next(),Generator 函數(shù)才開(kāi)始執(zhí)行释涛,直到遇到第一條 yield 語(yǔ)句加叁,函數(shù)便暫停執(zhí)行
再次調(diào)用 iterator.next(),Generator 函數(shù)會(huì)從上次暫停的地方繼續(xù)執(zhí)行唇撬,直到遇到下一條 yield 語(yǔ)句 (或 return 語(yǔ)句)
iterator.next() 方法返回一個(gè)對(duì)象殉农,該對(duì)象有 value 和 done 兩個(gè)屬性,value 屬性是 yield 后面的表達(dá)式的值 (或 return 的值)局荚,done 是一個(gè)布爾值超凳,表示遍歷是否結(jié)束

function* gen() {
yield 'hello';
yield 'world';
return 'ending'; // 如果函數(shù)最后沒(méi)有 return 語(yǔ)句愈污,則默認(rèn)為 return undefined。gen() 生成的 iterator 被遍歷時(shí)轮傍,不包括 return 的值
}

let iterator = gen(); // 生成一個(gè) iterator 對(duì)象
let { value, done } = iterator.next(); // 從頭開(kāi)始執(zhí)行 gen 函數(shù)暂雹,遇到 yield 'hello' 則停止執(zhí)行,此時(shí) value 為 'hello', done 為 false
let { value, done } = iterator.next(); // 從上次暫停處繼續(xù)執(zhí)行 gen 函數(shù)创夜,遇到 yield 'world' 則停止執(zhí)行杭跪,此時(shí) value 為 'world', done 為 false
let { value, done } = iterator.next(); // 從上次暫停處繼續(xù)執(zhí)行 gen 函數(shù),遇到 return 'ending' 則停止執(zhí)行驰吓,此時(shí) value 為 'ending', done 為 true
let { value, done } = iterator.next(); // gen 函數(shù)已經(jīng)執(zhí)行完畢了涧尿,不會(huì)再執(zhí)行了,此時(shí) value 為 undefined, done 為 true
let { value, done } = iterator.next(); // 以后再調(diào)用 iterator.next()檬贰,總是會(huì)返回 { value: undefined, done: true }

yield 表達(dá)式只能用在 Generator 函數(shù)中姑廉,用在其他函數(shù)中會(huì)報(bào)錯(cuò)

function* gen() {
var arr = [];
arr.forEach(item => {
yield item; // 報(bào)錯(cuò),yield 用在了 item => {} 函數(shù)中翁涤,該函數(shù)不是 Generator 函數(shù)
});
}

yield 表達(dá)式用在賦值表達(dá)式的右邊或者用作函數(shù)參數(shù)時(shí)不需要加括號(hào)桥言,用在其他表達(dá)式中,則必須加小括號(hào)葵礼,否則報(bào)錯(cuò)

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

通過(guò)調(diào)用 Generator 函數(shù)生成的 iterator 對(duì)象可以看作是該 Generator 類的實(shí)例

function* gen() {

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

API

Generator.prototype.next()

/* iterator.next() */

默認(rèn) yield 表達(dá)式的值為 undefined号阿,通過(guò) iterator.next(value) 可以賦予上一條 yield 表達(dá)式的值為 value

function* gen() {
let foo = yield 'hello'; // 第一次調(diào)用 iterator.next() 時(shí),執(zhí)行到 yield 'hello' 就停止鸳粉,此時(shí) let 賦值語(yǔ)句沒(méi)有完成扔涧,下一次調(diào)用 iterator.next() 時(shí)才完成 let foo 的賦值
console.log(foo); // undefined
let bar = yield 'world';
console.log(bar); // 100
}
let iterator = gen();
iterator.next(100); // 第一次調(diào)用 iterator.next() 時(shí)參數(shù)會(huì)被忽略,因?yàn)闆](méi)有上一條 yield 表達(dá)式
iterator.next(); // yield 'hello' 表達(dá)式的值為 undefined
iterator.next(100); // yield 'world' 表達(dá)式的值為 100

Generator.prototype.throw()

/* iterator.throw() */

  1. gen 函數(shù)中 throw 一個(gè)錯(cuò)誤届谈,如果沒(méi)有捕獲到該錯(cuò)誤扰柠,則整個(gè)腳本停止執(zhí)行

function* gen() {
yield 'hello';
throw 100;
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
}
let iterator = gen();
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.next(); // 從上次暫停的地方繼續(xù)執(zhí)行,遇見(jiàn) throw 100疼约,則結(jié)束 gen 函數(shù)的執(zhí)行,并且拋出一個(gè)錯(cuò)誤蝙泼,由于 gen 函數(shù)內(nèi)部和外部都沒(méi)有任何 catch 捕獲錯(cuò)誤程剥,所以整個(gè)腳本停止執(zhí)行
iterator.next(); // 這條語(yǔ)句不會(huì)執(zhí)行

  1. iterator.throw(); 的作用相當(dāng)于在上次暫停的地方開(kāi)始繼續(xù)執(zhí)行,然后立即 throw 一個(gè)錯(cuò)誤

function* gen() {
yield 'hello';
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
}
let iterator = gen();
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.throw(100); // 從上次暫停的地方繼續(xù)執(zhí)行汤踏,然后 throw 100织鲸,結(jié)束 gen 函數(shù)的執(zhí)行,并且拋出一個(gè)錯(cuò)誤溪胶,由于 gen 函數(shù)內(nèi)部和外部都沒(méi)有任何 catch 捕獲錯(cuò)誤搂擦,所以整個(gè)腳本停止執(zhí)行
iterator.next(); // 這條語(yǔ)句不會(huì)執(zhí)行

  1. gen 函數(shù)中 throw 的錯(cuò)誤,可以在 gen 函數(shù)體內(nèi)捕獲

function* gen() {
try {
yield 'hello';
throw 100;
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
// 以上三條語(yǔ)句很可能處于不同的 EventsLoop哗脖,但是拋出的錯(cuò)誤都可以被同一個(gè)詞法作用域的 catch 捕獲到
} catch (error) {
console.log('內(nèi)部捕獲到' + error);
}
yield 'javascript';
}
let iterator = gen();
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.next(); // 從上次暫停的地方繼續(xù)執(zhí)行瀑踢,遇見(jiàn) throw 100扳还,立即跳轉(zhuǎn)到 gen 函數(shù)體內(nèi)的 catch 語(yǔ)句,然后繼續(xù)執(zhí)行橱夭,直到遇見(jiàn)下一條 yield 語(yǔ)句或 return 語(yǔ)句氨距,返回 { value: 'javascript', done: false }
iterator.next(); // 返回 { value: undefined; done: true }

  1. iterator.throw() 拋出的錯(cuò)誤,可以在 gen 函數(shù)體內(nèi)捕獲

function* gen() {
try {
yield 'hello';
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
// 以上兩條語(yǔ)句很可能處于不同的 EventsLoop棘劣,但是拋出的錯(cuò)誤都可以被同一個(gè)詞法作用域的 catch 捕獲到
} catch (error) {
console.log('內(nèi)部捕獲到' + error);
}
yield 'javascript';
}
let iterator = gen();
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.throw(100); // 從上次暫停的地方繼續(xù)執(zhí)行俏让,然后 throw 100,立即跳轉(zhuǎn)到 gen 函數(shù)體內(nèi)的 catch 語(yǔ)句茬暇,然后繼續(xù)執(zhí)行首昔,直到遇見(jiàn)下一條 yield 語(yǔ)句或 return 語(yǔ)句,返回 { value: 'javascript', done: false }
iterator.next(); // 返回 { value: undefined; done: true }

  1. gen 函數(shù)中 throw 的錯(cuò)誤糙俗,可以在 gen 函數(shù)體外捕獲

function* gen() {
yield 'hello';
throw 100;
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
}
let iterator = gen();
try {
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.next(); // 從上次暫停的地方繼續(xù)執(zhí)行勒奇,然后 throw 100,由于 gen 函數(shù)體內(nèi)沒(méi)有 catch 語(yǔ)句臼节,因此立即結(jié)束 gen 函數(shù)的調(diào)用撬陵,并且跳轉(zhuǎn)到外部執(zhí)行 iterator.next() 所在 EventsLoop 的 catch 語(yǔ)句
iterator.next(); // 這條語(yǔ)句不會(huì)執(zhí)行
} catch (error) {
console.log('外部捕獲到' + error);
iterator.next(); // 返回 { value: undefined, done: true }
}

  1. iterator.throw() 拋出的錯(cuò)誤,可以在 gen 函數(shù)體外捕獲

function* gen() {
yield 'hello';
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
}
let iterator = gen();
try {
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.throw(100); // 從上次暫停的地方繼續(xù)執(zhí)行网缝,然后 throw 100巨税,由于 gen 函數(shù)體內(nèi)沒(méi)有 catch 語(yǔ)句,因此立即結(jié)束 gen 函數(shù)的調(diào)用粉臊,并且跳轉(zhuǎn)到外部執(zhí)行 iterator.throw() 所在 EventsLoop 的 catch 語(yǔ)句
iterator.next(); // 這條語(yǔ)句不會(huì)執(zhí)行
} catch (error) {
console.log('外部捕獲到' + error);
iterator.next(); // 返回 { value: undefined, done: true }
}

  1. 同時(shí)有內(nèi)部 try...catch 和 外部 try...catch草添,內(nèi)部?jī)?yōu)先捕獲

Generator.prototype.return()

/* iterator.return() */

調(diào)用 iterator.return(),相當(dāng)于立即 return

function* gen() {
yield 'hello';
yield 'world'; // 這條語(yǔ)句不會(huì)執(zhí)行
}
let iterator = gen();
iterator.next(); // 遇見(jiàn) yield 'hello' 停止執(zhí)行
iterator.return('ending'); // 從上次暫停的地方繼續(xù)執(zhí)行扼仲,然后 return 'ending'远寸,結(jié)束 gen 函數(shù)的調(diào)用,返回 { value: 'ending', done: true }

yield*

yield* iterable;

let foo = yield* gen(); // gen 函數(shù)如果有 return屠凶,則 return 的值為 yield* 表達(dá)式的值驰后,否則 yield* 表達(dá)式的值為 undefined

Generator 函數(shù) this

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

let iterator = gen(); // 這時(shí)還沒(méi)有執(zhí)行 gen 函數(shù)
iterator.next(); // 開(kāi)始執(zhí)行 gen 函數(shù),this 指向全局對(duì)象

let obj = {};
let iterator = gen.call(obj); // 這時(shí)還沒(méi)有執(zhí)行 gen 函數(shù)
iterator.next(); // 開(kāi)始執(zhí)行 gen 函數(shù)矗愧,this 指向 obj 對(duì)象

let obj = { gen };
let iterator = obj.gen(); // 這時(shí)還沒(méi)有執(zhí)行 gen 函數(shù)
iterator.next(); // 開(kāi)始執(zhí)行 gen 函數(shù)灶芝,this 指向 obj 對(duì)象

let iterator = new gen(); // 報(bào)錯(cuò),不能跟 new 命令一起使用

應(yīng)用

Generator 函數(shù)可以看作一個(gè)類似數(shù)組的數(shù)據(jù)結(jié)構(gòu)唉韭,數(shù)據(jù)結(jié)構(gòu)的成員就是 Generator 函數(shù)內(nèi)部 yield 命令生成的夜涕,該數(shù)據(jù)結(jié)構(gòu)作為 iterator 可以被遍歷

Generator 函數(shù)返回 iterator 對(duì)象,可以應(yīng)用 iterable 對(duì)象的場(chǎng)景属愤,都可以使用 iterator 對(duì)象

let [foo, bar] = gen(); // 解構(gòu)賦值
let arr = [...gen()]; // 擴(kuò)展運(yùn)算符
func(...gen()); // 擴(kuò)展運(yùn)算符
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 函數(shù)可以作為對(duì)象的 [Symbol.iterator] 屬性女器,使得該對(duì)象具有 Iterator 接口

let iterable = {

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

Genrator 函數(shù)可以封裝一個(gè)對(duì)象,使得該對(duì)象具有 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 函數(shù)可以用來(lái)進(jìn)行異步編程

Generator 函數(shù)異步編程

  1. 簡(jiǎn)介

function* gen() {
let f1 = yield asyncTask1();
let f2 = yield asyncTask2();
}
run(gen); // run 是流程管理函數(shù)住诸,可以自定義驾胆,也可以使用別人開(kāi)發(fā)好的

Generator 函數(shù)可以暫停執(zhí)行和恢復(fù)執(zhí)行涣澡,函數(shù)體內(nèi)外可以交換數(shù)據(jù)、交互處理錯(cuò)誤 (函數(shù)體內(nèi)的錯(cuò)誤可以在函數(shù)體內(nèi)或體外捕獲俏拱,函數(shù)體外可以 iterator.throw 拋出一個(gè)函數(shù)體內(nèi)的錯(cuò)誤)暑塑,因此可以用于異步編程
Generator 函數(shù)可以封裝異步任務(wù),異步任務(wù)的不同階段通過(guò) yield 來(lái)標(biāo)記锅必,通過(guò) iterator.next 方法分段執(zhí)行異步任務(wù)
Generator 函數(shù)需要傳遞到流程管理函數(shù)事格,調(diào)用流程管理函數(shù),則開(kāi)始自動(dòng)執(zhí)行 Generator 函數(shù)
Generator 函數(shù)自動(dòng)執(zhí)行的時(shí)候搞隐,遇到 yield 就暫停執(zhí)行驹愚,然后繼續(xù)執(zhí)行當(dāng)前 EventsLoop 剩余的同步代碼,等到 yield 后面的異步操作返回結(jié)果時(shí)繼續(xù)執(zhí)行 Generator 函數(shù)
Generator 函數(shù)在分段執(zhí)行的時(shí)候劣纲,不同段的代碼可能處于不同的 EventsLoop逢捺,iterator.next() 在哪個(gè) EventsLoop,那么該段代碼就在哪個(gè) EventsLoop
Generator 函數(shù)返回一個(gè) iterator 對(duì)象癞季,不管 Generator 函數(shù) return 什么劫瞳,該函數(shù)總是返回 iterator 對(duì)象,而且是先返回一個(gè) iterator 對(duì)象绷柒,然后通過(guò) iterator.next 才開(kāi)始執(zhí)行第一段函數(shù)志于,直到遇見(jiàn) yield 或函數(shù)結(jié)束
yield 后面的異步操作返回結(jié)果時(shí),可以在異步操作的回調(diào)函數(shù)中調(diào)用 iterator.next 來(lái)繼續(xù)執(zhí)行 Generator 函數(shù)废睦,也可以在異步操作返回的 promise 對(duì)象的 then 方法的回調(diào)函數(shù)中調(diào)用 iterator.next 來(lái)繼續(xù)執(zhí)行 Generator 函數(shù)伺绽,前者可以通過(guò)使用 Thunkify 模塊來(lái)實(shí)現(xiàn),后者可以通過(guò)使用 co 模塊來(lái)實(shí)現(xiàn)

  1. Thunkify 模塊

Thunk 函數(shù):將多參數(shù)的函數(shù)替換成一個(gè)只接受回調(diào)函數(shù)作為參數(shù)的單參數(shù)函數(shù)

$ npm install thunkify

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

// gen 函數(shù)封裝異步任務(wù)
function* gen() {
let f1 = yield readFileThunk('/etc/fstab'); // 返回一個(gè) Thunk 函數(shù)給外部嗜湃,這時(shí)還沒(méi)有開(kāi)始執(zhí)行異步操作奈应,等外部調(diào)用返回的 Thunk 函數(shù)時(shí),才開(kāi)始執(zhí)行異步操作
let f2 = yield readFileThunk('/etc/shells');
};

// 自定義流程管理函數(shù)购披,在異步操作的回調(diào)函數(shù)中調(diào)用 iterator.next杖挣,來(lái)恢復(fù) Generator 函數(shù)的執(zhí)行
function run(gen) {
let iterator = gen();
function next(err, data) {
if (err) {
// 異步操作失敗,處理異步錯(cuò)誤
doWithError(err);
} else {
// 異步操作成功刚陡,繼續(xù)執(zhí)行 gen 函數(shù)惩妇,并將異步操作返回的結(jié)果傳遞進(jìn) gen 函數(shù)
let result = iterator.next(data);
if (result.done) {
// 如果 gen 函數(shù)執(zhí)行完畢,則意味著異步任務(wù)已經(jīng)執(zhí)行完畢橘荠,并且已經(jīng)處理完異步操作的返回結(jié)果
return result.value;
} else {
// 如果 gen 函數(shù)沒(méi)有執(zhí)行完畢,則繼續(xù)執(zhí)行 gen 函數(shù)
// result.value 是內(nèi)部返回的 Thunk 函數(shù)郎逃,執(zhí)行該函數(shù)哥童,則開(kāi)始執(zhí)行異步操作,并且指定異步操作的回調(diào)函數(shù)為 next 函數(shù)褒翰,一般異步操作的回調(diào)函數(shù)都會(huì)包括參數(shù) (err, data)
result.value(next);
}
}
}
next();
}
run(gen);

3 co 模塊

推薦使用 co 模塊贮懈,不推薦使用 Thunkify 模塊匀泊,因?yàn)槭褂?Thunkify 模塊需要自己定義流程管理器

3.1 不使用 co 手動(dòng)進(jìn)行異步流程管理,在 promise 的 then 方法的回調(diào)函數(shù)中調(diào)用 iterator.next朵你,來(lái)恢復(fù) Generator 函數(shù)的執(zhí)行

function* gen() {
let f1 = yield promise1; // 遇見(jiàn) yield各聘,停止執(zhí)行 gen 函數(shù),然后繼續(xù)執(zhí)行 gen 函數(shù)外面的本輪 EventsLoop 剩下的代碼抡医。當(dāng) promise 對(duì)象狀態(tài)變?yōu)?Resolved 的時(shí)候躲因,繼續(xù)執(zhí)行 gen 函數(shù)
let f2 = yield promise2;
}

function run(gen) {
let iterator = gen();
function next(err, data) {
if (err) {
// 異步操作失敗,處理異步錯(cuò)誤
doWithError(err);
} else {
// 異步操作成功忌傻,繼續(xù)執(zhí)行 gen 函數(shù)大脉,并將異步操作返回的結(jié)果傳遞進(jìn) gen 函數(shù)
let result = iterator.next(data);
if (result.done) {
// 如果 gen 函數(shù)執(zhí)行完畢,則意味著異步任務(wù)已經(jīng)執(zhí)行完畢水孩,并且已經(jīng)處理完異步操作的返回結(jié)果
return result.value;
} else {
// 如果 gen 函數(shù)沒(méi)有執(zhí)行完畢镰矿,則繼續(xù)執(zhí)行 gen 函數(shù)
// result.value 是內(nèi)部返回的 promise 對(duì)象
result.value.then(function (data) {
// 如果異步操作成功了,會(huì)執(zhí)行 then 的回調(diào)函數(shù)俘种,在回調(diào)函數(shù)中調(diào)用 iterator.next()秤标,從而可以繼續(xù)執(zhí)行 gen 函數(shù)
next(null, data);
}).catch(function (err) {
// 如果異步操作失敗了,會(huì)執(zhí)行 catch 的回調(diào)函數(shù)
next(err);
});
}
}
}
next();
}
run(gen);

3.2 使用 co 自動(dòng)進(jìn)行異步流程管理

function* gen() {
let f1 = yield promise1; // 遇見(jiàn) yield宙刘,停止執(zhí)行 gen 函數(shù)苍姜,然后繼續(xù)執(zhí)行 gen 函數(shù)外面的本輪 EventsLoop 剩下的代碼。當(dāng) promise 對(duì)象狀態(tài)變?yōu)?Resolved 的時(shí)候荐类,繼續(xù)執(zhí)行 gen 函數(shù)
let f2 = yield promise2;
}

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

3.2.1 co 模塊的簡(jiǎn)介

co 函數(shù)返回一個(gè) promise 對(duì)象
當(dāng) gen 函數(shù) return (遇到函數(shù)結(jié)束標(biāo)記怖现,默認(rèn) return undefined),該 co 函數(shù)返回的 promise 對(duì)象狀態(tài)變?yōu)?Resolved玉罐,return 的值作為 resolve 回調(diào)函數(shù)的參數(shù)
當(dāng) gen 函數(shù) return 另一個(gè) promise 對(duì)象 anOtherPromise屈嗤,則 promise 對(duì)象的狀態(tài)與 anOtherPromise 對(duì)象的狀態(tài)綁定
當(dāng) gen 函數(shù) throw 拋出一個(gè)錯(cuò)誤,則進(jìn)行錯(cuò)誤處理機(jī)制 (見(jiàn)下面的錯(cuò)誤處理)
當(dāng) gen 函數(shù) yield 后面的 value 不是 promise 對(duì)象吊输,則相當(dāng)于 throw 拋出一個(gè)錯(cuò)誤
當(dāng) gen 函數(shù) yield 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Rejected 時(shí)饶号,相當(dāng)于 throw 拋出了一個(gè)錯(cuò)誤,導(dǎo)致 promise 對(duì)象狀態(tài)變?yōu)?Rejected 的 reject() 函數(shù)的參數(shù)作為 throw 后面的參數(shù)
當(dāng) gen 函數(shù) yield 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Resolved 時(shí)季蚂,恢復(fù) gen 函數(shù)的執(zhí)行茫船,yield 表達(dá)式的值為導(dǎo)致 promise 對(duì)象狀態(tài)變?yōu)?Resolved 的 resolve() 函數(shù)的參數(shù)

3.2.2 co 模塊的錯(cuò)誤處理

(1) 如果 gen 函數(shù)內(nèi)部有 try..catch,則內(nèi)部可以捕獲 throw 拋出的錯(cuò)誤

function* gen() {
try {
yield promise1;
throw '出錯(cuò)了扭屁!';
// 或者 yield notAPromiseValue;
// 或者 yield Promise.reject();
yield promise2; // 該行不會(huì)執(zhí)行
// 以上幾段代碼可能處于不同的 EventsLoop算谈,但是不同代碼段拋出的錯(cuò)誤可以被同一個(gè)詞法作用域的 catch 捕獲到,因?yàn)檫@里的 try...catch 并沒(méi)有執(zhí)行結(jié)束料滥,該 try...catch 分段執(zhí)行然眼,分散在好幾個(gè) EventsLoop 里
// 只要在一個(gè)代碼段執(zhí)行的時(shí)候拋出錯(cuò)誤,則立即跳轉(zhuǎn)到詞法作用域的 catch 語(yǔ)句葵腹,不會(huì)再執(zhí)行 try 語(yǔ)句中的剩余代碼段了
} catch (error) {
// 捕獲到 gen 函數(shù)內(nèi)部拋出的錯(cuò)誤高每,然后繼續(xù)執(zhí)行后面的語(yǔ)句屿岂,直到遇到 yield 或 return
console.log('內(nèi)部捕獲:' + error);
}
return yield promise3;
}
let promise = co(gen); // promise 的狀態(tài)變?yōu)?Resolved,gen 函數(shù)的 return 值為 promise 的 then 方法的回調(diào)函數(shù)的參數(shù)
promise.then(value => value);

(2) 如果 gen 函數(shù)內(nèi)部沒(méi)有 try...catch鲸匿,則 co 函數(shù)返回的 promise 對(duì)象狀態(tài)變?yōu)?Rejected爷怀,throw 的值作為 promise 的 catch 方法的回調(diào)函數(shù)的參數(shù)

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

(3) 如果 gen 函數(shù)內(nèi)部沒(méi)有 try...catch,并且 co 函數(shù)返回的 promise 對(duì)象沒(méi)有指定 catch 方法带欢,則拋出的錯(cuò)誤不會(huì)傳遞到外層代碼

let promise = co(gen);
promise.then(value => value); // promise 對(duì)象的狀態(tài)變?yōu)?Rejected

3.2.3 co 處理并發(fā)的異步操作

gen 函數(shù)中可以 yield [promise1, promise2]运授,作用相當(dāng)于 yield Promise.all[promise1, promise2];
等 promise1 和 promise2 全都變?yōu)?Resolved 的時(shí)候,才恢復(fù)執(zhí)行 gen 函數(shù)洪囤,yield 表達(dá)式的返回值為 [value1, value2]徒坡,
等 promise1 或 promise2 有一個(gè)變?yōu)?Rejected 的時(shí)候,相當(dāng)于 throw 一個(gè)錯(cuò)誤

async 函數(shù)

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

  1. 簡(jiǎn)介

async 函數(shù)就是 Generator 函數(shù)的語(yǔ)法糖

async 函數(shù)的執(zhí)行和普通函數(shù)一樣瘤缩,調(diào)用函數(shù)后自動(dòng)執(zhí)行
async 函數(shù)執(zhí)行的時(shí)候喇完,遇到 await 就暫停執(zhí)行,然后繼續(xù)執(zhí)行當(dāng)前 EventsLoop 剩余的同步代碼剥啤,等到 await 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Resolved 時(shí)繼續(xù)執(zhí)行 async 函數(shù)锦溪,函數(shù)內(nèi)的不同段的代碼在不同的 EventsLoop 執(zhí)行
async 函數(shù)返回一個(gè) promise 對(duì)象,不管 async 函數(shù) return 什么府怯,該函數(shù)總是返回 promise 對(duì)象
調(diào)用 async 函數(shù)時(shí)刻诊,先在當(dāng)前的 EventsLoop 中同步執(zhí)行第一段代碼,直到遇見(jiàn) await 或 return 時(shí)暫停執(zhí)行牺丙,然后返回一個(gè) promise 對(duì)象则涯,然后執(zhí)行當(dāng)前 EventsLoop 剩余的同步代碼

當(dāng) async 函數(shù) return (遇到函數(shù)結(jié)束標(biāo)記,默認(rèn) return undefined)冲簿,該 async 函數(shù)返回的 promise 對(duì)象狀態(tài)變?yōu)?Resolved粟判,return 的值作為 resolve 回調(diào)函數(shù)的參數(shù)
當(dāng) async 函數(shù) return 另一個(gè) promise 對(duì)象 anOtherPromise,則 promise 對(duì)象的狀態(tài)與 anOtherPromise 對(duì)象的狀態(tài)綁定
當(dāng) async 函數(shù) throw 拋出一個(gè)錯(cuò)誤峦剔,則進(jìn)行錯(cuò)誤處理機(jī)制 (見(jiàn)下面的錯(cuò)誤處理)
當(dāng) async 函數(shù) await 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Rejected 時(shí)档礁,相當(dāng)于 throw 拋出了一個(gè)錯(cuò)誤,導(dǎo)致 promise 對(duì)象狀態(tài)變?yōu)?Rejected 的 reject() 函數(shù)的參數(shù)作為 throw 后面的參數(shù)
當(dāng) async 函數(shù) await 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Resolved 時(shí)吝沫,恢復(fù) async 函數(shù)的執(zhí)行减牺,await 表達(dá)式的值為導(dǎo)致 promise 對(duì)象狀態(tài)變?yōu)?Resolved 的 resolve() 函數(shù)的參數(shù)
當(dāng) async 函數(shù) await 后面的 value 不是 promise 對(duì)象政勃,則相當(dāng)于 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 后面是基本類型的值疏魏,相當(dāng)于調(diào)用 Promise.resolve(value) 來(lái)轉(zhuǎn)換為狀態(tài)為 Resolved 的 promise 對(duì)象
當(dāng) await 后面的 promise 對(duì)象的狀態(tài)變?yōu)?Resolved 時(shí)吸占,async 函數(shù)會(huì)繼續(xù)執(zhí)行集峦,本質(zhì)上是因?yàn)樵?promise 對(duì)象狀態(tài)變?yōu)?Resolved 之后執(zhí)行的 then 的回調(diào)函數(shù)中顽决,調(diào)用了 iterator.next
由于 then 的回調(diào)函數(shù)會(huì)在 promise 對(duì)象狀態(tài)變?yōu)?Resolved 時(shí)的那輪 EventsLoop (在以上代碼中是當(dāng)前輪的 EventsLoop) 結(jié)束之后才能執(zhí)行灼舍,因此 async 函數(shù)中的第二段代碼會(huì)在當(dāng)前 EventsLoop 所有同步代碼都執(zhí)行之后再執(zhí)行

  1. 錯(cuò)誤處理

(1) 如果 async 函數(shù)內(nèi)部有 try..catch,則內(nèi)部可以捕獲 throw 拋出的錯(cuò)誤

async function func() {
try {
await promise1;
throw new Error('出錯(cuò)了!');
// 或者 await Promise.reject('出錯(cuò)了窘疮!'); // await 后面的 promise 對(duì)象狀態(tài)變?yōu)?Rejected,相當(dāng)于在此處 throw 一個(gè)錯(cuò)誤冀墨,本質(zhì)上是由流程管理器調(diào)用了 iterator.throw()
await promise2; // 該行語(yǔ)句不會(huì)執(zhí)行
// 以上幾段代碼可能處于不同的 EventsLoop闸衫,但是不同代碼段拋出的錯(cuò)誤可以被同一個(gè)詞法作用域的 catch 捕獲到,只要在一個(gè)代碼段執(zhí)行的時(shí)候拋出錯(cuò)誤诽嘉,則立即跳轉(zhuǎn)到詞法作用域的 catch 語(yǔ)句蔚出,不會(huì)再執(zhí)行 try 語(yǔ)句中的剩余代碼段了
} catch (error) {
// 捕獲到 async 函數(shù)內(nèi)部拋出的錯(cuò)誤,然后繼續(xù)執(zhí)行后面的語(yǔ)句虫腋,直到遇到 await 或 return
console.log('內(nèi)部捕獲到:' + error.message);
}
await promise3; // 會(huì)執(zhí)行該行語(yǔ)句
return;
}
let promise = func(); // 當(dāng) func 函數(shù)執(zhí)行結(jié)束骄酗,promise 的狀態(tài)會(huì)變?yōu)?Resolved

以上代碼中,async 函數(shù)中不同段的代碼處于不同的 EventsLoop 中悦冀,但是可以被相同詞法作用域的的同一個(gè) try...catch 語(yǔ)句捕獲趋翻,這點(diǎn)與 Generator 函數(shù)一樣

function* gen() {
try {
yield promise1;
throw new Error('出錯(cuò)了!');
// 或者外部調(diào)用 iterator.throw('出錯(cuò)了盒蟆!');
// 或者 yield Promise.reject('出錯(cuò)了踏烙!'); 本質(zhì)上是由流程管理器調(diào)用了 iterator.throw()
yield promise2;
} catch (error) {
// 捕獲到 Generator 函數(shù)內(nèi)部拋出的錯(cuò)誤,然后繼續(xù)執(zhí)行后面的語(yǔ)句历等,直到遇到 yield 或 return
console.log('內(nèi)部捕獲到:' + error.message);
}
yield promise3;
return;
}
let promise = co(gen); //當(dāng) gen 函數(shù)執(zhí)行結(jié)束讨惩,promise 的狀態(tài)會(huì)變?yōu)?Resolved

(2) 內(nèi)部捕獲的另一種辦法

async function func() {
await promise1.catch(error => {}); // await 后面的 promise 對(duì)象是 catch 返回的 promise 對(duì)象,由于 catch 的回調(diào)函數(shù)默認(rèn) return undefined寒屯,所以 catch 返回的 promise 對(duì)象的狀態(tài)肯定為 Resolved
await promise2.catch(error => {});
return;
}
let promise = func();
func 函數(shù)執(zhí)行結(jié)束荐捻,promise 的狀態(tài)會(huì)變?yōu)?Resolved

(3) 外部捕獲

async 函數(shù)內(nèi)部拋出錯(cuò)誤,如果 async 函數(shù)內(nèi)沒(méi)有 try...catch寡夹,則 async 函數(shù)立即停止執(zhí)行处面,函數(shù)返回的 promise 對(duì)象的狀態(tài)變?yōu)?Rejected
如果 async 函數(shù)返回的 promise 對(duì)象有 catch 語(yǔ)句,則執(zhí)行 catch 語(yǔ)句要出,如果沒(méi)有 catch 語(yǔ)句鸳君,則內(nèi)部拋出的錯(cuò)誤不會(huì)傳遞到外層代碼

async function func() {
await promise1;
throw new Error('出錯(cuò)了!');
// 或者 await Promise.reject();
await promise2; // 該行代碼不會(huì)執(zhí)行
}
let promise = func();
promise.catch(error => { console.log('外部捕獲到:' + error); }); // 如果沒(méi)有 catch患蹂,則 async 函數(shù)內(nèi)部的錯(cuò)誤不會(huì)傳遞到外層代碼

try {
async function func() {
await promise1;
throw new Error('出錯(cuò)了或颊!');
// 或者 await Promise.reject();
await promise2; // 該行代碼不會(huì)執(zhí)行
}
let promise = func();
} catch (error) {
console.log('外層代碼中捕獲到:' + error); // 不會(huì)執(zhí)行這里的代碼
}

異步編程

封裝單個(gè)異步操作

  1. 回調(diào)函數(shù)

// EventsLoop1
try {
asyncFunc(options, function (err, data) {
// EventsLoop2
if (err) {
// 不要 throw err,因?yàn)檫@里是 EventsLoop2传于,所以 EventsLoop1 中的 try...catch 無(wú)法捕獲到這里拋出的錯(cuò)誤
doWithError(err);
} else {
doWithData(data);
}
});
} catch (err) {
// 捕獲 asyncFunc 中代碼執(zhí)行時(shí)的同步錯(cuò)誤
}
// EventsLoop1
doSomething();

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

  1. Promise

// EventsLoop1
try {
let promise = asyncWrapperFunc(); // 該函數(shù)返回一個(gè) Promise 對(duì)象囱挑,封裝了異步操作
promise.then(function (data) {
// EventsLoop2 的末尾
doWithData(data);
}).catch(function (err) {
// 這里捕獲 Promise 參數(shù)函數(shù)中和前面的 then 的回調(diào)函數(shù)中同步執(zhí)行時(shí)拋出的錯(cuò)誤
doWithError(err);
});
// EventsLoop1
doSomething();
} catch (err) {
// 捕獲 asyncWrapperFunc 中代碼執(zhí)行時(shí)的同步錯(cuò)誤
}
// 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);

繼發(fā)執(zhí)行多個(gè)異步操作

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'];

// 回調(diào)函數(shù)嵌套

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 鏈?zhǔn)秸{(diào)用

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

并發(fā)執(zhí)行多個(gè)異步操作

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) => { // 調(diào)用匿名 async 函數(shù)的時(shí)候,返回 promise 對(duì)象
let data = await readp(file); // 執(zhí)行到 await 的時(shí)候沼溜,暫停執(zhí)行平挑,返回 promise 對(duì)象,執(zhí)行權(quán)交給相同 EventsLoop 的剩余同步代碼,也就是繼續(xù)調(diào)用 map 的參數(shù)函數(shù)通熄,并將 files 數(shù)組的下一個(gè)元素傳遞到 map 的參數(shù)函數(shù)中
doWithData(data);
});
await Promise.all(promises);
}
asyncParallel3().catch(doWithError);

asyncParallel1 asyncParallel2 asyncParallel3 都是并發(fā)進(jìn)行異步操作的請(qǐng)求 (readp(file))唆涝,但是對(duì)于異步操作返回的結(jié)果的處理方式不同 (doWithData(data))

處理方式對(duì)比:

asyncParallel1 同步并發(fā)處理異步操作的結(jié)果,等所有異步操作都接收到結(jié)果之后唇辨,才一次性地按順序進(jìn)行所有異步操作的結(jié)果的處理 doWithData(data)
asyncParallel2 異步繼發(fā)處理異步操作的結(jié)果廊酣,等第一個(gè)異步操作接收到結(jié)果之后,處理第一個(gè)異步操作的結(jié)果赏枚,即便第二個(gè)異步操作先接受到結(jié)果亡驰,也要等第一個(gè)異步操作處理完結(jié)果之后再處理第二個(gè)異步操作的結(jié)果
asyncParallel3 異步并發(fā)處理異步操作的結(jié)果,哪個(gè)異步操作先接收到結(jié)果饿幅,先處理哪個(gè)異步操作凡辱,不會(huì)等所有異步操作都接受到結(jié)果之后才開(kāi)始處理

處理速度對(duì)比:

asyncParallel1 < asyncParallel2 < asyncParallel3

asyncParallel1 最早需要等到所有異步操作都返回結(jié)果,才開(kāi)始進(jìn)行結(jié)果的處理
asyncParallel2 最先處理第一個(gè)異步操作的結(jié)果栗恩,然后處理第二個(gè)透乾,然后第三個(gè)...,只要第一個(gè)異步操作不是最后一個(gè)獲得結(jié)果的磕秤,就會(huì)比 asyncParallel1 快
asyncParallel3 誰(shuí)先獲得結(jié)果就先處理誰(shuí)续徽,不用等待其他異步操作獲得結(jié)果,速度最快

錯(cuò)誤處理對(duì)比:

asyncParallel1 只要有一個(gè)異步操作失敗亲澡,任何異步操作的結(jié)果都不會(huì)處理钦扭,就算其他異步操作成功也無(wú)法處理結(jié)果,最嚴(yán)格
asyncParallel2 某一個(gè)異步操作失敗床绪,則包括該異步操作在內(nèi)的所有還未進(jìn)行過(guò)結(jié)果處理的異步操作都不會(huì)再進(jìn)行結(jié)果處理了客情,已經(jīng)處理過(guò)結(jié)果的異步操作不受影響,比較嚴(yán)格
asyncParallel3 某個(gè)異步操作失敗癞己,只影響自己處理結(jié)果膀斋,不影響其他異步操作處理結(jié)果,最寬松

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 是函數(shù)名痹雅,{} 看做是 Point.prototype 對(duì)象
類名不存在變量提升仰担,必須先定義,后使用
類作為函數(shù)有 name 屬性绩社,屬性值為 class 關(guān)鍵字后面的字符串
所有類方法都是定義到 Point.prototype 對(duì)象中的摔蓝,所有類方法都是不可枚舉的
constructor 方法看做是 Point.prototype.constructor,指向 Point 函數(shù)本身
如果沒(méi)有指定 constructor愉耙,則默認(rèn)生成一個(gè)空的 constructor 函數(shù)
只能定義類的原型方法贮尉,無(wú)法定義原型屬性,所有實(shí)例屬性都可以在 constructor 函數(shù)中通過(guò) this 定義
類和模塊內(nèi)部默認(rèn)使用嚴(yán)格模式朴沿,不需要使用 'use strict' 顯示指定
Point 只能通過(guò) new Point() 調(diào)用猜谚,否則報(bào)錯(cuò) Point(); //報(bào)錯(cuò)
constructor 函數(shù)中可以使用 new.target败砂,new.target 指向 Point

類的屬性名可以采取表達(dá)式命名 methodName {},表達(dá)式的值為 String 或 Symbol 類型
可以定義 get 屬性和 set 屬性魏铅,該屬性是通過(guò) Object.defineProperty(Point.prototype, 'prop', descriptor} 來(lái)定義的
可以定義 Generator 函數(shù)
可以定義靜態(tài)方法昌犹,該靜態(tài)方法仍然不可枚舉,該方法屬于 Point 對(duì)象览芳,而不屬于實(shí)例

  1. Class 表達(dá)式

類表達(dá)式的值為函數(shù)對(duì)象祭隔,該函數(shù)對(duì)象只能用作構(gòu)造函數(shù)

const MyClass = class { // 匿名表達(dá)式

};
new MyClass();
MyClass(); // 報(bào)錯(cuò)

const MyClass = class Me { // 具名表達(dá)式,Me 只能用在類的內(nèi)部
getClassName() {
return Me.name; // Me
}
};
new Me(); // 報(bào)錯(cuò)路操,Me 只能用在函數(shù)內(nèi)部

立即執(zhí)行的 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 的實(shí)例,this 指向該實(shí)例
this.color = color; // 增強(qiáng)該實(shí)例
}
}

子類必須在 constructor 方法中調(diào)用 super 方法千贯,否則子類沒(méi)有自己的 this 對(duì)象
子類必須先調(diào)用 super 方法屯仗,然后再使用 this 關(guān)鍵字來(lái)增強(qiáng)對(duì)象,否則 ReferenceError
如果子類沒(méi)有 constructor 方法搔谴,默認(rèn)會(huì)創(chuàng)建一個(gè) constructor(...args) { super(...args); }

ES5 是先創(chuàng)建子類的實(shí)例魁袜,然后再在子類實(shí)例上借用父類的構(gòu)造函數(shù)來(lái)生成子類的屬性
function ColorPoint(x, y, color) {
Point.call(this, x, y);
this.color = color;
}
let foo = new ColorPoint(100, 200, 'red');

ES6 是先創(chuàng)建父類的實(shí)例,然后增強(qiáng)該實(shí)例敦第,最后返回這個(gè)實(shí)例
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 生成 Point 的實(shí)例峰弹,this 指向該實(shí)例
this.color = color; // 增強(qiáng)該實(shí)例
}
}
let bar = new ColorPoint(100, 200, 'red'); 其實(shí)返回的是增強(qiáng)之后的 Point 實(shí)例

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

3.2.1 super 作為函數(shù)使用

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

作為函數(shù)使用的時(shí)候,super() 只能用在子類的構(gòu)造函數(shù)中芜果,用在其他地方會(huì)報(bào)錯(cuò)
作為函數(shù)使用的時(shí)候鞠呈,super 指向父類的構(gòu)造函數(shù),但是會(huì)綁定子類的 this右钾,super() 相當(dāng)于調(diào)用 A.call(this)
子類調(diào)用 super()蚁吝,父類的構(gòu)造函數(shù)中的 new.target 會(huì)指向子類的構(gòu)造函數(shù)對(duì)象

3.2.2 super 作為對(duì)象使用

在普通方法中指向父類的原型對(duì)象

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

通過(guò) super.method() 來(lái)調(diào)用父類 prototype 中的方法時(shí),會(huì)綁定子類的 this舀射,super.method() 相當(dāng)于 A.prototype.method.call(this)
對(duì) super.property = value 為屬性賦值時(shí)窘茁,super 等于 this
通過(guò) super.property 讀取屬性值時(shí),super 等于父類的原型

在靜態(tài)方法中指向父類的函數(shù)對(duì)象

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); // 相當(dāng)于 A.foo(msg);
console.log('B' + msg);
}
}

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

3.2.3 對(duì)象方法中的 super 關(guān)鍵字

super 關(guān)鍵字可以用在對(duì)象 obj 的方法中,super 只能作為對(duì)象來(lái)使用邢羔,此時(shí) super 指向 obj 的原型對(duì)象

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

3.2.4 總結(jié)

super 作為對(duì)象使用驼抹,本質(zhì)上是指向詞法作用域中當(dāng)前上下文對(duì)象的 proto 對(duì)象

class B extends A {
hello() {
super.hello(); // super 定義在 B.prototype 對(duì)象的 hello 方法中,B.prototype.proto === A.prototype拜鹤,所以 super 指向 A.prototype
}
}

class B extends A {
static hello() {
super.hello(); // super 定義在 B 對(duì)象的 hello 方法中砂蔽,B.proto === A,所以 super 指向 A
}
}

var proto = {};
var obj = {
hello() {
super.hello(); // super 定義在 obj 對(duì)象的 hello 方法中署惯,obj.proto === proto左驾,所以 super 指向 proto
}
};
Object.setPrototypeOf(obj, proto);

3.3 子類可以繼承原生構(gòu)造函數(shù),在原生數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上定義自己的數(shù)據(jù)結(jié)構(gòu)

ES5 先創(chuàng)建子類的實(shí)例,然后借用父類的構(gòu)造函數(shù)來(lái)初始化子類的實(shí)例屬性诡右,原生構(gòu)造函數(shù)的內(nèi)部屬性不會(huì)被子類的實(shí)例初始化
Array 構(gòu)造函數(shù)有一個(gè)內(nèi)部屬性 [[DefineOwnProperty]]安岂,用來(lái)定義新屬性時(shí),更新 length 屬性帆吻,這個(gè)內(nèi)部屬性無(wú)法被子類獲取

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

ES6 先創(chuàng)建父類的實(shí)例域那,然后增強(qiáng)該實(shí)例作為子類的實(shí)例,因此子類的實(shí)例其實(shí)就是父類的實(shí)例猜煮,可以用來(lái)繼承原生構(gòu)造函數(shù)

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; // 必須返回一個(gè) descriptor 對(duì)象
}

修飾器可以用來(lái)修飾類和類的屬性
修飾器是一個(gè)函數(shù)次员,在編譯階段運(yùn)行修飾器函數(shù)
根據(jù)修飾器是修飾類還是修飾屬性,修飾器函數(shù)的參數(shù)會(huì)有所不同
如果同一個(gè)類或同一個(gè)屬性有多個(gè)修飾器王带,則該方法會(huì)先從外到內(nèi)進(jìn)入修飾器淑蔚,然后由內(nèi)向外執(zhí)行

修飾器外面可以封裝一層函數(shù)

@testable(true) // 相當(dāng)于編輯階段調(diào)用 var decorator = testable(true); decorator(A);
class A {

}

@testable(false)
class B {

}

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

模塊

  1. 簡(jiǎn)介

模塊就是文件,一個(gè) js 文件就是一個(gè)模塊
模塊自動(dòng)采用嚴(yán)格模式
每個(gè)模塊有自己的模塊作用域愕撰,模塊內(nèi)部的頂層變量只屬于該模塊的作用域刹衫,在該模塊外部不可見(jiàn)
在模塊之中,頂層的 this 指向 undefined
模塊通過(guò) export 可以暴露本模塊的內(nèi)部變量搞挣,模塊通過(guò) import 可以導(dǎo)入外部模塊的變量
export 和 import 命令都是在編譯時(shí)候執(zhí)行的带迟,靜態(tài)執(zhí)行的
import 代碼提升
import 和 export 命令都不能用在代碼塊中
JS 引擎在對(duì)腳本進(jìn)行靜態(tài)分析的時(shí)候,遇到 import 命令就會(huì)生成一個(gè)只讀引用囱桨。等腳本真正執(zhí)行的時(shí)仓犬,在根據(jù)這個(gè)只讀引用到被加載的模塊中取值

  1. API

2.1 export

  • 正確的方法:
    export var foo = 100;
    export function bar() {};
    export class baz {};
    export {foo, bar, baz}; // 與本模塊內(nèi)部的變量 foo bar baz 一一對(duì)應(yīng)
    export {foo as f};
    export default 表達(dá)式;
    export default 100;
    export default foo;
    export default function() {};
    export default function bar() {};
    export default class {};
    export default class baz {};

  • 錯(cuò)誤的方法:
    export 100;
    export foo;
    export default var foo = 100;

export 后面可以跟 變量、函數(shù)舍肠、類的聲明婶肩。
export 后面可以跟大括號(hào),大括號(hào)里面列舉對(duì)外接口貌夕,必須與模塊內(nèi)部的變量名名字相同律歼,一一對(duì)應(yīng)
一個(gè)模塊只能有一個(gè) export default
export default value 本質(zhì)上相當(dāng)于 export let default = value; 輸出了一個(gè) default 變量

2.2 import

import url; // 只執(zhí)行,不輸入啡专,執(zhí)行的時(shí)候是同步執(zhí)行
import {foo, bar, baz} from url; // foo, bar, baz 都是只讀的
import {foo as f} from url; // f 是只讀的
import * as obj from url; // obj 對(duì)象是 frozen 的险毁,Object.isFrozen(obj); 返回 true
import foo from url;
import foo, {bar, baz} from url; // foo 對(duì)應(yīng) default 變量,bar baz 對(duì)應(yīng) bar baz 變量

import 存在提升
import 語(yǔ)句同步執(zhí)行加載的模塊
{} 和 url 里面不能使用表達(dá)式
url 是相對(duì)路徑或者絕對(duì)路徑们童,可以省略 .js 擴(kuò)展名畔况,'./foo' '/www/admin/foo.js'
url 如果只是一個(gè)模塊名,需要提供配置文件說(shuō)明模塊的查找位置 'foo'
多次加載同一個(gè)模塊慧库,只執(zhí)行一次
模塊是 Singlon 模式跷跪,import {foo} from url; import {bar} from url; 相當(dāng)于 import {foo, bar} from url;
import foo from url 本質(zhì)上相當(dāng)于 import {default as foo} from url; 引入了 url 所在模塊的 default 變量

Node.js 的import命令
必須給出完整的文件名,不能省略.js后綴齐板,不管是url里還是package.json中的main字段吵瞻,都不能省略.js后綴
只支持加載本地模塊葛菇,不支持加載遠(yuǎn)程模塊
只支持相對(duì)路徑,不支持絕對(duì)路徑
可以使用模塊路徑

export import 復(fù)合寫法

在一個(gè)模塊中先輸入再輸出同一個(gè)模塊

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 的所有屬性和方法橡羞,但是會(huì)忽略 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 模塊是運(yùn)行時(shí)同步加載眯停,輸出的是一個(gè)值的復(fù)制
ES6 模塊是編譯時(shí)輸出接口,運(yùn)行時(shí)同步運(yùn)行模塊卿泽,輸出的是值的引用

CommonJS 模塊 require 命令第一次加載腳本時(shí)就會(huì)執(zhí)行整個(gè)腳本莺债,然后在內(nèi)存中生成一個(gè)對(duì)象

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

id 是模塊名,exports 是模塊輸出的各個(gè)接口签夭,loaded 表示該模塊的腳本是否執(zhí)行完畢
以后再次執(zhí)行 require 命令齐邦,也不會(huì)再次執(zhí)行該模塊,只會(huì)到 exports 屬性上去取值

ArrayBuffer

ArrayBuffer 對(duì)象:代表內(nèi)存中的一段二進(jìn)制數(shù)據(jù)第租,可以通過(guò)視圖進(jìn)行操作
視圖對(duì)象:讀和寫 ArrayBuffer 中存儲(chǔ)的二進(jìn)制數(shù)據(jù)措拇,不同類型的視圖按照不同的規(guī)則解釋二進(jìn)制數(shù)據(jù)
視圖對(duì)象部署了數(shù)組接口,可以用數(shù)組的方法操作內(nèi)存煌妈,視圖對(duì)象是類數(shù)組對(duì)象
TypedArray 視圖對(duì)象
DataView 視圖對(duì)象

  1. ArrayBuffer

var buf = new ArrayBuffer(n); // n 是字節(jié)數(shù)

API:

ArrayBuffer.prototype.byteLength // 字節(jié)數(shù)
ArrayBuffer.prototype.slice(); // 將 buf 的一部分內(nèi)存區(qū)域的內(nèi)容復(fù)制生成一份新的內(nèi)存,返回一個(gè)新的 ArrayBuffer 對(duì)象
ArrayBuffer.isView(view); // view 如果是視圖對(duì)象宣羊,返回 true璧诵,否則返回 false

  1. TypedArray (一共有 9 種類型,這里用 TypedArray 來(lái)統(tǒng)一代表)

很像數(shù)組仇冯,有 length 屬性之宿,用 [] 獲取單個(gè)元素,數(shù)組的方法都可以使用苛坚,數(shù)組成員都是同一個(gè)數(shù)據(jù)類型
部署了 Iterator 接口

var view = new TypedArray(buffer, byteOffset, length); // byteOffset 是偏移量比被,length 是數(shù)組長(zhǎng)度
var view = new TypedArray(n); // n 數(shù)組長(zhǎng)度,數(shù)組元素的值默認(rèn)為 0
var view = new TypedArray(iterable); // 參數(shù)可以是任何具有 Iterator 接口的對(duì)象泼舱,包括普通數(shù)組等缀,生成新的內(nèi)存
var view = new TypedArray(typedArray); // 參數(shù)可以是另一個(gè) TypedArray 實(shí)例,生成新的內(nèi)存

以上第 2 3 4 種方法創(chuàng)建視圖的同時(shí)娇昙,會(huì)自動(dòng)生成底層的 ArrayBuffer 對(duì)象

API:

TypedArray.prototype.buffer // 指向 ArrayBuffer 對(duì)象
TypedArray.prototype.byteLength // 字節(jié)數(shù)
TypedArray.prototype.byteOffset // 偏移量
TypedArray.prototype.length // 數(shù)組長(zhǎng)度
TypedArray.prototype.set(); // 復(fù)制內(nèi)存
TypedArray.prototype.subarray(); // 生成新的視圖尺迂,不生成新的內(nèi)存
TypedArray.prototype.slice(); // 生成新的視圖,不生成新的內(nèi)存
TypedArray.of(); // 類似 Array.of();
TypedArray.from(iterable); // 類似 Array.from();

  1. DataView

支持設(shè)定字節(jié)序

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

API:

DataView.prototype.buffer
DataView.prototype.byteLength
DataView.prototype.byteOffset
DataView.prototype.getInt8(byteIndex, isLittle); // byteIndex 是字節(jié)序號(hào)冒掌,isLittle true 是小端字節(jié)序噪裕,否則是大端字節(jié)序
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. 字節(jié)序:

對(duì)于多個(gè)字節(jié)存儲(chǔ)內(nèi)容來(lái)說(shuō),存在著字節(jié)序的問(wèn)題

16 位整數(shù) 255 (0x00FF) 存儲(chǔ)在內(nèi)存中股毫,小端字節(jié)序存儲(chǔ)為 FF00膳音,大端字節(jié)序存儲(chǔ)為 00FF
小端字節(jié)序順序相反,大端字節(jié)序順序相同
本機(jī)一般都是小端字節(jié)序铃诬,網(wǎng)絡(luò)是大端字節(jié)序
TypedArray 與本機(jī)相同祭陷,DataView 默認(rèn)大端字節(jié)序苍凛,可以指定大端或小端

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市颗胡,隨后出現(xiàn)的幾起案子毫深,更是在濱河造成了極大的恐慌,老刑警劉巖毒姨,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哑蔫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡弧呐,警方通過(guò)查閱死者的電腦和手機(jī)闸迷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俘枫,“玉大人腥沽,你說(shuō)我怎么就攤上這事○剑” “怎么了今阳?”我有些...
    開(kāi)封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)茅信。 經(jīng)常有香客問(wèn)我盾舌,道長(zhǎng),這世上最難降的妖魔是什么蘸鲸? 我笑而不...
    開(kāi)封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任妖谴,我火速辦了婚禮,結(jié)果婚禮上酌摇,老公的妹妹穿的比我還像新娘膝舅。我一直安慰自己,他們只是感情好窑多,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布仍稀。 她就那樣靜靜地躺著,像睡著了一般埂息。 火紅的嫁衣襯著肌膚如雪琳轿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天耿芹,我揣著相機(jī)與錄音崭篡,去河邊找鬼。 笑死吧秕,一個(gè)胖子當(dāng)著我的面吹牛琉闪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砸彬,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颠毙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斯入!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蛀蜜,我...
    開(kāi)封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刻两,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后滴某,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體磅摹,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年霎奢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了户誓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幕侠,死狀恐怖帝美,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晤硕,我是刑警寧澤悼潭,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站舞箍,受9級(jí)特大地震影響舰褪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜创译,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一抵知、第九天 我趴在偏房一處隱蔽的房頂上張望墙基。 院中可真熱鬧软族,春花似錦、人聲如沸残制。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)初茶。三九已至颗祝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恼布,已是汗流浹背螺戳。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留折汞,地道東北人倔幼。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爽待,于是被迫代替她去往敵國(guó)和親损同。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翩腐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容