ES6新紀(jì)元下

七乙埃、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è):

  1. 為各種數(shù)據(jù)結(jié)構(gòu)遇伞,提供一個(gè)統(tǒng)一的、簡(jiǎn)便的訪問(wèn)接口捶牢;
  2. 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列鸠珠;
  3. 主要供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

  1. 基本概念驮俗。
    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á)式(helloworld),即該函數(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) { ··· }
  1. 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
  1. 與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);
}
  1. 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 }
  1. 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);
}
  1. 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 }
  1. 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"

foobar都是 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á)式。

  1. 作為對(duì)象屬性的 Generator 函數(shù)
    如果一個(gè)對(duì)象的屬性是 Generator 函數(shù)渔呵,可以簡(jiǎn)寫(xiě)成下面的形式怒竿。
let obj = {
  * myGeneratorMethod() {
    ···
  }
};

九、async函數(shù)

  1. 含義扩氢。
    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ǔ)義。
    asyncawait矩父,比起星號(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ǔ)法糖扶檐。
  2. 錯(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);
  }
}
  1. 應(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

  1. 用法
    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"]
  1. 嚴(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)格模式。
  2. 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í)例已旧。
  1. 類必須使用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'
  1. 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í)例丸相。

  1. 私有方法和私有屬性
    私有方法/私有屬性是常見(jiàn)需求搔确,但 ES6 不提供,只能通過(guò)變通方法模擬實(shí)現(xiàn)灭忠。
    通常是在命名上加以區(qū)別膳算。
class Fn {

  // 公有方法
  foo () {
    //....
  }

  // 假裝是私有方法(其實(shí)外部還是可以訪問(wèn))
  _bar() {
    //....
  }
}
  1. 原型的屬性
    class定義類時(shí),只能在constructor里定義屬性弛作,在其他位置會(huì)報(bào)錯(cuò)涕蜂。
    如果需要在原型上定義方法可以使用:
  • Fn.prototype.prop = value;
  • Object.getPrototypeOf()獲取原型,再來(lái)擴(kuò)展
  • Object.assign(Fn.prototype,{在這里面寫(xiě)擴(kuò)展的屬性或者方法})
  1. 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
  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]
  1. 繼承
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ì)被繼承验庙。

  1. Object.getPrototypeOf()
    Object.getPrototypeOf方法可以用來(lái)從子類上獲取父類顶吮。
Object.getPrototypeOf(Fn2) === Fn
// true

因此,可以使用這個(gè)方法判斷粪薛,一個(gè)類是否繼承了另一個(gè)類悴了。

  1. 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

  1. export命令
    模塊功能主要由兩個(gè)命令構(gòu)成:exportimport属拾。
    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};

同樣的悍缠,functionclass的輸出卦绣,也必須遵守這樣的寫(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命令也是如此

  1. 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 () {};
  1. 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;
  1. 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';
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市未檩,隨后出現(xiàn)的幾起案子冤狡,更是在濱河造成了極大的恐慌,老刑警劉巖挎峦,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坦胶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纪岁,警方通過(guò)查閱死者的電腦和手機(jī)幔翰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贡定,“玉大人,你說(shuō)我怎么就攤上這事渠牲∏╄荆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵鹦付,是天一觀的道長(zhǎng)郎嫁。 經(jīng)常有香客問(wèn)我泽铛,道長(zhǎng)盔腔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任吓坚,我火速辦了婚禮盐杂,結(jié)果婚禮上链烈,老公的妹妹穿的比我還像新娘。我一直安慰自己漩勤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布究飞。 她就那樣靜靜地躺著,像睡著了一般葵擎。 火紅的嫁衣襯著肌膚如雪坪蚁。 梳的紋絲不亂的頭發(fā)上贱田,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音耗拓,去河邊找鬼。 笑死竿刁,一個(gè)胖子當(dāng)著我的面吹牛食拜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播副编,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼负甸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了痹届?” 一聲冷哼從身側(cè)響起呻待,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎短纵,沒(méi)想到半個(gè)月后带污,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡充易,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了控漠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碉渡。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤哄酝,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布勇皇,位于F島的核電站着撩,受9級(jí)特大地震影響薯鳍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦橄仆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至错英,卻和暖如春献雅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碍脏,地道東北人典尾。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像汞贸,于是被迫代替她去往敵國(guó)和親奶是。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屯烦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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