Javascript 類繼承

2023-02-17 10:52 更新

類繼承是一個(gè)類擴(kuò)展另一個(gè)類的一種方式。

因此,我們可以在現(xiàn)有功能之上創(chuàng)建新功能。

“extends” 關(guān)鍵字

假設(shè)我們有 class Animal

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }
  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }
}

let animal = new Animal("My animal");

這是我們對(duì)對(duì)象 animal 和 class Animal 的圖形化表示:


……然后我們想創(chuàng)建另一個(gè) class Rabbit

因?yàn)?rabbit 是 animal,所以 class Rabbit 應(yīng)該是基于 class Animal 的,可以訪問 animal 的方法,以便 rabbit 可以做“一般”動(dòng)物可以做的事兒。

擴(kuò)展另一個(gè)類的語(yǔ)法是:class Child extends Parent

讓我們創(chuàng)建一個(gè)繼承自 Animal 的 class Rabbit

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!

class Rabbit 的對(duì)象可以訪問例如 rabbit.hide() 等 Rabbit 的方法,還可以訪問例如 rabbit.run() 等 Animal 的方法。

在內(nèi)部,關(guān)鍵字 extends 使用了很好的舊的原型機(jī)制進(jìn)行工作。它將 Rabbit.prototype.[[Prototype]] 設(shè)置為 Animal.prototype。所以,如果在 Rabbit.prototype 中找不到一個(gè)方法,JavaScript 就會(huì)從 Animal.prototype 中獲取該方法。


例如,要查找 rabbit.run 方法,JavaScript 引擎會(huì)進(jìn)行如下檢查(如圖所示從下到上):

  1. 查找對(duì)象 ?rabbit?(沒有 ?run?)。
  2. 查找它的原型,即 ?Rabbit.prototype?(有 ?hide?,但沒有 ?run?)。
  3. 查找它的原型,即(由于 ?extends?)?Animal.prototype?,在這兒找到了 ?run? 方法。

我們可以回憶一下 原生的原型 這一章的內(nèi)容,JavaScript 內(nèi)建對(duì)象同樣也使用原型繼承。例如,Date.prototype.[[Prototype]] 是 Object.prototype。這就是為什么日期可以訪問通用對(duì)象的方法。

在 ?extends? 后允許任意表達(dá)式

類語(yǔ)法不僅允許指定一個(gè)類,在 extends 后可以指定任意表達(dá)式。

例如,一個(gè)生成父類的函數(shù)調(diào)用:

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello

這里 class User 繼承自 f("Hello") 的結(jié)果。

這對(duì)于高級(jí)編程模式,例如當(dāng)我們根據(jù)許多條件使用函數(shù)生成類,并繼承它們時(shí)來說可能很有用。

重寫方法

現(xiàn)在,讓我們繼續(xù)前行并嘗試重寫一個(gè)方法。默認(rèn)情況下,所有未在 class Rabbit 中指定的方法均從 class Animal 中直接獲取。

但是如果我們?cè)?nbsp;Rabbit 中指定了我們自己的方法,例如 stop(),那么將會(huì)使用它:

class Rabbit extends Animal {
  stop() {
    // ……現(xiàn)在這個(gè)將會(huì)被用作 rabbit.stop()
    // 而不是來自于 class Animal 的 stop()
  }
}

然而通常,我們不希望完全替換父類的方法,而是希望在父類方法的基礎(chǔ)上進(jìn)行調(diào)整或擴(kuò)展其功能。我們?cè)谖覀兊姆椒ㄖ凶鲆恍┦聝海窃谒盎蛑蠡蛟谶^程中會(huì)調(diào)用父類方法。

Class 為此提供了 "super" 關(guān)鍵字。

  • 執(zhí)行 ?super.method(...)? 來調(diào)用一個(gè)父類方法。
  • 執(zhí)行 ?super(...)? 來調(diào)用一個(gè)父類 constructor(只能在我們的 constructor 中)。

例如,讓我們的 rabbit 在停下來的時(shí)候自動(dòng) hide:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // 調(diào)用父類的 stop
    this.hide(); // 然后 hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!

