[英]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
添加了Drive
和Fly
方法,並沒有將它們聲明為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的作用:
請注意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
。 因此,如果你有一個子類,那么調用超級構造函數的唯一機會就是它的名字。
諾伯特,你應該注意到你的第一個例子就是道格拉斯·克羅克福德所謂的偽經典繼承。 關於此事需要注意的事項:
最后,我想提一下我在這里有幾個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
添加Drive
和Fly
方法,但是沒有將它們聲明為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
。 例如,對於Car
: Car.prototype.constructor === Car
,這是正確的,但對於Array
, Object
, String
等等同樣如此。
但是如果你將一個不同的對象重新分配給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.