成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

ES6 Class 繼承與 super

Eidesen / 2270人閱讀

摘要:在繼承類中,相應(yīng)的構(gòu)造函數(shù)被標(biāo)記為特殊的內(nèi)部屬性。區(qū)別在于當(dāng)一個(gè)普通的構(gòu)造函數(shù)運(yùn)行時(shí),它會(huì)創(chuàng)建一個(gè)空對(duì)象作為,然后繼續(xù)運(yùn)行。但是當(dāng)派生的構(gòu)造函數(shù)運(yùn)行時(shí),與上面說的不同,它指望父構(gòu)造函數(shù)來完成這項(xiàng)工作。

原文 https://javascript.info/class...

Class 繼承與 super

class 可以 extends 自另一個(gè) class。這是一個(gè)不錯(cuò)的語法,技術(shù)上基于原型繼承。

要繼承一個(gè)對(duì)象,需要在 {..} 前指定 extends 和父對(duì)象。

這個(gè) Rabbit 繼承自 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} stopped.`);
  }

}


// Inherit from Animal
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!

如你所見,如你所想,extend 關(guān)鍵字實(shí)際上是在 Rabbit.prototype 添加 [Prototype]],引用到 Animal.prototype。

所以現(xiàn)在 rabbit 既可以訪問它自己的方法,也可以訪問 Animal 的方法。

extends 后可跟表達(dá)式

Class 語法的 `extends" 后接的不限于指定一個(gè)類,更可以是表達(dá)式。

例如一個(gè)生成父類的函數(shù):

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í),這就很有用。

重寫一個(gè)方法

現(xiàn)在讓我們進(jìn)入下一步,重寫一個(gè)方法。到目前為止,RabbitAnimal 繼承了 stop 方法,this.speed = 0。

如果我們?cè)?Rabbit 中指定了自己的 stop,那么會(huì)被優(yōu)先使用:

class Rabbit extends Animal {
  stop() {
    // ...this will be used for rabbit.stop()
  }
}

......但通常我們不想完全替代父方法,而是在父方法的基礎(chǔ)上調(diào)整或擴(kuò)展其功能。我們進(jìn)行一些操作,讓它之前/之后或在過程中調(diào)用父方法。

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

使用 super.method(...) 調(diào)用父方法。

使用 super(...) 調(diào)用父構(gòu)造函數(shù)(僅在 constructor 函數(shù)中)。

