簡體   English   中英

了解JavaScript中的原型繼承

[英]Understanding prototypal inheritance in JavaScript

我是JavaScript OOP的新手。 你能解釋下面的代碼塊之間的區別嗎? 我測試了兩個塊都有效。 什么是最佳實踐,為什么?

第一塊:

 function Car(name){ this.Name = name; } Car.prototype.Drive = function(){ console.log("My name is " + this.Name + " and I'm driving."); } SuperCar.prototype = new Car(); SuperCar.prototype.constructor = SuperCar; function SuperCar(name){ Car.call(this, name); } SuperCar.prototype.Fly = function(){ console.log("My name is " + this.Name + " and I'm flying!"); } var myCar = new Car("Car"); myCar.Drive(); var mySuperCar = new SuperCar("SuperCar"); mySuperCar.Drive(); mySuperCar.Fly(); 

第二塊:

 function Car(name){ this.Name = name; this.Drive = function(){ console.log("My name is " + this.Name + " and I'm driving."); } } SuperCar.prototype = new Car(); function SuperCar(name){ Car.call(this, name); this.Fly = function(){ console.log("My name is " + this.Name + " and I'm flying!"); } } var myCar = new Car("Car"); myCar.Drive(); var mySuperCar = new SuperCar("SuperCar"); mySuperCar.Drive(); mySuperCar.Fly(); 

為什么作者使用prototype添加了DriveFly方法,並沒有將它們聲明為Car類中的this.Drive方法以及SuperCar類中的this.Fly

為什么需要將SuperCar.prototype.constructor設置回SuperCar 設置prototype時是否重寫constructor屬性? 我評論了這一行並沒有改變。

為什么叫Car.call(this, name); SuperCar構造函數中? 當我這樣做時, Car屬性和方法不會被“繼承”

var myCar = new Car("Car");

要添加到Norbert Hartl的答案中 ,不需要SuperCar.prototype.constructor,但是有些人使用它作為獲取對象的構造函數的便捷方式(在這種情況下為SuperCar對象)。

從第一個例子開始,Car.call(this,name)在SuperCar構造函數中,因為當你這樣做時:

var mySuperCar = new SuperCar("SuperCar");

這就是JavaScript的作用:

  1. 一個新的空白對象被實例化。
  2. 新鮮物體的內部原型設置為Car。
  3. SuperCar構造函數運行。
  4. 返回完成的對象並在mySuperCar中設置。

請注意JavaScript沒有為您調用Car。 原型就像它們一樣,你沒有為SuperCar設置的任何屬性或方法都將在Car中查找。 有時這很好,例如SuperCar沒有Drive方法,但它可以共享Car的方法,因此所有SuperCars都將使用相同的Drive方法。 其他時候你不想分享,比如每個SuperCar都有自己的名字。 那么如何將每個SuperCar的名稱設置為它自己的東西呢? 你可以在SuperCar構造函數中設置this.Name:

function SuperCar(name){
    this.Name = name;
}

這有效,但等一下。 我們不是在Car構造函數中做同樣的事情嗎? 不想重復自己。 由於Car已經設置了名稱,我們只需要調用它。

function SuperCar(name){
    this = Car(name);
}

哎呀,你永遠不想改變特殊的this對象引用。 還記得4個步驟嗎? 掛在JavaScript給你的對象上,因為這是保持SuperCar對象和Car之間珍貴的內部原型鏈接的唯一方法。 那么我們如何設置Name,而不是重復自己並且不丟棄我們新鮮的SuperCar對象JavaScript花了這么多特別的努力為我們做准備?

兩件事情。 一:的意思this是靈活的。 二:汽車是一種功能。 可以調用Car,而不是使用原始的,新鮮的實例化對象,而是使用SuperCar對象。 這為我們提供了最終解決方案,這是您問題中第一個示例的一部分:

function SuperCar(name){
    Car.call(this, name);
}

作為一個功能,車不能用函數的調用調用方法 ,改變的意思this車內我們打造了超級跑車的實例。 普雷斯托! 現在每個SuperCar都有自己的Name屬性。

要結束,SuperCar構造函數中的Car.call(this, name)為每個新的SuperCar對象提供了它自己唯一的Name屬性,但沒有復制已經在Car中的代碼。

一旦你理解了它們,原型並不可怕,但它們根本不像經典的類/繼承OOP模型。 在JavaScript中寫了一篇關於原型概念的文章。 它是為使用JavaScript的游戲引擎編寫的,但它與Firefox使用的JavaScript引擎相同,因此它應該都是相關的。 希望這可以幫助。

這兩個塊的區別在於,在第一個示例中, Drive()僅存在一次,而在第二個方法中,每個實例都存在Drive() (每次執行new Car() ,將再次創建函數drive() ) 。 或者說不同的是第一個使用原型來存儲函數而第二個使用構造函數。 函數的查找是構造函數,然后是原型。 因此,對於您的Drive()查找,無論它是在構造函數中還是在原型中,它都會找到它。 使用原型更有效,因為通常每種類型只需要一次函數。

javascript中的new調用會自動在原型中設置構造函數。 如果要覆蓋原型,則必須手動設置構造函數。

javascript中的繼承沒有像super 因此,如果你有一個子類,那么調用超級構造函數的唯一機會就是它的名字。

