“this”關鍵字是JavaScript中廣泛應用的一種特性,但它經常也是這門語言中最容易混淆和誤解的特性。“this”的實際意義是什么?它是如何求值的?
本文試圖以清晰的方式澄清和解釋這問題的答案。
有過其他編程經驗的人對“this”關鍵字並不陌生,大部分時候當通過構造函數實例化一個類的實例時,它指新創建的對象。例如,如果我有一個類Boat(),其擁有一個moveBoat()方法,當在moveBoat方法中引用“this”的時候,我們實際上訪問的基於Boat類新創建的對象。
在JavaScript中,當通過“new”關鍵字調用構造函數時,我們也有this概念,然而,這並不是唯一的規則,並且“this”經常在不同的執行上下文中引用到不同的對象。如果你不熟悉JavaScript中的執行上下文,我建議你閱讀我的另一篇文章。說的夠多了,讓我們看一些JavaScript的例子:
// 全局作用域 foo = 'abc'; alert(foo); // abc this.foo = 'def'; alert(foo); // def
每當你在全局作用域中使用“this”關鍵字時(沒在函數內部),它通常指向全局對象(global object)。現在讓我們看看函數內部“this”的值:
var boat = { size: 'normal', boatInfo: function() { alert(this === boat); alert(this.size); } }; boat.boatInfo(); // true, 'normal' var bigBoat = { size: 'big' }; bigBoat.boatInfo = boat.boatInfo; bigBoat.boatInfo(); // false, 'big'
那么上面的“this”如何確定?我們看到上面的boat對象有一個size屬性和一個boatInfo方法。在boatInfo()內部,會彈出this的值是否是boat對象,也會彈出this的size屬性。所以我們執行boat.boatInfo(),我們看見this的值是boat對象和boat的size屬性值是normal。
然后我們創建另一個對象bigBoat,也有一個size屬性是big。然而,bigBoat對象沒有boatInfo方法,所以我們從boat對象拷貝方法 bigBoat.boatInfo = boat.boatInfo。現在,當我們調用bigBoat.boatInfo()並進入函數時,我們看到this不等於boat,並且現在size屬性值是big。為什么會這樣?boatInfo()內部的this值是如何改變的?
你必須意識到的第一件事是函數內部this的值不是靜態的,每次你調用一個函數它總是重新求值,但這一過程發生在函數代碼實際執行之前。函數內部的this值實際由函數被調用的父作用域提供,更重要的是,依賴實際函數的語法。
當函數被調用時,我們看緊鄰括號“()”的左邊。如果在括號的左側存在一個引用,傳遞給調用函數的“this”值是引用屬於的對象,否則this的值將是全局對象。讓我們看一個例子:
function bar() { alert(this); } bar(); // global - 因為bar方法被調用時屬於 global 對象 var foo = { baz: function() { alert(this); } } foo.baz(); // foo - 因為baz()方法被調用時術語foo對象
如果this就這么簡單,那上面的代碼就足夠了。我們可以進一步使事情變得復雜,通過不同的調用語法,改變相同函數內部“this”的值。
var foo = { baz: function() { alert(this); } } foo.baz(); // foo - 因為baz被調用時屬於foo對象 var anotherBaz = foo.baz; anotherBaz(); // global - 因為anotherBaz()被調用時術語global對象
我們看到baz()內部的“this”值每次都不同,這是因為調用的語法不同。現在,讓我們看看深度嵌套對象內部“this”的值:
var anum = 0; var foo = { anum: 10, baz: { anum: 20, bar: function() { console.log(this.anum); } } } foo.baz.bar(); // 20 - 因為()的左邊是bar,而它被調用時屬於baz對象 var hello = foo.baz.bar; hello(); // 0 - 因為()的左邊是hello,而它被調用時屬於global對象
另一個經常被問的問題是事件處理程序內部的“this”關鍵字如何求值?答案是事件處理程序內部的“this”總是引用觸發事件的元素。讓我們看一個例子:
<div id="test">I am an element with id #test</div>
function doAlert() { alert(this.innerHTML); } doAlert(); // undefined var myElem = document.getElementById('test'); myElem.onclick = doAlert; alert(myElem.onclick === doAlert); // true myElem.onclick(); // I am an element
我們看到當doAlert()第一次調用時,彈出的值是undefined,由於doAlert()屬於global對象。然后我們寫myElem.onclick = doAlert。這意味這當onclick被出發時,它作為myElem的一個方法,“this”的值將是myElem元素。
我想說的最后一點是,“this”的值也可以通過call和apply手動設置,這超過我們所討論的范圍。還感興趣的是,當調用構造函數時,“this”引用新創建的實例對象。原因是因為構造函數前面的“new”關鍵字,它創建一個新對象,構造函數內部的“this”總引用新創建的對象。