例如,讓兔子在 stop 時(shí)自動(dòng)隱藏:

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} stopped.`);
  }

}

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


  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }

}

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

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

現(xiàn)在,Rabbitstop 方法通過 super.stop() 調(diào)用父類的方法。

箭頭函數(shù)無 super

正如在 arrow-functions 一章中提到,箭頭函數(shù)沒有 super。

它會(huì)從外部函數(shù)中獲取 super。例如:

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}

箭頭函數(shù)中的 superstop() 中的相同,所以它按預(yù)期工作。如果我們?cè)谶@里用普通函數(shù),便會(huì)報(bào)錯(cuò):

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
重寫構(gòu)造函數(shù)

對(duì)于構(gòu)造函數(shù)來說,這有點(diǎn)棘手 tricky。

直到現(xiàn)在,Rabbit 都沒有自己的 constructor。
Till now, Rabbit did not have its own constructor.

根據(jù)規(guī)范,如果一個(gè)類擴(kuò)展了另一個(gè)類并且沒有 constructor ,那么會(huì)自動(dòng)生成如下 constructor

class Rabbit extends Animal {
  // generated for extending classes without own constructors

  constructor(...args) {
    super(...args);
  }

}

我們可以看到,它調(diào)用了父 constructor 傳遞所有參數(shù)。如果我們不自己寫構(gòu)造函數(shù),就會(huì)發(fā)生這種情況。

現(xiàn)在我們將一個(gè)自定義構(gòu)造函數(shù)添加到 Rabbit 中。除了name,我們還會(huì)設(shè)置 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;
  }


  // ...
}


// Doesn"t work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.

哎呦出錯(cuò)了!現(xiàn)在我們不能生成兔子了,為什么呢?

簡(jiǎn)單來說:繼承類中的構(gòu)造函數(shù)必須調(diào)用 super(...),(!)并且在使用 this 之前執(zhí)行它。

...但為什么?這是什么情況?嗯...這個(gè)要求看起來確實(shí)奇怪。

現(xiàn)在我們探討細(xì)節(jié),讓你真正理解其中緣由 ——

在JavaScript中,繼承了其他類的構(gòu)造函數(shù)比較特殊。在繼承類中,相應(yīng)的構(gòu)造函數(shù)被標(biāo)記為特殊的內(nèi)部屬性 [[ConstructorKind]]:“derived”。

區(qū)別在于:

當(dāng)一個(gè)普通的構(gòu)造函數(shù)運(yùn)行時(shí),它會(huì)創(chuàng)建一個(gè)空對(duì)象作為 this,然后繼續(xù)運(yùn)行。

但是當(dāng)派生的構(gòu)造函數(shù)運(yùn)行時(shí),與上面說的不同,它指望父構(gòu)造函數(shù)來完成這項(xiàng)工作。

所以如果我們正在構(gòu)造我們自己的構(gòu)造函數(shù),那么我們必須調(diào)用 super,否則具有 this 的對(duì)象將不被創(chuàng)建,并報(bào)錯(cuò)。

對(duì)于 Rabbit 來說,我們需要在使用 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;
  }

  // ...
}


// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
Super 的實(shí)現(xiàn)與 [[HomeObject]]

讓我們?cè)偕钊肜斫?super 的底層實(shí)現(xiàn),我們會(huì)看到一些有趣的事情。

首先要說的是,以我們迄今為止學(xué)到的知識(shí)來看,實(shí)現(xiàn) super 是不可能的。

那么思考一下,這是什么原理?當(dāng)一個(gè)對(duì)象方法運(yùn)行時(shí),它將當(dāng)前對(duì)象作為 this。如果我們調(diào)用 super.method(),那么如何檢索 method?很容易想到,我們需要從當(dāng)前對(duì)象的原型中取出 method。從技術(shù)上講,我們(或JavaScript引擎)可以做到這一點(diǎn)嗎?

也許我們可以從 this 的 [[Prototype]] 中獲得方法,就像 this .__ proto __.method 一樣?不幸的是,這是行不通的。

讓我們?cè)囈辉?,?jiǎn)單起見,我們不使用 class 了,直接使用普通對(duì)象。

在這里,rabbit.eat() 調(diào)用父對(duì)象的 animal.eat() 方法:

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

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {

    // that"s how super.eat() could presumably work
    this.__proto__.eat.call(this); // (*)

  }
};

rabbit.eat(); // Rabbit eats.

(*) 這一行,我們從原型(animal)中取出 eat,并以當(dāng)前對(duì)象的上下文中調(diào)用它。請(qǐng)注意,.call(this) 在這里很重要,因?yàn)橹粚?this .__ proto __.eat() 的話 eat 的調(diào)用對(duì)象將會(huì)是 animal,而不是當(dāng)前對(duì)象。

以上代碼的 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

噢,完蛋!調(diào)用 longEar.eat() 報(bào)錯(cuò)了!

這原因一眼可能看不透,但如果我們跟蹤 longEar.eat() 調(diào)用,大概就知道為什么了。在 (*)(**) 兩行中, this 的值是當(dāng)前對(duì)象(longEar)。重點(diǎn)來了:所有方法都將當(dāng)前對(duì)象作為 this,而不是原型或其他東西。

因此,在兩行 (*)(**) 中,this.__ proto__ 的值都是 rabbit。他們都調(diào)用了 rabbit.eat,于是就這么無限循環(huán)下去。

情況如圖:

1.在 longEar.eat() 里面,(**) 行中調(diào)用了 rabbit.eat,并且this = longEar。

// inside longEar.eat() we have this = longEar
this.__proto__.eat.call(this) // (**)
// becomes
longEar.__proto__.eat.call(this)
// that is
rabbit.eat.call(this);

2.然后在rabbit.eat(*) 行中,我們希望傳到原型鏈的下一層,但是 this = longEar,所以 this .__ proto __.eat又是 rabbit.eat!

// inside rabbit.eat() we also have this = longEar
this.__proto__.eat.call(this) // (*)
// becomes
longEar.__proto__.eat.call(this)
// or (again)
rabbit.eat.call(this);

...因此 rabbit.eat 在無盡循環(huán)調(diào)動(dòng),無法進(jìn)入下一層。

這個(gè)問題不能簡(jiǎn)單使用 this 解決。

[[HomeObject]]

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

當(dāng)函數(shù)被指定為類或?qū)ο蠓椒〞r(shí),其 [[HomeObject]] 屬性為該對(duì)象。

這實(shí)際上違反了 unbind 函數(shù)的思想,因?yàn)榉椒ㄓ涀×怂鼈兊膶?duì)象。并且 [[HomeObject]] 不能被改變,所以這是永久 bind(綁定)。所以在 JavaScript 這是一個(gè)很大的變化。

但是這種改變是安全的。 [[HomeObject]] 僅用于在 super 中獲取下一層原型。所以它不會(huì)破壞兼容性。

讓我們來看看它是如何在 super 中運(yùn)作的:

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

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

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


longEar.eat();  // Long Ear eats.

每個(gè)方法都會(huì)在內(nèi)部 [[HomeObject]] 屬性中記住它的對(duì)象。然后 super 使用它來解析原型。

在類和普通對(duì)象中定義的方法中都定義了 [[HomeObject]],但是對(duì)于對(duì)象,必須使用:method() 而不是 "method: function()"

在下面的例子中,使用非方法語法(non-method syntax)進(jìn)行比較。這么做沒有設(shè)置 [[HomeObject]] 屬性,繼承也不起作用:

let animal = {
  eat: function() { // should be the short syntax: eat() {...}
    // ...
  }
};

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


rabbit.eat();  // Error calling super (because there"s no [[HomeObject]])
靜態(tài)方法和繼承

class 語法也支持靜態(tài)屬性的繼承。

例如:

class Animal {

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

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

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

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

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

現(xiàn)在我們可以調(diào)用 Rabbit.compare,假設(shè)繼承的 Animal.compare 將被調(diào)用。

它是如何工作的?再次使用原型。正如你猜到的那樣,extends 同樣給 Rabbit 提供了引用到 Animal[Prototype]。

所以,Rabbit 函數(shù)現(xiàn)在繼承 Animal 函數(shù)。Animal 自帶引用到 Function.prototype[[Prototype]](因?yàn)樗?extend 其他類)。

看看這里:

class Animal {}
class Rabbit extends Animal {}

// for static propertites and methods
alert(Rabbit.__proto__ === Animal); // true

// and the next step is Function.prototype
alert(Animal.__proto__ === Function.prototype); // true

// that"s in addition to the "normal" prototype chain for object methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);

這樣 Rabbit 可以訪問 Animal 的所有靜態(tài)方法。

在內(nèi)置對(duì)象中沒有靜態(tài)繼承

請(qǐng)注意,內(nèi)置類沒有靜態(tài) [[Prototype]] 引用。例如,Object 具有 Object.defineProperty,Object.keys等方法,但 Array,Date 不會(huì)繼承它們。

DateObject 的結(jié)構(gòu):

DateObject 之間毫無關(guān)聯(lián),他們獨(dú)立存在,不過 Date.prototype 繼承于 Object.prototype,僅此而已。

造成這個(gè)情況是因?yàn)?JavaScript 在設(shè)計(jì)初期沒有考慮使用 class 語法和繼承靜態(tài)方法。

原生拓展

Array,Map 等內(nèi)置類也可以擴(kuò)展。

舉個(gè)例子,PowerArray 繼承自原生 Array

// add one more method to it (can do more)
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false

請(qǐng)注意一件非常有趣的事情。像 filter,map 和其他內(nèi)置方法 - 返回新的繼承類型的對(duì)象。他們依靠 constructor 屬性來做到這一點(diǎn)。

在上面的例子中,

arr.constructor === PowerArray

所以當(dāng)調(diào)用 arr.filter() 時(shí),它自動(dòng)創(chuàng)建新的結(jié)果數(shù)組,就像 new PowerArray 一樣,于是我們可以繼續(xù)使用 PowerArray 的方法。

我們甚至可以自定義這種行為。如果存在靜態(tài) getter Symbol.species,返回新建對(duì)象使用的 constructor。

下面的例子中,由于 Symbol.species 的存在,map,filter等內(nèi)置方法將返回普通的數(shù)組:

class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }


  // built-in methods will use this as the constructor
  static get [Symbol.species]() {
    return Array;
  }

}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);


// filteredArr is not PowerArray, but Array

alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function

我們可以在其他 key 使用 Symbol.species,可以用于剝離結(jié)果值中的無用方法,或是增加其他方法。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96024.html

相關(guān)文章

  • ES6 class繼承super關(guān)鍵詞深入探索

    摘要:請(qǐng)看對(duì)應(yīng)版本干了什么可知,相當(dāng)于以前在構(gòu)造函數(shù)里的行為。這種寫法會(huì)與上文中寫法有何區(qū)別我們?cè)诃h(huán)境下運(yùn)行一下,看看這兩種構(gòu)造函數(shù)的有何區(qū)別打印結(jié)果打印結(jié)果結(jié)合上文中關(guān)于原型的論述,仔細(xì)品味這兩者的差別,最好手動(dòng)嘗試一下。 ES6 class 在ES6版本之前,JavaScript語言并沒有傳統(tǒng)面向?qū)ο笳Z言的class寫法,ES6發(fā)布之后,Babel迅速跟進(jìn),廣大開發(fā)者也很快喜歡上ES6帶...

    jubincn 評(píng)論0 收藏0
  • ES6class面向?qū)ο缶幊蹋?)

    摘要:接下來我們看下類的寫法,這個(gè)就很接近于傳統(tǒng)面向?qū)ο笳Z言了。如果你想了解傳統(tǒng)面向?qū)ο笳Z言,這里是一個(gè)好切入點(diǎn)。作為對(duì)象時(shí),指向父類的原型對(duì)象。這些就是為將來在中支持面向?qū)ο蟮念悪C(jī)制而預(yù)留的。 在ES5中,我們經(jīng)常使用方法或者對(duì)象去模擬類的使用,并基于原型實(shí)現(xiàn)繼承,雖然可以實(shí)現(xiàn)功能,但是代碼并不優(yōu)雅,很多人還是傾向于用 class 來組織代碼,很多類庫(kù)、框架創(chuàng)造了自己的 API 來實(shí)現(xiàn) c...

    wangjuntytl 評(píng)論0 收藏0
  • ES6Class創(chuàng)建對(duì)象繼承實(shí)現(xiàn)

    摘要:使用類創(chuàng)建實(shí)例對(duì)象也是直接對(duì)類使用命令,跟中構(gòu)造函數(shù)的用法一致。中沒有構(gòu)造函數(shù),作為構(gòu)造函數(shù)的語法糖,同時(shí)有屬性和屬性,因此同時(shí)存在兩條繼承鏈。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對(duì)象的原型的寫法更像面向?qū)ο笳Z言寫法。 ES6中通過class定義對(duì)象,默認(rèn)具有constructor方法和自定義方法,但是包含...

    zhou_you 評(píng)論0 收藏0
  • ES6Class創(chuàng)建對(duì)象繼承實(shí)現(xiàn)

    摘要:使用類創(chuàng)建實(shí)例對(duì)象也是直接對(duì)類使用命令,跟中構(gòu)造函數(shù)的用法一致。中沒有構(gòu)造函數(shù),作為構(gòu)造函數(shù)的語法糖,同時(shí)有屬性和屬性,因此同時(shí)存在兩條繼承鏈。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對(duì)象的原型的寫法更像面向?qū)ο笳Z言寫法。 ES6中通過class定義對(duì)象,默認(rèn)具有constructor方法和自定義方法,但是包含...

    wind5o 評(píng)論0 收藏0
  • ECMAScript 6入門類繼承筆記

    類繼承 看類繼承前,先回顧構(gòu)造函數(shù)怎么實(shí)現(xiàn)對(duì)象的繼承的 function F() { this.a = 1; } function Son() { F.call(this); } function inherit(S, F) { S.protot...

    _DangJin 評(píng)論0 收藏0
  • 再和“面向?qū)ο蟆闭剳賽?- 繼承(五)

    摘要:面向?qū)ο罄镒畲蟮奶攸c(diǎn)應(yīng)該就屬繼承了。在第二篇文章里說過原型實(shí)例跟構(gòu)造函數(shù)之間的繼承,并且還講了一道推算題。 通過上一篇文章想必各位老鐵已經(jīng)熟悉了class了,這篇文章接著介紹繼承。面向?qū)ο罄镒畲蟮奶攸c(diǎn)應(yīng)該就屬繼承了。一個(gè)項(xiàng)目可能需要不斷的迭代、完善、升級(jí)。那每一次的更新你是要重新寫呢,還是在原有的基礎(chǔ)上改吧改吧呢?當(dāng)然,不是缺心眼的人肯定都會(huì)在原來的基礎(chǔ)上改吧改吧,那這個(gè)改吧改吧就需要...

    Airmusic 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<