諾伯特,你應該注意到你的第一個例子就是道格拉斯·克羅克福德所謂的偽經典繼承。 關於此事需要注意的事項:

  1. 您將調用Car構造函數兩次,一次來自SuperCar.prototype = new Car()行,另一次來自“構造函數竊取”行Car.call(這...你可以創建一個幫助方法繼承原型而不是你的汽車構造函數只需運行一次,使設置更有效。
  2. SuperCar.prototype.constructor = SuperCar行將允許您使用instanceof來標識構造函數。 有些人希望其他人只是避免使用instanceof
  3. 參考變量如:var arr = ['one','two']在超級(例如Car)上定義時將被所有實例共享。 這意味着inst1.arr.push ['three'],inst2.arr.push ['four']等將出現在所有實例中! 基本上,您可能不想要的靜態行為。
  4. 第二個塊在構造函數中定義了fly方法。 這意味着每次調用它時,都會創建一個“方法對象”。 最好使用原型方法! 但是如果你願意,你可以將它保存在構造函數中 - 你只需要保護,這樣你只需要初始化一次原型文字(偽):if(SuperCar.prototype.myMethod!='function')...然后定義你的原型文字。
  5. '為什么要調用Car.call(這個,名字)....':我沒有時間仔細查看你的代碼,所以我可能錯了,但通常這樣每個實例都可以保持自己的狀態來修復我在上面描述的原型鏈的'staticy'行為問題。

最后,我想提一下我在這里有幾個TDD JavaScript繼承代碼的例子: TDD JavaScript繼承代碼和論文我很想得到你的反饋,因為我希望改進它並保持開源。 目標是幫助經典程序員快速掌握JavaScript,並補充Crockford和Zakas書籍的研究。

function abc() {
}

為函數abc創建的原型方法和屬性

abc.prototype.testProperty = 'Hi, I am prototype property';
abc.prototype.testMethod = function() { 
   alert('Hi i am prototype method')
}

為函數abc創建新實例

var objx = new abc();

console.log(objx.testProperty); // will display Hi, I am prototype property
objx.testMethod();// alert Hi i am prototype method

var objy = new abc();

console.log(objy.testProperty); //will display Hi, I am prototype property
objy.testProperty = Hi, I am over-ridden prototype property

console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property

http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html

我不是100%肯定,但我相信不同之處在於第二個示例只是將Car類的內容復制到SuperCar對象中,而第一個示例將SuperCar原型鏈接到Car類,因此運行時更改為Car類也會影響SuperCar類。

這里有幾個問題:

能否請您解釋以下代碼塊之間的區別。 我測試了兩個塊都有效。

第一個只創建一個Drive函數,第二個創建其中兩個:一個在myCar ,另一個在mySuperCar

下面是執行第一個或第二個塊時會產生不同結果的代碼:

myCar.Fly === mySuperCar.Fly // true only in the first case
Object.keys(myCar).includes("Fly") // true only in the second case
Object.keys(Car.prototype).length === 0 // true only in the second case

什么是最佳實踐,為什么?
為什么作者使用prototype添加DriveFly方法,但是沒有將它們聲明為Car類中的this.Drive方法和SuperCar類中的this.Fly

最好在原型上定義方法,因為:

  • 每個方法只定義一次
  • 每個方法也可用於在不執行構造函數的情況下創建的實例(調用Object.create(Car.prototype)時就是這種情況);
  • 您可以檢查實例原型鏈中的哪個級別定義了某個方法。

為什么需要將SuperCar.prototype.constructor設置回SuperCar 設置prototype時是否重寫constructor屬性? 我評論了這一行並沒有改變。

設置prototype時,不會覆蓋constructor屬性。 但是new Car()的構造函數是Car ,所以如果你將new Car()設置為SuperCar.prototype ,那么顯然SuperCar.prototype.constructor就是Car

只要你不重新分配prototype ,就有一個不變性: Constructor.prototype.constructor === Constructor 例如,對於CarCar.prototype.constructor === Car ,這是正確的,但對於ArrayObjectString等等同樣如此。

但是如果你將一個不同的對象重新分配給prototype ,那么這種不變性就會被打破。 通常這不是問題(正如您所注意到的),但最好還原它,因為它回答了“在創建新實例時哪個構造函數使用此原型對象?”的問題。 有些代碼可能會進行此類檢查並依賴於此。 請參閱“為什么有必要設置原型構造函數?” 對於這種情況。

為什么叫Car.call(this, name); SuperCar構造函數? 當我這樣做時,Car的屬性和方法不會被“繼承”

 var myCar = new Car("Car"); 

如果你不做Car.call(this, name); 那么你的SuperCar實例將沒有name屬性。 你當然可以決定這樣做this.name = name; 相反,它只是復制Car構造函數中的代碼,但在更復雜的情況下,這樣的代碼重復將是不好的做法。

SuperCar構造函數中調用new Car(name)會沒有用,因為這會創建另一個對象,而你真的需要擴展this對象。 通過不使用new (使用call代替),你實際上告訴Car函數不作為構造函數運行(即創建新對象),而是使用傳遞給它的對象。

時代變了

在現代版本的JavaScript中,您可以使用super(name)而不是Car.call(this, name)

function SuperCar(name) {
    super(name);
}

今天,您還將使用class語法並從問題中編寫第一個代碼塊,如下所示:

 class Car { constructor(name) { this.name = name; } drive() { console.log(`My name is ${this.name} and I'm driving.`); } } class SuperCar extends Car { constructor(name) { super(name); } fly() { console.log(`My name is ${this.name} and I'm flying!`); } } const myCar = new Car("Car"); myCar.drive(); const mySuperCar = new SuperCar("SuperCar"); mySuperCar.drive(); mySuperCar.fly(); 

請注意,您甚至不必提及prototype屬性來實現目標。 class ... extends語法還負責將prototype.constructor屬性設置為你問題中的第一個塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM