關於函數
在Javascript中函數實際上就是一個對象,具有參考型別的特徵,所以你可以將函數直接傳遞給變數,這個變數將表示指向函數“對象"的指標,例如:
function test(message){ alert(message); } var f = test; f('hello world');
你也可以直接將函數申明賦值給變數:
var f = function(message){ alert(message); };f('hello world');
在這種情況下,函數申明中可以省略函數名稱,因為此時名稱已經沒有任何意義,我們可直接通過變數f來調用函數。
通過Function類型,我們可以更好地理解函數即對象:
var f = new Function("message","alert(message);");f('hello world');
關於this
this可以看成調用函數的實際範圍上下文。比較以下函數的執行結果:
function test(){ this.property = 'hello world'; } test(); alert(window.property); //由於在全域範圍內調用,test函數中的this實際指向全域對象(window) var obj = {}; test.call(obj); //通過call第一個參數指定執行內容範圍,所以test函數中this指向obj執行個體。 alert(obj.property); var obj2 = {}; obj2.test2 = test; //將obj2執行個體方法test指向 全域test方法 obj2.test2(); //由於是在obj2上調用test方法,所以test函數中的this也指向了obj2執行個體 alert(obj2.property);
定義類型
在Javascript中可以定義建構函式,建構函式與一般函數沒有任何區別,在建立執行個體時,如果我們使用了new關鍵字,那麼這個函數就具有建構函式的特性,否則就是一般函數,如下所示,我們定義了一個Person類型:
function Person(){ this.name = 'xfrog'; this.Say = function(){ alert(this.name); };}
當使用new關鍵字時,可以建立一個新的Person對象執行個體:
var p1 = new Person();p1.Say();
如果不使用new關鍵字,將直接執行Person函數,由於執行內容為全域範圍,故name屬性和Say方法將被添加到window對象:
Person();Say();window.Say();
原型
注意上述Person的定義方式,當使用new來建立Person執行個體時,將會執行Person建構函式,也就是會聲明name屬性和Say方法,這樣可能產生效率問題,注意以下代碼:
var p1 = new Person();var p2 = new Person();var test = p1.Say == p2.Say;
比較p1和p2兩個Say函數指標,返回false,表示每個Person執行個體中的Say方法都是獨立的,而事實上Say函數的功能是完全一樣的,我們完全沒有必要為每個對象重新分配Say函數”對象“,如果Person執行個體很多,將會造成大量的記憶體耗用。
如果將Say函數提取出來放入全域執行範圍,似乎可解決次問題:
function Person(){ this.name = 'xfrog'; this.Say = say; } function say(){ alert(this.name); } var p1 = new Person(); var p2 = new Person(); alert(p1.Say == p2.Say); p1.name = 'wang'; p1.Say();
由於this始終和執行內容相關,p1和p2執行個體中的Say方法中會正確地返回對應執行個體的name屬性。但是,使用此方式有違物件導向的思想,也失去了類型密封的原則。還會造成大量的全域函數。
為瞭解決這些缺點,Javascript引出了原型的概念,簡單理解,原型可以看成是類型的共用區,原型本身是一個對象,而對象中的屬性對於類型來說是共用的。Javascript中每個類型通過prototype屬性來表示原型,通過這個屬性可指定共用方法:
function Person(){ } Person.prototype.name = 'xfrog'; Person.prototype.Say = function(){ alert(this.name); }; var p1 = new Person(); var p2 = new Person(); alert(p1.Say == p2.Say); //返回true
為什麼這裡可以通過p1.Say來訪問Say方法呢?這是因為ECMAScript標準規定了類型屬性的尋找順序:先在類型的執行個體上尋找,如果沒有則繼續在類型原型上尋找,這一尋找路徑採用短路演算法,即找到首個後即返回,考慮如下代碼:
function Person(){ this.name = 'wang'; } Person.prototype.name = 'xfrog'; Person.prototype.Say = function(){ alert(this.name); } var p1 = new Person(); p1.Say(); //將返回wang
上面提到prototype實際上是一個對象,那麼我們是否可以直接存取呢? 在一些瀏覽器實現(如Chrome、Fixfox等)的確可通過執行個體的__proto__屬性來訪問內部的prototype對象,這種特徵表明Javascript引擎在每個對象的內部都是通過一個變數來儲存對prototype的引用,這保證了prototype對應整個類型的執行個體來說是共用的,例如,你可在Chrome瀏覽器內使用如下方式來訪問Say方法:
由於原型是一個對象,我們可以直接將一個對象賦值給prototype:
function Person(){ } Person.prototype = {name:'xfrog', Say:function(){ alert(this.name); }};
注意這個方式下,實際上是完全替換了Person的prototype,這與上面Person.prototype.name方式還是有細微差異的,這是因為任何類型,Javascript引擎都會添加預設的prototype,在這個prototype中包含一個對建構函式的引用,即原型對象屬性constructor,所以通常使用替代prototype方式時,我們需要手動加上constructor屬性:
Person.prototype = { constructor: Person, name :'xfrog', Say:function(){ alert(this.name); } }
注意,由於prototype對於整個類型是共用的,那麼在prototype中的參考型別可能會存在問題,前面的Say函數作為一個對象,也是參考型別,所以每個執行個體中的Say都指向原型對象中的同一個函數,這本身沒有問題,也是我們使用原型的初衷,但對於其他引用對象,可能結果並不是我們想要的:
function Person(){ } Person.prototype = { name: 'xfrog', obj : { age: 18 }, Say : function(){ alert(this.obj.age); } }; var p1 = new Person(); var p2 = new Person(); p1.obj.age = 20; p1.Say(); p2.Say();
p2.Say返回的是20,這是因為obj屬性作為原型屬性是共用的,在記憶體中只存在一個執行個體,所以通過p1修改後,p2隻能得到修改後的狀態。如果要避免此情況,可將obj屬性放到執行個體中:
function Person(){ this.obj = { age: 18 }; }
以上就是小編為大家帶來的淺談Javascript中的函數、this以及原型全部內容了,希望大家多多支援雲棲社區~