現(xiàn)在,Rabbit 在執(zhí)行過程中調(diào)用父類的 super.stop() 方法,所以 Rabbit 也具有了 stop 方法。

箭頭函數(shù)沒有 ?super?

正如我們?cè)?nbsp;深入理解箭頭函數(shù) 一章中所提到的,箭頭函數(shù)沒有 super。

如果被訪問,它會(huì)從外部函數(shù)獲取。例如:

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // 1 秒后調(diào)用父類的 stop
  }
}

箭頭函數(shù)中的 super 與 stop() 中的是一樣的,所以它能按預(yù)期工作。如果我們?cè)谶@里指定一個(gè)“普通”函數(shù),那么將會(huì)拋出錯(cuò)誤:

// 意料之外的 super
setTimeout(function() { super.stop() }, 1000);

重寫 constructor

對(duì)于重寫 constructor 來說,則有點(diǎn)棘手。

到目前為止,Rabbit 還沒有自己的 constructor

根據(jù) 規(guī)范,如果一個(gè)類擴(kuò)展了另一個(gè)類并且沒有 constructor,那么將生成下面這樣的“空” constructor

class Rabbit extends Animal {
  // 為沒有自己的 constructor 的擴(kuò)展類生成的
  constructor(...args) {
    super(...args);
  }
}

正如我們所看到的,它調(diào)用了父類的 constructor,并傳遞了所有的參數(shù)。如果我們沒有寫自己的 constructor,就會(huì)出現(xiàn)這種情況。

現(xiàn)在,我們給 Rabbit 添加一個(gè)自定義的 constructor。除了 name 之外,它還會(huì)指定 earLength。

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }

  // ...
}

// 不工作!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.

哎呦!我們得到了一個(gè)報(bào)錯(cuò)?,F(xiàn)在我們沒法新建 rabbit。是什么地方出錯(cuò)了?

簡(jiǎn)短的解釋是:

繼承類的 constructor 必須調(diào)用 super(...),并且 (!) 一定要在使用 this 之前調(diào)用。

……但這是為什么呢?這里發(fā)生了什么?確實(shí),這個(gè)要求看起來很奇怪。

當(dāng)然,本文會(huì)給出一個(gè)解釋。讓我們深入細(xì)節(jié),這樣你就可以真正地理解發(fā)生了什么。

在 JavaScript 中,繼承類(所謂的“派生構(gòu)造器”,英文為 “derived constructor”)的構(gòu)造函數(shù)與其他函數(shù)之間是有區(qū)別的。派生構(gòu)造器具有特殊的內(nèi)部屬性 [[ConstructorKind]]:"derived"。這是一個(gè)特殊的內(nèi)部標(biāo)簽。

該標(biāo)簽會(huì)影響它的 new 行為:

  • 當(dāng)通過 ?new? 執(zhí)行一個(gè)常規(guī)函數(shù)時(shí),它將創(chuàng)建一個(gè)空對(duì)象,并將這個(gè)空對(duì)象賦值給 ?this?。
  • 但是當(dāng)繼承的 constructor 執(zhí)行時(shí),它不會(huì)執(zhí)行此操作。它期望父類的 constructor 來完成這項(xiàng)工作。

因此,派生的 constructor 必須調(diào)用 super 才能執(zhí)行其父類(base)的 constructor,否則 this 指向的那個(gè)對(duì)象將不會(huì)被創(chuàng)建。并且我們會(huì)收到一個(gè)報(bào)錯(cuò)。

為了讓 Rabbit 的 constructor 可以工作,它需要在使用 this 之前調(diào)用 super(),就像下面這樣:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// 現(xiàn)在可以了
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10

重寫類字段: 一個(gè)棘手的注意要點(diǎn)

高階要點(diǎn)

這個(gè)要點(diǎn)假設(shè)你對(duì)類已經(jīng)有了一定的經(jīng)驗(yàn),或許是在其他編程語(yǔ)言中。

