Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20a%20%3D%20'global'%3B%0A%09(function%20()%20%7B%0A%09%20%20%20%20alert(a)%3B%0A%09%20%20%20%20var%20a%20%3D%20'local'%3B%0A%09%7D)()%3B%0A" wmode="transparent"> javascript
var a = 'global'; java
(function () { app
alert(a); 函數
var a = 'local'; this
})(); spa
你們第一眼看到這個例子以爲輸出結果是什麼?'global'?仍是'local'?其實都不是,輸出的是undefined,不用迷惑,個人題外話就是爲了講這個東西的。prototype
其實很簡單,看一看JavaScript運行機制就會明白。咱們能夠把這種現象看作"預聲明"。可是若是稍微深究一下,會明白得更透徹。設計
這裏其實涉及到對象屬性綁定機制。由於全部JavaScript函數都是一個對象。在函數裏聲明的變量能夠看作這個對象的"相似屬性"。對象屬性的綁定在語言裏是有分"早綁定"和"晚綁定"之分的。對象
【早綁定】是指在實例化對象以前定義其屬性和方法。解析程序時能夠提早轉換爲機器代碼。一般的強類型語言如C++,java等,都是早綁定機制的。而JavaScript不是強類型語言。它使用的是"晚綁定"機制。繼承
【晚綁定】是指在程序運行前,無需檢查對象類型,只要檢查對象是否支持特性和方法便可。能夠在綁定前對對象執行大量操做而不受任何懲罰。
上面代碼出現的"預聲明"現象,咱們大可用"晚綁定"機制來解釋。在函數的做用域中,全部變量都是"晚綁定"的。 即聲明是頂級的。因此上面的代碼和下面的一致:
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20a%20%3D%20'global'%3B%0A%09(function%20()%20%7B%0A%09%20%20%20%20var%20a%3B%0A%09%20%20%20%20alert(a)%3B%0A%09%20%20%20%20a%20%3D%20'local'%3B%0A%09%7D)()%3B%0A" wmode="transparent">
var a = 'global';
(function () {
var a;
alert(a);
a = 'local';
})();
在alert(a)以前只對a做了聲明而沒有賦值。因此結果可想而知。
在JavaScript裏,我所知道的幾種定義類和對象的方式:
使用直接量構建對象是最基礎的方式,但也有不少弊端。
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20Obj%20%3D%20new%20Object%3B%0A%09Obj.name%20%3D%20'sun'%3B%0A%09Obj.showName%20%3D%20function()%20%7B%0A%09%20%20%20%20alert('this.name')%3B%0A%09%7D%0A" wmode="transparent">
var Obj = new Object;
Obj.name = 'sun';
Obj.showName = function() {
alert('this.name');
}
咱們構建了一個對象Obj,它有一個屬性name,一個方法showName。可是若是咱們要再構建一個相似的對象呢?難道還要再重複一遍?NO!,咱們能夠用一個返回特定類型對象的工廠函數來實現。就像工廠同樣,流水線的輸出咱們要的特定類型結果。
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09function%20createObj(name)%20%7B%0A%09%20%20%20%20var%20tempObj%20%3D%20new%20Object%3B%0A%09%20%20%20%20tempObj.name%20%3D%20name%3B%0A%09%20%20%20%20tempObj.showName%20%3D%20function%20()%20%7B%0A%09%20%20%20%20%20%20%20%20alert(this.name)%3B%0A%09%20%20%20%20%7D%3B%0A%09%20%20%20%20return%20tempObj%3B%0A%09%7D%0A%09var%20obj1%20%3D%20createObj('obj_one')%3B%0A%09var%20obj2%20%3D%20createObj('obj_two')%3B%0A" wmode="transparent">
function createObj(name) {
var tempObj = new Object;
tempObj.name = name;
tempObj.showName = function () {
alert(this.name);
};
return tempObj;
}
var obj1 = createObj('obj_one');
var obj2 = createObj('obj_two');
這種工廠函數不少人是不把他當作構建對象的一種形式的。一部分緣由是語義:即它並不像使用了運算符new來構建的那麼正規。還有一個更大的原 因,是由於這個工廠每次產出一個對象都會建立一個新函數showName(),即每一個對象擁有不一樣的版本,但實際上他們共享的是同一個函數。
有些人把showName在工廠函數外定義,而後經過屬性指向該方法,能夠避開這個問題:
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09function%20showName%20()%20%7B%0A%09%20%20%20%20alert(this.name)%3B%0A%09%7D%20%20%20%20%0A%09function%20createObj(name)%20%7B%0A%09%20%20%20%20var%20tempObj%20%3D%20new%20Object%3B%0A%09%20%20%20%20tempObj.name%20%3D%20name%3B%0A%09%20%20%20%20tempObj.showName%20%3D%20showName%3B%0A%09%20%20%20%20return%20tempObj%3B%0A%09%7D%0A%09var%20obj1%20%3D%20createObj('obj_one')%3B%0A%09var%20obj2%20%3D%20createObj('obj_two')%3B%0A" wmode="transparent">
function showName () {
alert(this.name);
}
function createObj(name) {
var tempObj = new Object;
tempObj.name = name;
tempObj.showName = showName;
return tempObj;
}
var obj1 = createObj('obj_one');
var obj2 = createObj('obj_two');
惋惜的是,這種方式讓showName()這個函數看起來不像對象的一個方法。
這種方式是爲了解決上面工廠函數的第一個問題,即沒有new運算符的問題。但是第二個問題它依然不能解決。咱們來看看。
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09function%20Obj(name)%20%7B%0A%09%20%20%20%20this.name%20%3D%20name%3B%0A%09%20%20%20%20this.showName%20%3D%20function%20()%20%7B%0A%09%20%20%20%20%20%20%20%20alert(this.name)%3B%0A%09%20%20%20%20%7D%0A%09%7D%0A%09var%20obj1%20%3D%20new%20Obj('obj_one')%3B%0A%09var%20obj2%20%3D%20new%20Obj('obj_two')%3B%0A" wmode="transparent">
function Obj(name) {
this.name = name;
this.showName = function () {
alert(this.name);
}
}
var obj1 = new Obj('obj_one');
var obj2 = new Obj('obj_two');
它的好處是不用在構造函數內新建一個對象了,由於new運算符執行的時候會自動建立一個對象,而且只有經過this才能訪問這個對象。因此 咱們能夠直接經過this來對這個對象進行賦值。並且不用再return,由於this指向默認爲構造函數的返回值。同時,用了new關鍵字來建立咱們想 要的對象是否是感受更"正式"了。惋惜,它仍然不能解決會重複生成方法函數的問題,這個狀況和工廠函數同樣。
這種方式對比以上方式,有個很大的優點,就是它解決了方法函數會被生成屢次的問題。它利用了對象的prototype屬性。咱們依賴原型能夠重寫對象實例。
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20Obj%20%3D%20function%20()%20%7B%7D%0A%09Obj.prototype.name%20%3D%20'me'%3B%0A%09Obj.prototype.showName%20%3D%20function%20()%20%7B%0A%09%20%20%20%20alert(this.name)%3B%0A%09%7D%0A%09var%20obj1%20%3D%20new%20Obj()%3B%0A%09var%20obj2%20%3D%20new%20Obj()%3B%0A" wmode="transparent">
var Obj = function () {}
Obj.prototype.name = 'me';
Obj.prototype.showName = function () {
alert(this.name);
}
var obj1 = new Obj();
var obj2 = new Obj();
咱們依賴原型對構造函數進行重寫,不管是屬性仍是方法都是經過原型引用的方式給新建的對象,所以都只會被建立一次。惋惜的是,這種方式存在兩個致命的問題:
沒辦法在構建對象的時候就寫入想要的屬性,由於原型在構造函數做用域外邊,沒辦法經過傳遞參數的方式在對象建立的時候就寫入屬性值。只能在對象建立完畢後對值進行重寫。
致命問題在於當屬性指向對象時,這個對象會被多個實例所共享。考慮下面的代碼:
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20Obj%20%3D%20function%20()%20%7B%7D%0A%09Obj.prototype.name%20%3D%20'me'%3B%0A%09Obj.prototype.flag%20%3D%20new%20Array('A'%2C%20'B')%3B%0A%09Obj.prototype.showName%20%3D%20function%20()%20%7B%0A%09%20%20%20%20alert(this.name)%3B%0A%09%7D%0A%09var%20obj1%20%3D%20new%20Obj()%3B%0A%09var%20obj2%20%3D%20new%20Obj()%3B%0A%09%20%0A%09obj1.flag.push('C')%3B%0A%09%20%0A%09alert(obj1.flag)%3B%20%2F%2F%20A%2CB%2CC%0A%09alert(obj2.flag)%3B%20%2F%2FA%2CB%2CC%0A" wmode="transparent">
var Obj = function () {}
Obj.prototype.name = 'me';
Obj.prototype.flag = new Array('A', 'B');
Obj.prototype.showName = function () {
alert(this.name);
}
var obj1 = new Obj();
var obj2 = new Obj();
obj1.flag.push('C');
alert(obj1.flag); // A,B,C
alert(obj2.flag); //A,B,C
是的,當flag屬性指向對象時,那麼實例obj1和obj2都共享它,哪怕咱們僅僅改變了obj1的flag屬性,可是它的改變在實例obj2中任然可見。面對這個問題,讓咱們不得不想是否應該把【構造函數方式】和【原型方式】結合起來,讓他們互補。。。
咱們讓屬性用構造函數方式建立,方法用原型方式建立便可:
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20Obj%20%3D%20function%20(name)%20%7B%0A%09%20%20%20%20this.name%20%3D%20name%3B%0A%09%20%20%20%20this.flag%20%3D%20new%20Array('A'%2C%20'B')%3B%0A%09%7D%0A%09Obj.prototype%20%3D%20%7B%0A%09%20%20%20%20showName%20%3A%20function%20()%20%7B%0A%09%20%20%20%20%20%20%20%20alert(this.name)%3B%0A%09%20%20%20%20%7D%0A%09%7D%0A%09var%20obj1%20%3D%20new%20Obj()%3B%0A%09var%20obj2%20%3D%20new%20Obj()%3B%0A%09%20%0A%09obj1.flag.push('C')%3B%0A%09%20%0A%09alert(obj1.flag)%3B%20%2F%2F%20A%2CB%2CC%0A%09alert(obj2.flag)%3B%20%2F%2FA%2CB%0A" wmode="transparent">
var Obj = function (name) {
this.name = name;
this.flag = new Array('A', 'B');
}
Obj.prototype = {
showName : function () {
alert(this.name);
}
}
var obj1 = new Obj();
var obj2 = new Obj();
obj1.flag.push('C');
alert(obj1.flag); // A,B,C
alert(obj2.flag); //A,B
這種方式有效地結合了原型和構造函數的優點,是目前用的最多,也是反作用最少的方式。
不過,有些追求完美的傢伙還不知足,由於在視覺上還沒達到他們的要求,由於經過原型來建立方法的過程在視覺上仍是會讓人以爲它不太像實例的 方法(尤爲對於傳統OOP語言的開發者來講。)因此,咱們可讓原型活動起來,讓他也加入到構造函數裏面去,好讓這個構造函數在視覺上更爲統一。而這一系 列的過程只需用一個判斷便可完成。
Js代碼 <EMBED height=15 type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer width=14 src=http://wangjie2013.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%09var%20Obj%20%3D%20function%20(name)%20%7B%0A%09%20%20%20%20this.name%20%3D%20name%3B%0A%09%20%20%20%20this.flag%20%3D%20new%20Array('A'%2C%20'B')%3B%0A%09%20%20%20%20if%20(typeof%20Obj._init%20%3D%3D%20'undefined')%20%7B%0A%09%20%20%20%20%20%20%20%20Obj.prototype%20%3D%20%7B%0A%09%20%20%20%20%20%20%20%20%20%20%20%20showName%20%3A%20function%20()%20%7B%0A%09%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alert(this.name)%3B%0A%09%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%09%20%20%20%20%20%20%20%20%7D%3B%0A%09%20%20%20%20%20%20%20%20Obj._init%20%3D%20true%3B%0A%09%20%20%20%20%7D%0A%09%7D%0A" wmode="transparent">
var Obj = function (name) {
this.name = name;
this.flag = new Array('A', 'B');
if (typeof Obj._init == 'undefined') {
Obj.prototype = {
showName : function () {
alert(this.name);
}
};
Obj._init = true;
}
}
如上,用_init做爲一個標誌來判斷是否已經給原型建立了方法。若是是那麼就再也不執行。這樣其實在本質上是沒有任何變化的,方法還是經過原型建立,惟一的區別在於這個構造函數看起來"江山統一"了。
可是這種動態原型的方式是有問題的,《JavaScript高級程序設計》裏並無深究。建立第一個對象的時候會由於prototype在對象實例化以前沒來的及建起來,是根本沒法訪問的。因此第一個對象是沒法訪問原型方法的。同時這種方式在子類繼承中也會有問題。