寫文章之前先講個笑話,活躍一下氣氛——
校長:“先生,我有一個好訊息,一個壞訊息,都是關於您兒子的。” 家長:“校長,先說壞訊息吧!”
校長:“壞訊息是,您的兒子的動作十分女性化。” 家長:“那好訊息呢?” 校長:“好訊息是他現在是本校校花。”
言歸正傳,先放原文地址:
http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
這是我第一次翻譯別人的文章,不到之處,敬請諒解。
全文:
javascript——原型繼承到底是如何?的
在網上,我們到處都看到javascript是基於原型的繼承。儘管如此,javascript預設僅僅只是通過new操作符提供一種特殊形式的原型繼承
因此,許多言論和解釋都是難以理解的。本文就是剖析原型繼承的實質以及如何使用原型繼承。
原型繼承定義
當我們遇到原型繼承時,我們通常會看到如下的一段解釋:
當需要讀取一個對象的屬性時,javascript會沿著原型鏈向上尋找直到發現吻合的屬性為止。
在多數的javascript實現中,__proto__用來表示原型鏈的下一個對象,
在接下來的內容中,我們會發現__proto__與prototype的不同之處。
Note:__proto__是非標準的 ,因此不應該出現在你的正式代碼中,本文使用這個屬性,只為明理,而非實用。
下面的代碼揭示了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
}
我們舉一個通俗的例子:二位座標點。一個點有x、y兩個座標和一個print方法。
遵循上面的原型繼承定義,我們來產生一個具有3個屬性(x、y、print)的對象,為了建立一個新的點,用__proto__來建立一個點對象。
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的發明人)想讓javascript看起來與一些傳統的物件導向語言(比如Java,C++)跟接近,在哪些傳統語言中,通過new操作符來建立一個類的執行個體,因此,他也跟風實現了new操作符。
C++有建構函式的概念,用來初始化一個執行個體屬性。因此,new操作符必須用在一個函數上。
對象的方法需要有地方存放,鑒於我們在使用原型式語言,我們就把他放在函數的prototype屬性裡面
new操作符就像含有一個F函數,以及參數new F(arguments),幾個步驟如下:
1、建立一個類的執行個體,得到的是一個__proto__指向F。protoype的Null 物件。
2、初始化前面得到的Null 物件,也就是執行個體。在這一步,F函數被調用,並且將參數傳遞進來,將this指向第一步的執行個體。
3、返回這個執行個體
可能說的不好理解,我們用代碼方式來實現他的的步驟:
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
javacsript中真正的原型繼承
javascript標準僅僅給出類new操作符。儘管如此,Douglas Crockford找到了一個方法利用new來實現真正的原型繼承。他寫出了如下的 Object.create函數:
Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new F();
};
這樣看起來很怪異,但是卻是很簡單。這個函數僅僅建立了新對象,並且他的原型可以指向任何你想要指向的位置,如果我們可以隨意使用__proto__,我們也可以這麼寫:
Object.create = function (parent) {
return { '__proto__': parent };
};
下面用真正的原型繼承來改寫我們上面的座標點例子:
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 和 __proto__)使用起來還是有一些缺點的:
1、不是標準方法:__proto__是不標準甚至是已淘汰的,原生的Object.create方法與Douglas Crockford的實現並不是完全等價的。
2、不夠最佳化:Object.create (不管是原生的還是模仿的)都不如new操作符最佳化,速度差距可能達到10倍。