這里提供了一個(gè)更好的視角來窺探這門語(yǔ)言,且解釋了它的行為為什么可能會(huì)是 bugs 的來源(但不是非常頻繁)。

如果你發(fā)現(xiàn)這難以理解,什么都別管,繼續(xù)往下閱讀,之后有機(jī)會(huì)再回來看。

我們不僅可以重寫方法,還可以重寫類字段。

不過,當(dāng)我們?cè)诟割悩?gòu)造器中訪問一個(gè)被重寫的字段時(shí),有一個(gè)詭異的行為,這與絕大多數(shù)其他編程語(yǔ)言都很不一樣。

請(qǐng)思考此示例:

class Animal {
  name = 'animal';

  constructor() {
    alert(this.name); // (*)
  }
}

class Rabbit extends Animal {
  name = 'rabbit';
}

new Animal(); // animal
new Rabbit(); // animal

這里,Rabbit 繼承自 Animal,并且用它自己的值重寫了 name 字段。

因?yàn)?nbsp;Rabbit 中沒有自己的構(gòu)造器,所以 Animal 的構(gòu)造器被調(diào)用了。

有趣的是在這兩種情況下:new Animal() 和 new Rabbit(),在 (*) 行的 alert 都打印了 animal。

換句話說,父類構(gòu)造器總是會(huì)使用它自己字段的值,而不是被重寫的那一個(gè)。

古怪的是什么呢?

如果這還不清楚,那么讓我們用方法來進(jìn)行比較。

這里是相同的代碼,但是我們調(diào)用 this.showName() 方法而不是 this.name 字段:

class Animal {
  showName() {  // 而不是 this.name = 'animal'
    alert('animal');
  }

  constructor() {
    this.showName(); // 而不是 alert(this.name);
  }
}

class Rabbit extends Animal {
  showName() {
    alert('rabbit');
  }
}

new Animal(); // animal
new Rabbit(); // rabbit

請(qǐng)注意:這時(shí)的輸出是不同的。

這才是我們本來所期待的結(jié)果。當(dāng)父類構(gòu)造器在派生的類中被調(diào)用時(shí),它會(huì)使用被重寫的方法。

……但對(duì)于類字段并非如此。正如前文所述,父類構(gòu)造器總是使用父類的字段。

這里為什么會(huì)有這樣的區(qū)別呢?

實(shí)際上,原因在于字段初始化的順序。類字段是這樣初始化的:

  • 對(duì)于基類(還未繼承任何東西的那種),在構(gòu)造函數(shù)調(diào)用前初始化。
  • 對(duì)于派生類,在 ?super()? 后立刻初始化。

在我們的例子中,Rabbit 是派生類,里面沒有 constructor()。正如先前所說,這相當(dāng)于一個(gè)里面只有 super(...args) 的空構(gòu)造器。

所以,new Rabbit() 調(diào)用了 super(),因此它執(zhí)行了父類構(gòu)造器,并且(根據(jù)派生類規(guī)則)只有在此之后,它的類字段才被初始化。在父類構(gòu)造器被執(zhí)行的時(shí)候,Rabbit 還沒有自己的類字段,這就是為什么 Animal 類字段被使用了。

這種字段與方法之間微妙的區(qū)別只特定于 JavaScript。

幸運(yùn)的是,這種行為僅在一個(gè)被重寫的字段被父類構(gòu)造器使用時(shí)才會(huì)顯現(xiàn)出來。接下來它會(huì)發(fā)生的東西可能就比較難理解了,所以我們要在這里對(duì)此行為進(jìn)行解釋。

如果出問題了,我們可以通過使用方法或者 getter/setter 替代類字段,來修復(fù)這個(gè)問題。

深入:內(nèi)部探究和 [[HomeObject]]

進(jìn)階內(nèi)容

如果你是第一次閱讀本教程,那么則可以跳過本節(jié)。

這是關(guān)于繼承和 ?super? 背后的內(nèi)部機(jī)制。

讓我們更深入地研究 super。我們將在這個(gè)過程中發(fā)現(xiàn)一些有趣的事兒。

