原文地址javascript
這篇文章事後,你會以爲,在 JavaScript 中編寫繼承的代碼不存在所謂的「聖盃」。我將介紹一些經常使用的方法,指出他們存在的問題(優劣),其中一個可能就是你所須要的。最重要的是,這是一課,能夠用不一樣語法完成相同的事情,讓你看見美妙而神奇的 JavaScript 世界。css
先從一個繼承的例子開始,看下面代碼的優劣是什麼。html
這個例子很簡單,只是從一個普通的對象繼承。java
代碼段 1:閉包
// Basic object
var Base = {
// Public properties and methods
dayName: "Tuesday",
day: this.dayName,
getDay: function () {
return this.dayName;
},
setDay: function (newDayName) {
this.dayName = newDayName;
}
};
目前爲止的代碼還能夠。讓咱們建立一個對象 Sub,它繼承 Base 對象。app
代碼段 2:函數
// Using new Base() is not an option,
// since it isn't a constructor
Sub.prototype = Base;
function Sub() {
// Constructor
};
代碼段 3:測試
var a = new Sub();
// Returns "Tuesday"
alert(a.getDay());
var b = new Sub();
// Returns "Tuesday"
alert(b.getDay());
// Sets dayName to "Wednesday"
a.setDay("Wednesday");
// Returns "Wednesday"
alert(a.getDay());
// Returns "Tuesday"
alert(b.getDay());
// Returns undefined
alert(b.day);
除了最後的那個返回 undefined 外,其餘都跟預期的同樣。問題就在於,當執行時,Base 對象嘗試設置其屬性,但沒有任何內部的引用。對 day 屬性使用一個方法能夠解決這個問題。this
代碼段 4:spa
day: function () {
return this.dayName;
}
正如上面討論的,你能夠指定 Base 對象做爲祖先來從原型(prototype)繼承,但由於 Base 不是真正的構造函數,你沒有用 new 關鍵字來調用它。基本上,這意味着,直到 Sub 對象的一個實例被建立,Base 構造函數纔會執行。這是好事,由於在真正須要以前,你指望任何沒必要要的代碼在構造函數中執行。就我我的而言,我不會在構造函數中聽任何重要的東西,而是放一個init 方法,這樣,當它被調用時,我就可以徹底了。
這裏很差的地方是,全部的屬性和方法都被聲明爲 Base 對象的內聯,這就意味着,你不能利用原型的行爲。另外,該方法沒有途徑聽任何的私有變量。
不少人都問個人一件事是,若是對象中有私有變量,那麼,可否經過原型方法訪問它們。答案是,很不幸——不能。然而,對於私有方法,仍是能作到的,正以下面講到的。
從這裏開始,爲了完成同一個結果,我將提出三種不一樣的語法。這三種語法都是很是不一樣的方式。另外,我也將展現私有變量和原型存在的問題,以及它們如何與私有方法一塊兒工做。
首先是通常的原型繼承,聲明一個私有變量和私有方法。
代碼段 5:
// Basic Prototype inheritance
function Base() {
// Private variable
var dayName = "Tuesday";
// Private method
function getPrivateDayName() {
return dayName;
}
// Public properties and methods
this.day = dayName;
this.getDay = function () {
return getPrivateDayName();
};
this.setDay = function (newDayName) {
dayName = newDayName;
};
};
Sub.prototype = new Base;
function Sub() {
// Constructor
};
語法簡單,從構造函數內能夠訪問全部東西。
若想訪問私有變量和私有方法,全部的東西都須要放在構造函數內,而不是經過原型。
這不是推薦的方法。
當涉及到單件對象(singleton object)時,我我的最喜歡 Yahoo JavaScript 模塊模式(Yahoo JavaScript Module Pattern)。對於原型繼承,你也能夠對任何 Sub 對象使用它做爲原型的祖先對象,以下所示:
代碼段 6:
// Yahoo JavaScript Module Pattern
var Base = function () {
// Private variable
var dayName = "Tuesday";
// Private method
var getPrivateDayName = function () {
return dayName;
}
// Public properties and methods
return {
day: dayName,
getDay: function () {
return getPrivateDayName.call(this);
},
setDay: function (newDayName) {
dayName = newDayName;
}
};
} ();
// Using new Base() is not an option,
// since it isn't a constructor
Sub.prototype = Base;
function Sub() {
// Constructor
};
這個代碼結構還不錯,把私有和公共屬性之間進行了很好地分離。變量 dayName 是私有變量,從外部沒法訪問,只能經過 getDay 和 setDay 函數訪問。
Sub.prototype = Base 這不是真正的構造函數,不能用 new 調用它。另外,return {…} 中全部的公共屬性和方法都內聯(inline)在對象中,所以,沒有利用推薦的原型方法。
下面代碼,建立一個閉包,裏邊有構造函數,私有變量和方法,並把原型屬性和方法指定給對象。而後,它返回實際的構造函數對象,所以,下一次運行時,它的行爲就跟一個正常的構造函數同樣,同時,閉包全部的屬性和方法都仍然可訪問。
到目前爲止,這是最優雅的代碼。
代碼段 7:
// Closure-created constructor
var Base = (function () {
// Constructor
function Base() {
}
// Private variable
var dayName = "Tuesday";
// Private method
function getPrivateDayName() {
return dayName;
}
// Public properties and methods
Base.prototype.day = dayName;
Base.prototype.getDay = function () {
return getPrivateDayName.call(this);
};
Base.prototype.setDay = function (newDayName) {
dayName = newDayName;
};
return Base;
})();
Sub.prototype = new Base;
function Sub() {
// Constructor
};
與代碼段 6 相比,該代碼使用了閉包,注意代碼中的,公共屬性和方法:day 變量、getDay 和 setDay 函數,有一個已初始化、具備對原型屬性和方法進行徹底控制的構造函數,反過來,又能夠訪問私有變量和方法。這個結構很是好,由於,在同一個代碼塊中,你有構造函數,屬性和方法。
惟一真正的缺點是,私有變量被限制到做用域,所以對於全部實例都是相同的。此外,構造函數外有私有變量,這點顯得有點怪異。
對上面三種語法都使用下面代碼測試。
代碼段 8:
var a = new Sub();
// Returns "Tuesday"
alert(a.getDay());
var b = new Sub();
// Returns "Tuesday"
alert(b.getDay());
// Sets dayName to "Wednesday"
a.setDay("Wednesday");
// Returns "Wednesday"
alert(a.getDay());
// Returns "Wednesday"
alert(b.getDay());
// Returns "Tuesday"
alert(b.day);
爲何會有這樣的結果?三種方法都建立一個私有變量,執行得也很好,結果也相同,但致使了,若是你改變某個實例對象的一個私有變量,那麼,對全部的實例對象都改變了。也就是說,對上面三個方法,實例對象 a 若改變其私有變量 dayName,那麼實例對象 b 的私有變量也會被改變。這樣,它就更像一個私有的靜態屬性,而不是一個真正的私有屬性。
So, if you want to have something private, more like a non-public constant, any of the above approaches is good, but not for actual private variables. Private variables only work really well with singleton objects in JavaScript.
因此,若是您想擁有某個私人的東西,它更像是一個非公有的常量(靜態的,如 dayName 變量),而不是一個真正的私有變量,上面任何一個方法均可以。JavaScript 中,私有變量只在單件對象(singleton object)會很好的工做。
然而,對於私有方法,它的執行顯得臃腫!你不公開它,而是在原型代碼中使用它。
正如你能夠看到,JavaScript 提供不少方法來作相同的事情,不一樣的解決方案有不一樣的優勢和缺點。我也認爲,一個重要的經驗是,在代碼所能作的(提供預期結果)與對運行時、執行和重用來講最優之間,是不一樣的。選擇對你來講,最合適的方式。