2014-10-27 48 views
1

假設我正在拍攝應用程序中的一些基本繼承,我可以通過將我的孩子的原型設置爲父項來實現此目的。在JavaScript中安全地繼承原型

// Parent "class" 
var Car = function(year) { 
    this.year = year; 
}; 

Car.prototype.drive = function() { 
    console.log('Vrooom'); 
}; 

// Child "class" 
var Toyota = function() { 
    Car.apply(this, arguments); 
}; 

Toyota.prototype = Car.prototype; 

var myCar = new Car(2010), myToyota = new Toyota(2010); 

myCar.drive(); // Vrooom 
myToyota.drive(); // Vrooom 

似乎工作,但顯然是不好的,因爲如果我設置一個新的方法對我Toyota.prototype,它將在Car的原型來設定。

Toyota.prototype.stop = function() { 
    console.log('stooop'); 
}; 

myCar.stop(); // stooop <--- bad 
myToyota.stop(); // stooop <--- good 

爲了解決這個問題,而不是Toyota.prototype = Car.prototype;我可以添加一箇中介:

var ctor = function() { }; 
ctor.prototype = Car.prototype; 

Toyota.prototype = new ctor(); 

Toyota.prototype.stop = function() { 
    console.log('stooop'); 
}; 

myCar.stop(); // throws undefined error <--- good 
myToyota.stop(); // stooop <--- good 

但我不明白爲什麼這個工程。 ctor創建一個新的實例,其原型設置爲Car's原型,然後Toyota將其原型設置爲該新實例。

但是,爲什麼要創建一個空的對象,其原型被Toyota的原型引用?
爲什麼不在Toyota上設置一個新方法將它設置爲Car就像它在第一個例子中那樣? 如果我想要幾層繼承,那麼我需要每次都將它們粘貼在一起,並且每次都粘貼new ctor

+1

也許下面的答案會對你有幫助,它涵蓋構造函數,原型,繼承和混合ins:http://stackoverflow.com/a/16063711/1641941 – HMR 2014-10-27 23:53:48

+0

@spender你的意思是'Toyota.prototype = Object.create(Car.prototype)'不是嗎?最好不要創建一個Parent實例作爲Child的原型。 – HMR 2014-10-27 23:54:53

+0

@HMR:是的,我刪除了我的答案,因爲我很清楚,我在JS中的繼承知識有些缺陷。忘了也刪​​除評論。 – spender 2014-10-28 00:17:09

回答

2

但爲什麼創建與得到由豐田的原型引用 原型一個空對象?

因爲如果你這樣做:Toyota.prototype = new Car();

現在豐田的原型是汽車,實例,這意味着除了具有汽車'鏈中的原型,它也具有在構造函數本身中設置的任何屬性。基本上你不會想要Toyota.prototype有一個屬性叫做year像這樣:Toyota.prototype.year。因此這是一個很大最好是有一個空的構造,像這樣:

var ctor = function() { }; 
ctor.prototype = Car.prototype; 

Toyota.prototype = new ctor(); 

現在Toyota.prototypenew ctor()實例作爲它的原型,該消息又在自己的鏈Car.prototype。這意味着豐田的實例現在可以執行Car.prototype中存在的方法。


爲什麼不設置上汽車豐田設定一個新的方法,像它在第一個例子呢 ?

用此:Toyota.prototype = Car.prototype;您將豐田的原型設置爲與Car.prototype包含的原型相同。既然它是同一個對象,在一個地方改變它也會在其他地方改變它。這意味着傳遞的對象是引用而不是值,這是另一種說法,即當您將對象分配給3個不同的變量時,無論使用哪個變量,它都是相同的對象。例如字符串按值傳遞。這裏有一個字符串的例子:

var str1 = 'alpha'; 
var str2 = str1; 
var str3 = str1; 

str2 = 'beta'; 

// Changing str2 doesn't change the rest. 
console.log(str1); //=> "alpha" 
console.log(str3); //=> "alpha" 
console.log(str2); //=> "beta" 

現在比較對象及其屬性;

var obj1 = {name: 'joe doe'}; 
var obj2 = obj1; 
var obj3 = obj1; 