首先要說的是,從我們迄今為止學(xué)到的知識(shí)來看,super 是不可能運(yùn)行的。

的確是這樣,讓我們問問自己,以技術(shù)的角度它是如何工作的?當(dāng)一個(gè)對(duì)象方法執(zhí)行時(shí),它會(huì)將當(dāng)前對(duì)象作為 this。隨后如果我們調(diào)用 super.method(),那么引擎需要從當(dāng)前對(duì)象的原型中獲取 method。但這是怎么做到的?

這個(gè)任務(wù)看起來是挺容易的,但其實(shí)并不簡(jiǎn)單。引擎知道當(dāng)前對(duì)象的 this,所以它可以獲取父 method 作為 this.__proto__.method。不幸的是,這個(gè)“天真”的解決方法是行不通的。

讓我們演示一下這個(gè)問題。簡(jiǎn)單起見,我們使用普通對(duì)象而不使用類。

如果你不想知道更多的細(xì)節(jié)知識(shí),你可以跳過此部分,并轉(zhuǎn)到下面的 [[HomeObject]] 小節(jié)。這沒關(guān)系的。但如果你感興趣,想學(xué)習(xí)更深入的知識(shí),那就繼續(xù)閱讀吧。

在下面的例子中,rabbit.__proto__ = animal?,F(xiàn)在讓我們嘗試一下:在 rabbit.eat() 我們將會(huì)使用 this.__proto__ 調(diào)用 animal.eat()

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {
    // 這就是 super.eat() 可以大概工作的方式
    this.__proto__.eat.call(this); // (*)
  }
};

rabbit.eat(); // Rabbit eats.

在 (*) 這一行,我們從原型(animal)中獲取 eat,并在當(dāng)前對(duì)象的上下文中調(diào)用它。請(qǐng)注意,.call(this) 在這里非常重要,因?yàn)楹?jiǎn)單的調(diào)用 this.__proto__.eat() 將在原型的上下文中執(zhí)行 eat,而非當(dāng)前對(duì)象。

在上面的代碼中,它確實(shí)按照了期望運(yùn)行:我們獲得了正確的 alert。

現(xiàn)在,讓我們?cè)谠玩溕显偬砑右粋€(gè)對(duì)象。我們將看到這件事是如何被打破的:

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    // ...bounce around rabbit-style and call parent (animal) method
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    // ...do something with long ears and call parent (rabbit) method
    this.__proto__.eat.call(this); // (**)
  }
};

longEar.eat(); // Error: Maximum call stack size exceeded

代碼無法再運(yùn)行了!我們可以看到,在試圖調(diào)用 longEar.eat() 時(shí)拋出了錯(cuò)誤。

原因可能不那么明顯,但是如果我們跟蹤 longEar.eat() 調(diào)用,就可以發(fā)現(xiàn)原因。在 (*) 和 (**) 這兩行中,this 的值都是當(dāng)前對(duì)象(longEar)。這是至關(guān)重要的一點(diǎn):所有的對(duì)象方法都將當(dāng)前對(duì)象作為 this,而非原型或其他什么東西。

因此,在 (*) 和 (**) 這兩行中,this.__proto__ 的值是完全相同的:都是 rabbit。它們倆都調(diào)用的是 rabbit.eat,它們?cè)诓煌5匮h(huán)調(diào)用自己,而不是在原型鏈上向上尋找方法。

這張圖介紹了發(fā)生的情況:


  1. 在 longEar.eat() 中,(**) 這一行調(diào)用 rabbit.eat 并為其提供 this=longEar。
  2. // 在 longEar.eat() 中我們有 this = longEar
    this.__proto__.eat.call(this) // (**)
    // 變成了
    longEar.__proto__.eat.call(this)
    // 也就是
    rabbit.eat.call(this);
  3. 之后在 rabbit.eat 的 (*) 行中,我們希望將函數(shù)調(diào)用在原型鏈上向更高層傳遞,但是 this=longEar,所以 this.__proto__.eat 又是 rabbit.eat!
  4. // 在 rabbit.eat() 中我們依然有 this = longEar
    this.__proto__.eat.call(this) // (*)
    // 變成了
    longEar.__proto__.eat.call(this)
    // 或(再一次)
    rabbit.eat.call(this);
  5. ……所以 ?rabbit.eat? 在不停地循環(huán)調(diào)用自己,因此它無法進(jìn)一步地提升。

