【翻譯】JavaScript原型繼承工作原理
Javascript – 如何真正實現原型鏈繼承
本文轉載自:衆成翻譯
譯者:Daguo
鏈接:http://www.zcfy.cc/article/1337
原文:http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
在網上的很多地方我們可以得知javascript是基於原型鏈繼承的,其實Javascript只提供一種特殊的方法來實現原型鏈繼承,就是通過new操作。但是很多解讀都令人難以理解,這篇文章旨在說明到底什麼是原型鏈繼承以及如何在Javascript中真正地運用它。
原型鏈繼承的定義
當你讀到有關於Javascript原型鏈繼承的內容時,經常看到這樣的定義:
當對象存取一個屬性時,Javascript會沿着原型鏈向上查詢直到找到要存取的屬性名(或原型鏈頂端)Javascript Garden
許多Javascript實踐運用 proto 屬性來表示原型鏈上的下一個對象,我們會在下文看看__proto__和prototype之間有什麼區別。
提示: __proto__不是一種標準,不應該將它運用在你的實際代碼裏,它在文章只是用來解釋Javascript如何實現繼承。
下面的代碼展示了javascript引擎是如何獲取屬性的:
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop))
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop)
else
return undefined
}
我們舉個通常的栗子:一個2D的點,一個點有兩個座標值x和y,以及一個print方法。
根據前面原型鏈繼承的定義,我們將構造一個對象表示點,擁有三個屬性x,y,print,爲了創建一個新的點,我們只需要讓新的對象的__proto__設爲Point:
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = {x: 10, y: 20, __proto__: Point};
p.print(); // 10 20
Javascript原型鏈繼承的奇特之處
令人疑惑的是,每一個教別人Javascript原型鏈繼承的人都不用上面的代碼,他們會使用如下代碼:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () { console.log(this.x, this.y); }
};
var p = new Point(10, 20);
p.print(); // 10 20
這和之前的代碼完全不同,Point是一個函數,用到prototype屬性,new操作符,這是什麼意思?
new是如何運作的
Brendan Eich 希望Javascript看起來更像傳統的面嚮對象語言,比如Java 和C++,因此用new操作符來創建一個類的實例,於是他給Javascript添加了new操作符。
- C++中有關於構造的概念,是用來初始化實例屬性,因此new操作符必須指向一個函數。
- 我們需要在一些地方用到對象的方法,由於我們使用的是一門原型語言,可將方法放在函數的原型屬性上。
new操作符創建函數F和參數arguments : new F(arguments…),進行簡單的三步操作:
- 創建一個實例,它是一個空對象,其__proto__屬性被設爲F.prototype。
- 實例初始化,將函數F的參數傳遞到實例上,F的this(上下文)被設爲指向實例。
- 返回實例。
現在我們明白new操作符做了什麼,我們可以在Javascript中實踐它。
function New (f) {
/*1*/ var n = { '__proto__': f.prototype };
return function () {
/*2*/ f.apply(n, arguments);
/*3*/ return n;
};
}
一個小測試看看它如何運行。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () { console.log(this.x, this.y); }
};
var p1 = new Point(10, 20);
p1.print(); // 10 20
console.log(p1 instanceof Point); // true
var p2 = New (Point)(10, 20);
p2.print(); // 10 20
console.log(p2 instanceof Point); // true
Javascript真正的原型鏈繼承
在Javascript specifications 只給了我們一種基於new操作符的實現方法 ,但是Douglas Crockford 還是找到一種利用new操作符實現原型繼承的方法,他寫了一個Object.create函數。
Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new F();
};
這看起來非常奇怪,但它的原理其實非常簡單。將創建的一個新對象的prototype屬性設置爲你想要繼承的對象,如果允許使用__proto__,那麼可以改寫爲:
Object.create = function (parent) {
return { '__proto__': parent };
};
下面就是用真正的原型鏈繼承來實現的Point例子的繼承過程。
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = Object.create(Point);
p.x = 10;
p.y = 20;
p.print(); // 10 20
結論
我們已經知道了什麼是原型鏈繼承以及Javascript怎樣通過一種特殊的方式實現它。
但是,這種原型鏈繼承方法(Object.create and __proto__) 有一些缺點:
- 沒有標準: __proto__不是標準甚至是被反對的. 包括通用的Object.create和Douglas Crockford實踐都沒有確定的標準形式。
- 沒有優化: Object.create比起new操作方式顯得更加笨重,這一點被證明於10 times slower.
參考閱讀:
- Douglas Crockford: Prototypal Inheritance
- MDC Documentation: proto
- John Resig: getPrototypeOf
- Javascript Garden: Object.prototype
- Dmitry Shoshnikov: OOP: ECMAScript Implementation
- Angus Croll: Understanding Javascript prototypes
- Yehuda Katz: Understanding JavaScript Function Invocation and “this”