console.log(obj1.name); //=> "joe doe" 
console.log(obj2.name); //=> "joe doe" 
console.log(obj3.name); //=> "joe doe" 

obj2.name = 'JOHN SMITH'; 

console.log(obj1.name); //=> "JOHN SMITH" 
console.log(obj2.name); //=> "JOHN SMITH" 
console.log(obj3.name); //=> "JOHN SMITH" 


如果我要繼承好幾層...

下面是一個創建繼承層的另一種方式:

var Car = function(year) { 
    this.year = year; 
}; 

Car.prototype.drive = function() { 
    console.log('Vrooom'); 
}; 

var Toyota = function() { 
    Car.apply(this, arguments); 
}; 

// `Object.create` does basically the same thing as what you were doing with `ctor()` 
// Check out the documentation for `Object.create` as it takes a 2nd argument too. 
Toyota.prototype = Object.create(Car.prototype); 


// Create instances 
var 
    myCar = new Car(2010), 
    myToyota = new Toyota(2010); 


// Add method to Toyota's prototype 
Toyota.prototype.stop = function() { 
    console.log('stooop'); 
}; 

讓我們試試它現在出來:

myToyota.stop(); //=> 'stooop' 
myCar.stop(); //=> 'TypeError: undefined is not a function' 

myCar.drive(); //=> Vrooom 
myToyota.drive(); //=> Vrooom 
+1

謝謝!這一切都是有道理的,你是唯一一個不告訴我只是將它設置爲'new Car()'的實例:D – diplosaurus 2014-10-27 22:24:23

+2

我刪除了我對此的答案... +1 – spender 2014-10-27 23:11:30

+0

最好不要使用create a instance Parent的設置爲Child的原型。父可能有不應該在Child.prototype上的實例特定成員,或者當您定義Child時,您無法創建父實例,因爲它取決於應用程序尚未達到的特定應用程序狀態定義孩子。改用Object.create。 – HMR 2014-10-27 23:52:50

1

你的問題是以下行:

Toyota.prototype = Car.prototype;

再後來修改該對象:因爲在第一行,你設置Toyota.prototype完全相同的對象Car.prototype

Toyota.prototype.stop = function() { 
    console.log('stooop'); 
}; 

這不是一個副本,它是對同一個對象的引用!因此,只要您在Toyota.prototype上修改stop,您實際上修改了Toyota.prototypeCar.prototype,因爲它是一樣的。

你真的想要做的就是用替換的第一行是什麼:

Toyota.prototype = Object.create(Car);

所以,你現在有Toyota點的原型到Car功能,因爲它應該,而不是Car自己的prototype。恭喜,你已經使用了原型鏈! (注:使用Object.create做的類繼承本質上是更加穩定可靠,因爲它不跑,你可能已經包含在Car功能的代碼,但是隻設置了原型鏈接)。

進一步討論關於這裏正在發生的事情,以及爲什麼你可能會更好使用JS中的「類繼承」,你可能想閱讀Chapters 4-6 of You Don't Know JS (objects & prototypes)而不是

關於你最後一個問題:「如果我想要幾層繼承,那麼我需要每次都用新的ctor將它們粘合在一起?」 - 是的,你需要這樣做,這是JavaScript中(假)類和繼承的標準模式。

+1

最好不要使用創建父實例來設置爲Child的原型。父可能有不應該在Child.prototype上的實例特定成員,或者當您定義Child時,您無法創建父實例,因爲它取決於應用程序尚未達到的特定應用程序狀態定義孩子。改用Object.create。 – HMR 2014-10-27 23:52:35

+0

最好不要使用這些「class」結構,並在所有地方使用Object.create。但是的確如此。 – yerforkferchips 2014-10-28 07:25:09

+0

所以你建議不使用構造函數?在任何情況下;你是從Douglass Crockford那裏得到的嗎?你知道他還沒有爲「古典繼承」產生正確的代碼嗎?你將如何去初始化特定於實例的成員,以及關於構造函數的真正壞處是什麼? – HMR 2014-10-28 09:27:44