七乙埃、Iterator
迭代器是一種接口、是一種機(jī)制锯岖。
為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問(wèn)機(jī)制介袜。任何數(shù)據(jù)結(jié)構(gòu)只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)出吹。
Iterator 的作用有三個(gè):
- 為各種數(shù)據(jù)結(jié)構(gòu)遇伞,提供一個(gè)統(tǒng)一的、簡(jiǎn)便的訪問(wèn)接口捶牢;
- 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列鸠珠;
- 主要供
for...of
消費(fèi)。
Iterator本質(zhì)上秋麸,就是一個(gè)指針對(duì)象渐排。
過(guò)程是這樣的:
(1)創(chuàng)建一個(gè)指針對(duì)象,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置灸蟆。
(2)第一次調(diào)用指針對(duì)象的next
方法驯耻,可以將指針指向數(shù)據(jù)結(jié)構(gòu)的第一個(gè)成員。
(3)第二次調(diào)用指針對(duì)象的next
方法炒考,指針就指向數(shù)據(jù)結(jié)構(gòu)的第二個(gè)成員可缚。
(4)不斷調(diào)用指針對(duì)象的next
方法,直到它指向數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置斋枢。
普通函數(shù)實(shí)現(xiàn)Iterator城看。
function myIter(obj){
let i = 0;
return {
next(){
let done = (i>=obj.length);
let value = !done ? obj[i++] : undefined;
return {
value,
done,
}
}
}
}
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)如下:Array、Map杏慰、Set弄喘、String惩激、函數(shù)的arguments對(duì)象捂敌、NodeList對(duì)象惩坑。
下面的例子是數(shù)組的Symbol.iterator
屬性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
下面是另一個(gè)類似數(shù)組的對(duì)象調(diào)用數(shù)組的Symbol.iterator
方法的例子朝扼。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
注意赃阀,普通對(duì)象部署數(shù)組的Symbol.iterator
方法,并無(wú)效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
字符串是一個(gè)類似數(shù)組的對(duì)象榛斯,也原生具有 Iterator 接口观游。
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
八、Generator
- 基本概念驮俗。
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案懂缕,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。
執(zhí)行 Generator 函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象王凑,也就是說(shuō)搪柑,Generator 函數(shù)還是一個(gè)遍歷器對(duì)象生成函數(shù)。返回的遍歷器對(duì)象索烹,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個(gè)狀態(tài)工碾。
跟普通函數(shù)的區(qū)別:
-
function
關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào); - 函數(shù)體內(nèi)部使用
yield
表達(dá)式百姓,定義不同的內(nèi)部狀態(tài)渊额。 - Generator函數(shù)不能跟new一起使用,會(huì)報(bào)錯(cuò)垒拢。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代碼定義了一個(gè) Generator 函數(shù)helloWorldGenerator
旬迹,它內(nèi)部有兩個(gè)yield
表達(dá)式(hello
和world
),即該函數(shù)有三個(gè)狀態(tài):hello子库,world 和 return 語(yǔ)句(結(jié)束執(zhí)行)舱权。
調(diào)用 Generator 函數(shù)后矗晃,該函數(shù)并不執(zhí)行仑嗅,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象张症,也就是遍歷器對(duì)象仓技。
下一步,必須調(diào)用遍歷器對(duì)象的next
方法俗他,使得指針移向下一個(gè)狀態(tài)脖捻。也就是說(shuō),每次調(diào)用next
方法兆衅,內(nèi)部指針就從函數(shù)頭部或上一次停下來(lái)的地方開(kāi)始執(zhí)行地沮,直到遇到下一個(gè)yield
表達(dá)式(或return
語(yǔ)句)為止。換言之羡亩,Generator 函數(shù)是分段執(zhí)行的摩疑,yield
表達(dá)式是暫停執(zhí)行的標(biāo)記,而next
方法可以恢復(fù)執(zhí)行畏铆。
ES6 沒(méi)有規(guī)定雷袋,function
關(guān)鍵字與函數(shù)名之間的星號(hào),寫(xiě)在哪個(gè)位置辞居。這導(dǎo)致下面的寫(xiě)法都能通過(guò)楷怒。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
- yield 表達(dá)式蛋勺。
由于 Generator 函數(shù)返回的遍歷器對(duì)象,只有調(diào)用next
方法才會(huì)遍歷下一個(gè)內(nèi)部狀態(tài)鸠删,所以其實(shí)提供了一種可以暫停執(zhí)行的函數(shù)抱完。yield
表達(dá)式就是暫停標(biāo)志。
遍歷器對(duì)象的next
方法的運(yùn)行邏輯如下冶共。
(1)遇到yield
表達(dá)式乾蛤,就暫停執(zhí)行后面的操作,并將緊跟在yield
后面的那個(gè)表達(dá)式的值捅僵,作為返回的對(duì)象的value
屬性值家卖。
(2)下一次調(diào)用next
方法時(shí),再繼續(xù)往下執(zhí)行庙楚,直到遇到下一個(gè)yield
表達(dá)式上荡。
(3)如果沒(méi)有再遇到新的yield
表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束馒闷,直到return
語(yǔ)句為止酪捡,并將return
語(yǔ)句后面的表達(dá)式的值,作為返回的對(duì)象的value
屬性值纳账。
(4)如果該函數(shù)沒(méi)有return
語(yǔ)句逛薇,則返回的對(duì)象的value
屬性值為undefined
。
yield表達(dá)式與return語(yǔ)句的相同之處:
都能返回緊跟在語(yǔ)句后面的那個(gè)表達(dá)式的值疏虫。
yield表達(dá)式與return語(yǔ)句的不同之處:
每次遇到yield
永罚,函數(shù)暫停執(zhí)行,下一次再?gòu)脑撐恢美^續(xù)向后執(zhí)行卧秘,而return
語(yǔ)句不具備位置記憶的功能呢袱。一個(gè)函數(shù)里面,只能執(zhí)行一次(或者說(shuō)一個(gè))return
語(yǔ)句翅敌,但是可以執(zhí)行多次(或者說(shuō)多個(gè))yield
表達(dá)式羞福。正常函數(shù)只能返回一個(gè)值,因?yàn)橹荒軋?zhí)行一次return
蚯涮;Generator 函數(shù)可以返回一系列的值治专,因?yàn)榭梢杂腥我舛鄠€(gè)yield
。
yield
表達(dá)式只能用在 Generator 函數(shù)里面遭顶,用在其他地方都會(huì)報(bào)錯(cuò)张峰。
另外,yield
表達(dá)式如果用在另一個(gè)表達(dá)式之中液肌,必須放在圓括號(hào)里面挟炬。
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield 123)); // OK
- 與Iterator接口的關(guān)系。
由于 Generator 函數(shù)就是遍歷器生成函數(shù),因此可以把 Generator 賦值給對(duì)象的Symbol.iterator
屬性谤祖,從而使得該對(duì)象具有 Iterator 接口婿滓。
Object.prototype[Symbol.iterator] = function* (){
for(let i in this){
yield this[i];
}
}
//--------------
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
- next方法的參數(shù)。
yield
表達(dá)式本身沒(méi)有返回值粥喜,或者說(shuō)總是返回undefined
凸主。next
方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield
表達(dá)式的返回值额湘。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
Generator 函數(shù)從暫停狀態(tài)到恢復(fù)運(yùn)行卿吐,它的上下文狀態(tài)(context)是不變的。通過(guò)next
方法的參數(shù)锋华,就有辦法在 Generator 函數(shù)開(kāi)始運(yùn)行之后嗡官,繼續(xù)向函數(shù)體內(nèi)部注入值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
- for...of 循環(huán)毯焕。
for...of
循環(huán)可以自動(dòng)遍歷 Generator 函數(shù)時(shí)生成的Iterator
對(duì)象衍腥,且此時(shí)不再需要調(diào)用next
方法。
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
function* fibonacci() {
let [prev, curr] = [1, 1];
while(true){
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 10000000) break;
console.log(n);
}
- Generator.prototype.return()
Generator 函數(shù)返回的遍歷器對(duì)象纳猫,還有一個(gè)return
方法婆咸,可以返回給定的值,并且終結(jié)遍歷 Generator 函數(shù)芜辕。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
- yield*
如果在 Generator 函數(shù)內(nèi)部尚骄,調(diào)用另一個(gè) Generator 函數(shù),默認(rèn)情況下是沒(méi)有效果的侵续。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
foo
和bar
都是 Generator 函數(shù)倔丈,在bar
里面調(diào)用foo
,是不會(huì)有效果的询兴。
這個(gè)就需要用到yield*
表達(dá)式乃沙,用來(lái)在一個(gè) Generator 函數(shù)里面執(zhí)行另一個(gè) Generator 函數(shù)起趾。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
再來(lái)看一個(gè)對(duì)比的例子诗舰。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一個(gè)遍歷器對(duì)象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
上面例子中,outer2
使用了yield*
训裆,outer1
沒(méi)使用眶根。結(jié)果就是,outer1
返回一個(gè)遍歷器對(duì)象边琉,outer2
返回該遍歷器對(duì)象的內(nèi)部值属百。
從語(yǔ)法角度看,如果yield
表達(dá)式后面跟的是一個(gè)遍歷器對(duì)象变姨,需要在yield
表達(dá)式后面加上星號(hào)族扰,表明它返回的是一個(gè)遍歷器對(duì)象。這被稱為yield*
表達(dá)式。
- 作為對(duì)象屬性的 Generator 函數(shù)
如果一個(gè)對(duì)象的屬性是 Generator 函數(shù)渔呵,可以簡(jiǎn)寫(xiě)成下面的形式怒竿。
let obj = {
* myGeneratorMethod() {
···
}
};
九、async函數(shù)
- 含義扩氢。
ES2017 標(biāo)準(zhǔn)引入了 async 函數(shù)耕驰,使得異步操作變得更加方便。async 函數(shù)是 Generator 函數(shù)的語(yǔ)法糖录豺。
async
函數(shù)使用時(shí)就是將 Generator 函數(shù)的星號(hào)(*
)替換成async
朦肘,將yield
替換成await
,僅此而已双饥。
async
函數(shù)對(duì) Generator 函數(shù)的區(qū)別:
(1)內(nèi)置執(zhí)行器媒抠。
Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,而async
函數(shù)自帶執(zhí)行器咏花。也就是說(shuō)领舰,async
函數(shù)的執(zhí)行,與普通函數(shù)一模一樣迟螺,只要一行冲秽。
(2)更好的語(yǔ)義。
async
和await
矩父,比起星號(hào)和yield
锉桑,語(yǔ)義更清楚了。async
表示函數(shù)里有異步操作窍株,await
表示緊跟在后面的表達(dá)式需要等待結(jié)果民轴。
(3)正常情況下,await
命令后面是一個(gè) Promise 對(duì)象球订。如果不是后裸,會(huì)被轉(zhuǎn)成一個(gè)立即resolve
的 Promise 對(duì)象。
(4)返回值是 Promise冒滩。
async
函數(shù)的返回值是 Promise 對(duì)象微驶,這比 Generator 函數(shù)的返回值是 Iterator 對(duì)象方便多了。你可以用then
方法指定下一步的操作开睡。
進(jìn)一步說(shuō)因苹,async
函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè) Promise 對(duì)象篇恒,而await
命令就是內(nèi)部then
命令的語(yǔ)法糖扶檐。 - 錯(cuò)誤處理。
如果await
后面的異步操作出錯(cuò)胁艰,那么等同于async
函數(shù)返回的 Promise 對(duì)象被reject
款筑。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出錯(cuò)了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯(cuò)了
上面代碼中智蝠,async
函數(shù)f
執(zhí)行后,await
后面的 Promise 對(duì)象會(huì)拋出一個(gè)錯(cuò)誤對(duì)象奈梳,導(dǎo)致catch
方法的回調(diào)函數(shù)被調(diào)用寻咒,它的參數(shù)就是拋出的錯(cuò)誤對(duì)象。具體的執(zhí)行機(jī)制颈嚼,可以參考后文的“async 函數(shù)的實(shí)現(xiàn)原理”毛秘。
防止出錯(cuò)的方法,也是將其放在try...catch
代碼塊之中阻课。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯(cuò)了');
});
} catch(e) {
}
return await('hello world');
}
如果有多個(gè)await
命令叫挟,可以統(tǒng)一放在try...catch
結(jié)構(gòu)中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
- 應(yīng)用
var fn = function (time) {
console.log("開(kāi)始處理異步");
setTimeout(function () {
console.log(time);
console.log("異步處理完成");
iter.next();
}, time);
};
function* g(){
console.log("start");
yield fn(3000)
yield fn(500)
yield fn(1000)
console.log("end");
}
let iter = g();
iter.next();
下面是async函數(shù)的寫(xiě)法限煞。
var fn = function (time) {
return new Promise(function (resolve, reject) {
console.log("開(kāi)始處理異步");
setTimeout(function () {
resolve();
console.log(time);
console.log("異步處理完成");
}, time);
})
};
var start = async function () {
// 在這里使用起來(lái)就像同步代碼那樣直觀
console.log('start');
await fn(3000);
await fn(500);
await fn(1000);
console.log('end');
};
start();
十抹恳、Class
- 用法
class跟let、const一樣:不存在變量提升署驻、不能重復(fù)聲明奋献。
ES6 提供了更接近傳統(tǒng)語(yǔ)言的寫(xiě)法,引入了 Class(類)這個(gè)概念旺上,作為對(duì)象的模板瓶蚂。通過(guò)class
關(guān)鍵字,可以定義類宣吱。
ES6 的class
可以看作只是一個(gè)語(yǔ)法糖窃这,它的絕大部分功能,ES5 都可以做到征候,新的class
寫(xiě)法只是讓對(duì)象原型的寫(xiě)法更加清晰杭攻、更像面向?qū)ο缶幊痰恼Z(yǔ)法而已。
//es5
function Fn(x, y) {
this.x = x;
this.y = y;
}
Fn.prototype.add = function () {
return this.x + this.y;
};
//等價(jià)于
//es6
class Fn{
constructor(x,y){
this.x = x;
this.y = y;
}
add(){
return this.x + this.y;
}
}
var F = new Fn(1, 2);
console.log(F.add()) //3
構(gòu)造函數(shù)的prototype
屬性疤坝,在 ES6 的“類”上面繼續(xù)存在兆解。事實(shí)上,類的所有方法都定義在類的prototype
屬性上面跑揉。
class Fn {
constructor() {
// ...
}
add() {
// ...
}
sub() {
// ...
}
}
// 等同于
Fn.prototype = {
constructor() {},
add() {},
sub() {},
};
類的內(nèi)部所有定義的方法锅睛,都是不可枚舉的(non-enumerable),這與es5不同畔裕。
//es5
var Fn = function (x, y) {
// ...
};
Fn.prototype.add = function() {
// ...
};
Object.keys(Fn.prototype)
// ["add"]
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
//es6
class Fn {
constructor(x, y) {
// ...
}
add() {
// ...
}
}
Object.keys(Fn.prototype)
// []
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
- 嚴(yán)格模式
類和模塊的內(nèi)部衣撬,默認(rèn)就是嚴(yán)格模式乖订,所以不需要使用use strict
指定運(yùn)行模式扮饶。只要你的代碼寫(xiě)在類或模塊之中,就只有嚴(yán)格模式可用乍构。
考慮到未來(lái)所有的代碼甜无,其實(shí)都是運(yùn)行在模塊之中扛点,所以 ES6 實(shí)際上把整個(gè)語(yǔ)言升級(jí)到了嚴(yán)格模式。 - constructor
constructor
方法是類的默認(rèn)方法岂丘,通過(guò)new
命令生成對(duì)象實(shí)例時(shí)陵究,自動(dòng)調(diào)用該方法。一個(gè)類必須有constructor
方法奥帘,如果沒(méi)有顯式定義铜邮,一個(gè)空的constructor
方法會(huì)被默認(rèn)添加。
class Fn {
}
// 等同于
class Fn {
constructor() {}
}
constructor
方法默認(rèn)返回實(shí)例對(duì)象(即this
)寨蹋,完全可以指定返回另外一個(gè)對(duì)象松蒜。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函數(shù)返回一個(gè)全新的對(duì)象,結(jié)果導(dǎo)致實(shí)例對(duì)象不是Foo類的實(shí)例已旧。
- 類必須使用new調(diào)用
類必須使用new
調(diào)用秸苗,否則會(huì)報(bào)錯(cuò)。這是它跟普通構(gòu)造函數(shù)的一個(gè)主要區(qū)別运褪,后者不用new
也可以執(zhí)行惊楼。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
- Class表達(dá)式
與函數(shù)一樣,類也可以使用表達(dá)式的形式定義秸讹。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代碼使用表達(dá)式定義了一個(gè)類檀咙。需要注意的是,這個(gè)類的名字是MyClass
而不是Me
璃诀,Me
只在 Class 的內(nèi)部代碼可用攀芯,指代當(dāng)前類。
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
如果類的內(nèi)部沒(méi)用到的話文虏,可以省略Me
侣诺,也就是可以寫(xiě)成下面的形式。
const MyClass = class { /* ... */ };
采用 Class 表達(dá)式氧秘,可以寫(xiě)出立即執(zhí)行的 Class年鸳。
let Person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('張三');
Person.sayName(); // "張三"
上面代碼中,person
是一個(gè)立即執(zhí)行的類的實(shí)例丸相。
- 私有方法和私有屬性
私有方法/私有屬性是常見(jiàn)需求搔确,但 ES6 不提供,只能通過(guò)變通方法模擬實(shí)現(xiàn)灭忠。
通常是在命名上加以區(qū)別膳算。
class Fn {
// 公有方法
foo () {
//....
}
// 假裝是私有方法(其實(shí)外部還是可以訪問(wèn))
_bar() {
//....
}
}
- 原型的屬性
class定義類時(shí),只能在constructor里定義屬性弛作,在其他位置會(huì)報(bào)錯(cuò)涕蜂。
如果需要在原型上定義方法可以使用:
- Fn.prototype.prop = value;
- Object.getPrototypeOf()獲取原型,再來(lái)擴(kuò)展
- Object.assign(Fn.prototype,{在這里面寫(xiě)擴(kuò)展的屬性或者方法})
- Class的靜態(tài)方法
類相當(dāng)于實(shí)例的原型映琳,所有在類中定義的方法机隙,都會(huì)被實(shí)例繼承蜘拉。
如果在一個(gè)方法前,加上static
關(guān)鍵字有鹿,就表示該方法不會(huì)被實(shí)例繼承旭旭,而是直接通過(guò)類來(lái)調(diào)用,這就稱為“靜態(tài)方法”葱跋。
ES6 明確規(guī)定持寄,Class 內(nèi)部只有靜態(tài)方法,沒(méi)有靜態(tài)屬性娱俺。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
//靜態(tài)屬性只能手動(dòng)設(shè)置
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
- get际看、set
存值函數(shù)和取值函數(shù)
class Fn{
constructor(){
this.arr = []
}
get bar(){
return this.arr;
}
set bar(value){
this.arr.push(value)
}
}
let obj = new Fn();
obj.menu = 1;
obj.menu = 2;
console.log(obj.menu)//[1,2]
console.log(obj.arr)//[1,2]
- 繼承
class Fn {
}
class Fn2 extends Fn {
}
子類必須在constructor
方法中調(diào)用super
方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)矢否。這是因?yàn)樽宇悰](méi)有自己的this
對(duì)象仲闽,而是繼承父類的this
對(duì)象,然后對(duì)其進(jìn)行加工僵朗。如果不調(diào)用super
方法赖欣,子類就得不到this
對(duì)象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
// super()//必須調(diào)用
}
}
let cp = new ColorPoint(); // ReferenceError
父類的靜態(tài)方法也會(huì)被繼承验庙。
- Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用來(lái)從子類上獲取父類顶吮。
Object.getPrototypeOf(Fn2) === Fn
// true
因此,可以使用這個(gè)方法判斷粪薛,一個(gè)類是否繼承了另一個(gè)類悴了。
- super關(guān)鍵字
super
這個(gè)關(guān)鍵字,既可以當(dāng)作函數(shù)使用违寿,也可以當(dāng)作對(duì)象使用湃交。在這兩種情況下,它的用法完全不同藤巢。
第一種情況搞莺,super
作為函數(shù)調(diào)用時(shí),代表父類的構(gòu)造函數(shù)掂咒。ES6 要求才沧,子類的構(gòu)造函數(shù)必須執(zhí)行一次super
函數(shù)。
作為函數(shù)時(shí)绍刮,super()
只能用在子類的構(gòu)造函數(shù)之中温圆,用在其他地方就會(huì)報(bào)錯(cuò)。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代碼中孩革,子類B
的構(gòu)造函數(shù)之中的super()
岁歉,代表調(diào)用父類的構(gòu)造函數(shù)。這是必須的嫉戚,否則 JavaScript 引擎會(huì)報(bào)錯(cuò)刨裆。
注意澈圈,super
雖然代表了父類A
的構(gòu)造函數(shù)彬檀,但是返回的是子類B
的實(shí)例帆啃,即super
內(nèi)部的this
指的是B
,因此super()
在這里相當(dāng)于A.prototype.constructor.call(this)
窍帝。
第二種情況努潘,super
作為對(duì)象時(shí),在普通方法中坤学,指向父類的原型對(duì)象疯坤;在靜態(tài)方法中,指向父類深浮。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代碼中压怠,子類B
當(dāng)中的super.p()
,就是將super
當(dāng)作一個(gè)對(duì)象使用飞苇。這時(shí)菌瘫,super
在普通方法之中,指向A.prototype
布卡,所以super.p()
就相當(dāng)于A.prototype.p()
雨让。
由于this
指向子類,所以如果通過(guò)super
對(duì)某個(gè)屬性賦值忿等,這時(shí)super
就是this
栖忠,賦值的屬性會(huì)變成子類實(shí)例的屬性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上面代碼中贸街,super.x
賦值為3
庵寞,這時(shí)等同于對(duì)this.x
賦值為3
。而當(dāng)讀取super.x
的時(shí)候薛匪,讀的是A.prototype.x
皇帮,所以返回undefined
。
十一蛋辈、Module
- export命令
模塊功能主要由兩個(gè)命令構(gòu)成:export
和import
属拾。
export
命令用于規(guī)定模塊的對(duì)外接口。
import
命令用于輸入其他模塊提供的功能冷溶。
一個(gè)模塊就是一個(gè)獨(dú)立的文件渐白。該文件內(nèi)部的所有變量,外部無(wú)法獲取逞频。如果你希望外部能夠讀取模塊內(nèi)部的某個(gè)變量纯衍,就必須使用export
關(guān)鍵字輸出該變量。
export輸出變量的寫(xiě)法:
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
還可以一起導(dǎo)出苗胀。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
//跟上面寫(xiě)法等價(jià)襟诸,推薦這種寫(xiě)法瓦堵。
export
命令除了輸出變量,還可以輸出函數(shù)或類(class)歌亲。
export function multiply(x, y) {
return x * y;
};
通常情況下菇用,export
輸出的變量就是本來(lái)的名字,但是可以使用as
關(guān)鍵字重命名陷揪。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
export
命令規(guī)定的是對(duì)外的接口惋鸥,必須與模塊內(nèi)部的變量建立一一對(duì)應(yīng)關(guān)系。
// 報(bào)錯(cuò)
export 1;
// 報(bào)錯(cuò)
var m = 1;
export m;
//正確寫(xiě)法
// 寫(xiě)法一
export var m = 1;
// 寫(xiě)法二
var m = 1;
export {m};
// 寫(xiě)法三
var n = 1;
export {n as m};
同樣的悍缠,function
和class
的輸出卦绣,也必須遵守這樣的寫(xiě)法。
// 報(bào)錯(cuò)
function f() {}
export f;
// 正確
export function f() {};
// 正確
function f() {}
export {f};
export
語(yǔ)句輸出的接口飞蚓,與其對(duì)應(yīng)的值是動(dòng)態(tài)綁定關(guān)系滤港,即通過(guò)該接口,可以取到模塊內(nèi)部實(shí)時(shí)的值趴拧。但是不建議這樣做溅漾。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
上面代碼輸出變量foo
,值為bar
八堡,500 毫秒之后變成baz
樟凄。
export
命令可以出現(xiàn)在模塊的任何位置,只要處于模塊頂層就可以兄渺。如果處于塊級(jí)作用域內(nèi)缝龄,就會(huì)報(bào)錯(cuò),下面的import
命令也是如此
- import命令
使用export
命令定義了模塊的對(duì)外接口以后挂谍,其他 JS 文件就可以通過(guò)import
命令加載這個(gè)模塊叔壤。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上面代碼的import
命令,用于加載profile.js
文件口叙,并從中輸入變量炼绘。import
命令接受一對(duì)大括號(hào),里面指定要從其他模塊導(dǎo)入的變量名妄田。大括號(hào)里面的變量名俺亮,必須與被導(dǎo)入模塊(profile.js
)對(duì)外接口的名稱相同。
如果想為輸入的變量重新取一個(gè)名字疟呐,import
命令要使用as
關(guān)鍵字,將輸入的變量重命名启具。
import { lastName as surname } from './profile';
import
后面的from
指定模塊文件的位置,可以是相對(duì)路徑拷沸,也可以是絕對(duì)路徑,.js
后綴可以省略秧了。
注意,import
命令具有提升效果示惊,會(huì)提升到整個(gè)模塊的頭部愉镰,首先執(zhí)行。
foo();
import { foo } from 'my_module';
//import的執(zhí)行早于foo的調(diào)用钧汹。這種行為的本質(zhì)是丈探,import命令是編譯階段執(zhí)行的,在代碼運(yùn)行之前拔莱。
由于import
是靜態(tài)執(zhí)行碗降,所以不能使用表達(dá)式和變量,這些只有在運(yùn)行時(shí)才能得到結(jié)果的語(yǔ)法結(jié)構(gòu)塘秦。
// 報(bào)錯(cuò)
import { 'f' + 'oo' } from 'my_module';
// 報(bào)錯(cuò)
let module = 'my_module';
import { foo } from module;
// 報(bào)錯(cuò)
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
除了指定加載某個(gè)輸出值讼渊,還可以使用整體加載,即用星號(hào)(*
)指定一個(gè)對(duì)象尊剔,所有輸出值都加載在這個(gè)對(duì)象上面爪幻。
注意,模塊整體加載所在的那個(gè)對(duì)象须误,不允許運(yùn)行時(shí)改變挨稿。下面的寫(xiě)法都是不允許的。
import * as circle from './circle';
// 下面兩行都是不允許的
circle.foo = 'hello';
circle.area = function () {};
- export default
使用import
命令的時(shí)候京痢,用戶需要知道所要加載的變量名或函數(shù)名奶甘,否則無(wú)法加載。
為了給用戶提供方便祭椰,讓他們不用閱讀文檔就能加載模塊臭家,就要用到export default
命令,為模塊指定默認(rèn)輸出方淤。
// export-default.js
export default function () {
console.log('foo');
}
其他模塊加載該模塊時(shí)钉赁,import
命令可以為該匿名函數(shù)指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
需要注意的是臣淤,這時(shí)import
命令后面橄霉,不使用大括號(hào)。
export default
命令用在非匿名函數(shù)前,也是可以的姓蜂。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者寫(xiě)成
function foo() {
console.log('foo');
}
export default foo;
上面代碼中按厘,foo
函數(shù)的函數(shù)名foo
,在模塊外部是無(wú)效的逮京。加載的時(shí)候懒棉,視同匿名函數(shù)加載策严。
比較一下默認(rèn)輸出和正常輸出。
// 第一組
export default function crc32() { // 輸出
// ...
}
import crc32 from 'crc32'; // 輸入
// 第二組
export function crc32() { // 輸出
// ...
};
import {crc32} from 'crc32'; // 輸入
上面代碼的兩組寫(xiě)法倔韭,第一組是使用export default
時(shí)寿酌,對(duì)應(yīng)的import
語(yǔ)句不需要使用大括號(hào)醇疼;第二組是不使用export default
時(shí)僵腺,對(duì)應(yīng)的import
語(yǔ)句需要使用大括號(hào)辰如。
export default
命令用于指定模塊的默認(rèn)輸出。顯然豌蟋,一個(gè)模塊只能有一個(gè)默認(rèn)輸出梧疲,因此export default
命令只能使用一次缭受。所以米者,import命令后面才不用加大括號(hào)宇智,因?yàn)橹豢赡芪ㄒ粚?duì)應(yīng)export default
命令随橘。
本質(zhì)上妻顶,export default
就是輸出一個(gè)叫做default
的變量或方法蜒车,然后系統(tǒng)允許你為它取任意名字酿愧。所以嬉挡,下面的寫(xiě)法是有效的庞钢。
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
正是因?yàn)?code>export default命令其實(shí)只是輸出一個(gè)叫做default
的變量,所以它后面不能跟變量聲明語(yǔ)句财岔。
// 正確
export var a = 1;
// 正確
var a = 1;
export default a;
// 錯(cuò)誤
export default var a = 1;
上面代碼中桐款,export default a
的含義是將變量a
的值賦給變量default
魔眨。所以遏暴,最后一種寫(xiě)法會(huì)報(bào)錯(cuò)唠梨。
同樣地,因?yàn)?code>export default命令的本質(zhì)是將后面的值儿捧,賦給default
變量蚁鳖,所以可以直接將一個(gè)值寫(xiě)在export default
之后。
// 正確
export default 42;
// 報(bào)錯(cuò)
export 42;
- export和import的復(fù)合寫(xiě)法
如果在一個(gè)模塊之中徙垫,先輸入后輸出同一個(gè)模塊己英,import
語(yǔ)句可以與export
語(yǔ)句寫(xiě)在一起损肛。
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
模塊的接口改名和整體輸出治拿,也可以采用這種寫(xiě)法劫谅。
// 接口改名
export { foo as myFoo } from 'my_module';
// 整體輸出
export * from 'my_module';