這個(gè)問題沒法僅僅通過使用 this 來解決。

[[HomeObject]]

為了提供解決方法,JavaScript 為函數(shù)添加了一個(gè)特殊的內(nèi)部屬性:[[HomeObject]]

當(dāng)一個(gè)函數(shù)被定義為類或者對(duì)象方法時(shí),它的 [[HomeObject]] 屬性就成為了該對(duì)象。

然后 super 使用它來解析(resolve)父原型及其方法。

讓我們看看它是怎么工作的,首先,對(duì)于普通對(duì)象:

let animal = {
  name: "Animal",
  eat() {         // animal.eat.[[HomeObject]] == animal
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {         // rabbit.eat.[[HomeObject]] == rabbit
    super.eat();
  }
};

let longEar = {
  __proto__: rabbit,
  name: "Long Ear",
  eat() {         // longEar.eat.[[HomeObject]] == longEar
    super.eat();
  }
};

// 正確執(zhí)行
longEar.eat();  // Long Ear eats.

它基于 [[HomeObject]] 運(yùn)行機(jī)制按照預(yù)期執(zhí)行。一個(gè)方法,例如 longEar.eat,知道其 [[HomeObject]] 并且從其原型中獲取父方法。并沒有使用 this。

方法并不是“自由”的

正如我們之前所知道的,函數(shù)通常都是“自由”的,并沒有綁定到 JavaScript 中的對(duì)象。正因如此,它們可以在對(duì)象之間復(fù)制,并用另外一個(gè) this 調(diào)用它。

[[HomeObject]] 的存在違反了這個(gè)原則,因?yàn)榉椒ㄓ涀×怂鼈兊膶?duì)象。[[HomeObject]] 不能被更改,所以這個(gè)綁定是永久的。

在 JavaScript 語(yǔ)言中 [[HomeObject]] 僅被用于 super。所以,如果一個(gè)方法不使用 super,那么我們?nèi)匀豢梢砸曀鼮樽杂傻牟⑶铱稍趯?duì)象之間復(fù)制。但是用了 super 再這樣做可能就會(huì)出錯(cuò)。

下面是復(fù)制后錯(cuò)誤的 super 結(jié)果的示例:

let animal = {
  sayHi() {
    alert(`I'm an animal`);
  }
};

// rabbit 繼承自 animal
let rabbit = {
  __proto__: animal,
  sayHi() {
    super.sayHi();
  }
};

let plant = {
  sayHi() {
    alert("I'm a plant");
  }
};

// tree 繼承自 plant
let tree = {
  __proto__: plant,
  sayHi: rabbit.sayHi // (*)
};

tree.sayHi();  // I'm an animal (?!?)

調(diào)用 tree.sayHi() 顯示 “I’m an animal”。這絕對(duì)是錯(cuò)誤的。

原因很簡(jiǎn)單:

  • 在 ?(*)? 行,?tree.sayHi? 方法是從 ?rabbit? 復(fù)制而來。也許我們只是想避免重復(fù)代碼?
  • 它的 ?[[HomeObject]]? 是 ?rabbit?,因?yàn)樗窃?nbsp;?rabbit? 中創(chuàng)建的。沒有辦法修改 ?[[HomeObject]]?。
  • ?tree.sayHi()? 內(nèi)具有 ?super.sayHi()?。它從 ?rabbit? 中上溯,然后從 ?animal? 中獲取方法。

這是發(fā)生的情況示意圖:


方法,不是函數(shù)屬性

[[HomeObject]] 是為類和普通對(duì)象中的方法定義的。但是對(duì)于對(duì)象而言,方法必須確切指定為 method(),而不是 "method: function()"。

這個(gè)差別對(duì)我們來說可能不重要,但是對(duì) JavaScript 來說卻非常重要。

