原型
原型是 javascript 面向對象特性中重要的概念,也是大家太熟悉的概念。因為在絕大多
數的面向對象語言中,對象是基于類的(例如 java 和 c++ ) ,對象是類執行個體化的結果。而在
javascript 語言中,沒有類的概念
① ,對象由對象執行個體化。打個比方來說,基于類的語言中類
就像一個模具,對象由這個模具澆注産生,而基于原型的語言中,原型就好像是一件藝術品
的原件,我們通過一台 100% 精确的機器把這個原件複制出很多份。
前面小節的例子中都沒有涉及原型,僅僅通過構造函數和 new 語句生成類,讓我們看
看如何使用原型和構造函數共同生成對象。
function person() {}
person.prototype.name = ‘byvoid’;
person.prototype.showname = function() {
console.log(this.name);
};
var person = new person();
person.showname();
上面這段代碼使用了原型而不是構造函數初始化對象。 這樣做與直接在構造函數内定義
屬性有什麼不同呢?
**構造函數内定義的屬性繼承方式與原型不同, 子對象需要顯式調用父對象才能繼承構
造函數内定義的屬性。
構造函數内定義的任何屬性, 包括函數在内都會被重複建立, 同一個構造函數産生的
兩個對象不共享執行個體。**
構造函數内定義的函數有運作時閉包的開銷, 因為構造函數内的局部變量對其中定義
的函數來說也是可見的。
下面這段代碼可以驗證以上問題:
function foo() {
var innervar = ‘hello’;
this.prop1 = ‘byvoid’;
this.func1 = function() {
innervar = ”;
}
foo.prototype.prop2 = ‘carbo’;
foo.prototype.func2 = function() {
console.log(this.prop2);
var foo1 = new foo();
var foo2 = new foo();
console.log(foo1.func1 == foo2.func1); // 輸出 false
console.log(foo1.func2 == foo2.func2); // 輸出 true
盡管如此,并不是說在構造函數内建立屬性不好,而是兩者各有适合的範圍。那麼我們
什麼時候使用原型,什麼時候使用構造函數内定義來建立屬性呢?
**除非必須用構造函數閉包,否則盡量用原型定義成員函數,因為這樣可以減少開銷。
盡量在構造函數内定義一般成員, 尤其是對象或數組, 因為用原型定義的成員是多個
執行個體共享的。**
接下來,我們介紹一下javascript中的原型鍊機制。
原型鍊
javascript 中有兩個特殊的對象: object 與 function ,它們都是構造函數,用于生
成對象。 object.prototype 是所有對象的祖先, function.prototype 是所有函數的原
型,包括構造函數。我把 javascript 中的對象分為三類,
一類是使用者建立的對象,
一類是構造函數對象,
一類是原型對象。
使用者建立的對象,即一般意義上用 new 語句顯式構造的對象。
構造函數對象指的是普通的構造函數,即通過 new 調用生成普通對象的函數。
原型對象特指構造函數 prototype 屬性指向的對象。
這三類對象中每一類都有一個 proto 屬性,它指向該對象的原型,從任何對象沿着它開始周遊都可以追溯到 object.prototype 。
構造函數對象有 prototype 屬性,指向一個原型對象,通過該構造函數建立對象時,被建立對象的 proto 屬性将會指向構造函數的 prototype 屬性。
原型對象有 constructor屬性,指向它對應的構造函數。讓我們通過下面這個例子來了解原型:
function foo() {}
object.prototype.name = ‘my object’;
foo.prototype.name = ‘bar’;
var obj = new object();
var foo = new foo();
console.log(obj.name); // 輸出 my object
console.log(foo.name); // 輸出 bar
console.log(foo.proto.name); // 輸出 bar
console.log(foo.proto.proto.name); // 輸出 my object
console.log(foo.proto.constructor.prototype.name); // 輸出 bar
我們定義了一個叫做 foo () 的構造函數,生成了對象 foo 。同時我們還分别給 object和 foo 生成原型對象。
下圖解析了它們之間錯綜複雜的關系。
對象的複制
javascript 和 java 一樣都沒有像c語言中一樣的指針,所有對象類型的變量都是指向對
象的引用,兩個變量之間指派傳遞一個對象并不會對這個對象進行複制,而隻是傳遞引用。
有些時候我們需要完整地複制一個對象,這該如何做呢? java 語言中有 clone 方法可以實
現對象複制,但 javascript 中沒有這樣的函數。是以我們需要手動實作這樣一個函數,一個
簡單的做法是複制對象的所有屬性:
object.prototype.clone = function() {
var newobj = {};
for (var i in this) {
newobj[i] = this[i];
return newobj;
var obj = {
name: ‘byvoid’,
likes: [‘node’]
var newobj = obj.clone();
obj.likes.push(‘python’);
console.log(obj.likes); // 輸出 [ ‘node’, ‘python’ ]
console.log(newobj.likes); // 輸出 [ ‘node’, ‘python’ ]
上面的代碼是一個對象淺拷貝(shallow copy)的實作,即隻複制基本類型的屬性,而
共享對象類型的屬性。 淺拷貝的問題是兩個對象共享對象類型的屬性, 例如上例中 likes 屬
性指向的是同一個數組。
實作一個完全的複制,或深拷貝(deep copy)并不是一件容易的事,因為除了基本資料
類型,還有多種不同的對象,對象内部還有複雜的結構,是以需要用遞歸的方式來實作:
if (typeof(this[i]) == ‘object’ || typeof(this[i]) == ‘function’) {
newobj[i] = this[i].clone();
} else {
array.prototype.clone = function() {
var newarray = [];
for (var i = 0; i < this.length; i++) {
newarray[i] = this[i].clone();
newarray[i] = this[i];
return newarray;
function.prototype.clone = function() {
var that = this;
var newfunc = function() {
return that.apply(this, arguments);
newfunc[i] = this[i];
return newfunc;
likes: [‘node’],
display: function() {
},
newobj.likes.push(‘python’);
console.log(obj.likes); // 輸出 [ ‘node’ ]
console.log(newobj.display == obj.display); // 輸出 false
上面這個實作看起來很完美,它不僅遞歸地複制了對象複雜的結構,還實作了函數的深
拷貝。這個方法在大多數情況下都很好用,但有一種情況它卻無能為力,例如下面的代碼:
var obj1 = {
ref: null
var obj2 = {
ref: obj1
obj1.ref = obj2;
這段代碼的邏輯非常簡單,就是兩個互相引用的對象。當我們試圖使用深拷貝來複制
obj1 和 obj2 中的任何一個時,問題就出現了。因為深拷貝的做法是遇到對象就進行遞歸
複制,那麼結果隻能無限循環下去。對于這種情況,簡單的遞歸已經無法解決,必須設計一
套**圖論算法, 分析對象之間的依賴關系, 建立一個拓撲結構圖, 然後分别依次複制每個頂點,
并重新建構它們之間的依賴關系**。