轉自http://blog.csdn.net/lixam/article/details/12493245程序員
我在封裝web控件時遇到一個this調用問題,主要思想是將控件封裝成一個js類,可是這個類中某個方法成員中有一個閉包,且該閉包要對此類中的方法進行調用,我直接使用this進行調用失敗,先看代碼,標紅的部分是我已經修改過的,修改前的代碼爲
createMenu1: function () {
var xmlHttp = this.createXmlHttp();
xmlHttp.open("get", "Menu1.xml", true);
xmlHttp.send(null);web
xmlHttp.onreadystatechange = onReadyStateChange;
function onReadyStateChange() {
if (xmlHttp.status == 200 & xmlHttp.readyState == 4) {
var menu1Xml = xmlHttp.responseXML;
//JQuery操做實現動態效果
this.analysisXmlAndCreateHtml(menu1Xml);
// this.setMenuStyle();
this.bindAction();
}
}
}
修改後地代碼
/*
* 說明:Menu1類,實現菜單的快速建立
* 建立人: Lixam
* 建立時間:2013-9-10
* 修改人:
* 修改時間:
*/
var Menu1 = function () { }編程
Menu1.prototype = {
/*
* 功能: 建立menu1
* 參數: 無
* 返回值: 無
* 建立人: Lixam
* 建立時間:2013-9-11
* 修改人:
* 修改時間:
*/
createMenu1: function () {
var xmlHttp = this.createXmlHttp();
xmlHttp.open("get", "Menu1.xml", true);
xmlHttp.send(null);數組
var _this = this; //緩存this,以便閉包函數調用
xmlHttp.onreadystatechange = onReadyStateChange;
function onReadyStateChange() {
if (xmlHttp.status == 200 & xmlHttp.readyState == 4) {
var menu1Xml = xmlHttp.responseXML;
//JQuery操做實現動態效果
_this.analysisXmlAndCreateHtml(menu1Xml);
// _this.setMenuStyle();
_this.bindAction();
}
}
},
//其它代碼
} 瀏覽器
修改前調用失敗緣由主要是沒理解清楚this的用法,記住,誰調用了this那麼this就指向誰,修改前的this實際上是指向了xmlHttp,在閉包調用this前先用緩存將this存下再調用就Ok,下面附上一篇文章供學習。緩存
JavaScript 語言中的 this
因爲其運行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它能夠是全局對象、當前對象或者任意對象,這徹底取決於函數的調用方式。JavaScript 中函數的調用有如下幾種方式:做爲對象方法調用,做爲函數調用,做爲構造函數調用,和使用 apply 或 call 調用。下面咱們將按照調用方式的不一樣,分別討論 this 的含義。
做爲對象方法調用
在 JavaScript 中,函數也是對象,所以函數能夠做爲一個對象的屬性,此時該函數被稱爲該對象的方法,在使用這種調用方式時,this 被天然綁定到該對象。
清單 2. point.js閉包
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
this.x = this.x + x;
this.y = this.y + y;
}
}; app
point.moveTo(1, 1)//this 綁定到當前對象,即 point 對象
做爲函數調用
函數也能夠直接被調用,此時 this 綁定到全局對象。在瀏覽器中,window 就是該全局對象。好比下面的例子:函數被調用時,this 被綁定到全局對象,接下來執行賦值語句,至關於隱式的聲明瞭一個全局變量,這顯然不是調用者但願的。
清單 3. nonsense.js框架
function makeNoSense(x) {
this.x = x;
} 編程語言
makeNoSense(5);
x;// x 已經成爲一個值爲 5 的全局變量
對於內部函數,即聲明在另一個函數體內的函數,這種綁定到全局對象的方式會產生另一個問題。咱們仍然之前面提到的 point 對象爲例,此次咱們但願在 moveTo 方法內定義兩個函數,分別將 x,y 座標進行平移。結果可能出乎你們意料,不只 point 對象沒有移動,反而多出兩個全局變量 x,y。
清單 4. point.js
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
// 內部函數
var moveX = function(x) {
this.x = x;//this 綁定到了哪裏?
};
// 內部函數
var moveY = function(y) {
this.y = y;//this 綁定到了哪裏?
};
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
point.x; //==>0
point.y; //==>0
x; //==>1
y; //==>1
這屬於 JavaScript 的設計缺陷,正確的設計方式是內部函數的 this 應該綁定到其外層函數對應的對象上,爲了規避這一設計缺陷,聰明的 JavaScript 程序員想出了變量替代的方法,約定俗成,該變量通常被命名爲 that。
清單 5. point2.js
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
var that = this;
// 內部函數
var moveX = function(x) {
that.x = x;
};
// 內部函數
var moveY = function(y) {
that.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1
做爲構造函數調用
JavaScript 支持面向對象式編程,與主流的面向對象式編程語言不一樣,JavaScript 並無類(class)的概念,而是使用基於原型(prototype)的繼承方式。相應的,JavaScript 中的構造函數也很特殊,若是不使用 new 調用,則和普通函數同樣。做爲又一項約定俗成的準則,構造函數以大寫字母開頭,提醒調用者使用正確的方式調用。若是調用正確,this 綁定到新建立的對象上。
清單 6. Point.js
function Point(x, y){
this.x = x;
this.y = y;
}
使用 apply 或 call 調用
讓咱們再一次重申,在 JavaScript 中函數也是對象,對象則有方法,apply 和 call 就是函數對象的方法。這兩個方法異常強大,他們容許切換函數執行的上下文環境(context),即 this 綁定的對象。不少 JavaScript 中的技巧以及類庫都用到了該方法。讓咱們看一個具體的例子:
清單 7. Point2.js
function Point(x, y){
this.x = x;
this.y = y;
this.moveTo = function(x, y){
this.x = x;
this.y = y;
}
}
var p1 = new Point(0, 0);
var p2 = {x: 0, y: 0};
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10]);
在上面的例子中,咱們使用構造函數生成了一個對象 p1,該對象同時具備 moveTo 方法;使用對象字面量建立了另外一個對象 p2,咱們看到使用 apply 能夠將 p1 的方法應用到 p2 上,這時候 this 也被綁定到對象 p2 上。另外一個方法 call 也具有一樣功能,不一樣的是最後的參數不是做爲一個數組統一傳入,而是分開傳入的。
換個角度理解
若是像做者同樣,你們也以爲上述四種方式不方便記憶,過一段時間後,又搞不明白 this 究竟指什麼。那麼我向你們推薦 Yehuda Katz 的這篇文章:Understanding JavaScript Function Invocation and 「this」。在這篇文章裏,Yehuda Katz 將 apply 或 call 方式做爲函數調用的基本方式,其餘幾種方式都是在這一基礎上的演變,或稱之爲語法糖。Yehuda Katz 強調了函數調用時 this 綁定的過程,無論函數以何種方式調用,均需完成這一綁定過程,不一樣的是,做爲函數調用時,this 綁定到全局對象;做爲方法調用時,this 綁定到該方法所屬的對象。
結束?
經過上面的描述,若是你們已經能明確區分各類狀況下 this 的含義,這篇文章的目標就已經完成了。若是你們的好奇心再強一點,想知道爲何 this 在 JavaScript 中的含義如此豐富,那就得繼續閱讀下面的內容了。做者須要提早告知你們,下面的內容會比前面稍顯枯燥,若是隻想明白 this 的含義,閱讀到此已經足夠了。若是你們不嫌枯燥,非要探尋其中究竟,那就一塊兒邁入下一節吧。
函數的執行環境
JavaScript 中的函數既能夠被看成普通函數執行,也能夠做爲對象的方法執行,這是致使 this 含義如此豐富的主要緣由。一個函數被執行時,會建立一個執行環境(ExecutionContext),函數的全部的行爲均發生在此執行環境中,構建該執行環境時,JavaScript 首先會建立 arguments變量,其中包含調用函數時傳入的參數。接下來建立做用域鏈。而後初始化變量,首先初始化函數的形參表,值爲 arguments變量中對應的值,若是 arguments變量中沒有對應值,則該形參初始化爲 undefined。若是該函數中含有內部函數,則初始化這些內部函數。若是沒有,繼續初始化該函數內定義的局部變量,須要注意的是此時這些變量初始化爲 undefined,其賦值操做在執行環境(ExecutionContext)建立成功後,函數執行時纔會執行,這點對於咱們理解 JavaScript 中的變量做用域很是重要,鑑於篇幅,咱們先不在這裏討論這個話題。最後爲 this變量賦值,如前所述,會根據函數調用方式的不一樣,賦給 this全局對象,當前對象等。至此函數的執行環境(ExecutionContext)建立成功,函數開始逐行執行,所需變量均從以前構建好的執行環境(ExecutionContext)中讀取。
Function.bind
有了前面對於函數執行環境的描述,咱們來看看 this 在 JavaScript 中常常被誤用的一種狀況:回調函數。JavaScript 支持函數式編程,函數屬於一級對象,能夠做爲參數被傳遞。請看下面的例子 myObject.handler 做爲回調函數,會在 onclick 事件被觸發時調用,但此時,該函數已經在另一個執行環境(ExecutionContext)中執行了,this 天然也不會綁定到 myObject 對象上。
清單 8. callback.js
button.onclick = myObject.handler;
這是 JavaScript 新手們常常犯的一個錯誤,爲了不這種錯誤,許多 JavaScript 框架都提供了手動綁定 this 的方法。好比 Dojo 就提供了 lang.hitch,該方法接受一個對象和函數做爲參數,返回一個新函數,執行時 this 綁定到傳入的對象上。使用 Dojo,能夠將上面的例子改成:
清單 9. Callback2.js
button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已經提供了內置的 bind 方法供你們使用。eval 方法JavaScript 中的 eval 方法能夠將字符串轉換爲 JavaScript 代碼,使用 eval 方法時,this 指向哪裏呢?答案很簡單,看誰在調用 eval 方法,調用者的執行環境(ExecutionContext)中的 this 就被 eval 方法繼承下來了。結束語本文介紹了 JavaScript 中的 this 關鍵字在各類狀況下的含義,雖然這只是 JavaScript 中一個很小的概念,但藉此咱們能夠深刻了解 JavaScript 中函數的執行環境,而這是理解閉包等其餘概念的基礎。掌握了這些概念,才能充分發揮 JavaScript 的特色,纔會發現 JavaScript 語言特性的強大。