在下面的例子中,使用非方法(non-method)語(yǔ)法進(jìn)行了比較。未設(shè)置 [[HomeObject]] 屬性,并且繼承無效:

let animal = {
  eat: function() { // 這里是故意這樣寫的,而不是 eat() {...
    // ...
  }
};

let rabbit = {
  __proto__: animal,
  eat: function() {
    super.eat();
  }
};

rabbit.eat();  // 錯(cuò)誤調(diào)用 super(因?yàn)檫@里沒有 [[HomeObject]])

總結(jié)

  1. 想要擴(kuò)展一個(gè)類:?class Child extends Parent?:
    • 這意味著 ?Child.prototype.__proto__? 將是 ?Parent.prototype?,所以方法會(huì)被繼承。
  2. 重寫一個(gè) constructor:
    • 在使用 ?this? 之前,我們必須在 ?Child? 的 constructor 中將父 constructor 調(diào)用為 ?super()?。
  3. 重寫一個(gè)方法:
    • 我們可以在一個(gè) ?Child? 方法中使用 ?super.method()? 來調(diào)用 ?Parent? 方法。
  4. 內(nèi)部:
    • 方法在內(nèi)部的 ?[[HomeObject]]? 屬性中記住了它們的類/對(duì)象。這就是 ?super? 如何解析父方法的。
    • 因此,將一個(gè)帶有 ?super? 的方法從一個(gè)對(duì)象復(fù)制到另一個(gè)對(duì)象是不安全的。

補(bǔ)充:

  • 箭頭函數(shù)沒有自己的 ?this? 或 ?super?,所以它們能融入到就近的上下文中,像透明似的。

任務(wù)


創(chuàng)建實(shí)例時(shí)出錯(cuò)

重要程度: 5

這里有一份 ?Rabbit? 擴(kuò)展 ?Animal? 的代碼。

不幸的是,?Rabbit? 對(duì)象無法被創(chuàng)建。是哪里出錯(cuò)了呢?請(qǐng)解決它。

class Animal {

  constructor(name) {
    this.name = name;
  }

}

class Rabbit extends Animal {
  constructor(name) {
    this.name = name;
    this.created = Date.now();
  }
}

let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined
alert(rabbit.name);

解決方案

這是因?yàn)樽宇惖?constructor 必須調(diào)用 super()。

這里是修正后的代碼:

class Animal {

  constructor(name) {
    this.name = name;
  }

}

class Rabbit extends Animal {
  constructor(name) {
    super(name);
    this.created = Date.now();
  }
}

let rabbit = new Rabbit("White Rabbit"); // 現(xiàn)在好了
alert(rabbit.name); // White Rabbit

擴(kuò)展 clock

重要程度: 5

我們獲得了一個(gè) ?Clock? 類。到目前為止,它每秒都會(huì)打印一次時(shí)間。

class Clock {
  constructor({ template }) {
    this.template = template;
  }

  render() {
    let date = new Date();

    let hours = date.getHours();
    if (hours < 10) hours = '0' + hours;

    let mins = date.getMinutes();
    if (mins < 10) mins = '0' + mins;

    let secs = date.getSeconds();
    if (secs < 10) secs = '0' + secs;

    let output = this.template
      .replace('h', hours)
      .replace('m', mins)
      .replace('s', secs);

    console.log(output);
  }

  stop() {
    clearInterval(this.timer);
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), 1000);
  }
}

創(chuàng)建一個(gè)繼承自 Clock 的新的類 ExtendedClock,并添加參數(shù) precision — 每次 “ticks” 之間間隔的毫秒數(shù),默認(rèn)是 1000(1 秒)。

  • 你的代碼應(yīng)該在 ?extended-clock.js? 文件里。
  • 不要修改原有的 ?clock.js?。請(qǐng)擴(kuò)展它。

解決方案

class ExtendedClock extends Clock {
  constructor(options) {
    super(options);
    let { precision = 1000 } = options;
    this.precision = precision;
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), this.precision);
  }
};


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)