JavaScript 裡的 this 到底指得是什嗎?很多人都會告訴你 this 指的是當前對象。這樣理解對嗎?在大多數情況下確實沒錯。比如我們經常會在網頁上寫這樣的 JavaScript:
<input type="submit" value="提交" onclick="this.value='正在提交資料'" />
這裡的this顯然指的是當前對象,即這個提交按鈕。通常,我們使用this的情況都與此類似。但是有什麼情況不是這樣的呢?
大家看看這個例子:
var foo = function() {
console.log(this);
}
foo();
new foo();
比較一下 foo() 和 new foo() 的運行結果,你會發現,前者 this 指向的並非 foo 本身,而是當前頁面的window對象,而後者才真正的指向foo。這是為什麼呢?
其實這牽涉到JavaScript的一條重要特性,就是所謂的“閉包”。閉包這個概念說複雜也不複雜,但也不是簡單到能用一兩句話說清。偶會在以後的文章中深入探討這個Javascript 最重要的特性。現在,我要告訴大家的是,因為閉包的存在,JavaScript中的範圍變得相當重要。
所謂的範圍,簡單的說,就是建立一個函數時在什麼環境下建立的。而this變數的值,如果沒有指定的話,就是函數當前的範圍。
在前面的例子裡,foo() 函數是在全域範圍(這裡就是window對象),所以this的值是當前的window對象。而通過 new foo() 這樣的形式,其實是建立了一個foo()的副本,並在這個副本上進行的操作,所以這裡的this就是foo()的這個副本。
這樣講可能有點抽象,大家來看個實際的例子:
<input type="button" id="aButton" value="demo" onclick="" />
<script type="text/javascript">
function demo() {
this.value = Math.random();
}
</script>
如果直接調用demo() 函數,程式就會報錯,因為demo函數是在window對象中定義的,所以demo的擁有者(範圍)是window,demo的this也是window。而window是沒有value屬性的,所以就報錯了。
如果我們通過棄置站台的方式,將這個函數的副本添加到一個HTML元素,那麼他的所有者就成了這個元素,this也指代了這個元素:
document.getElementById("aButton").onclick = demo;
這樣就將aButton的onlick屬性設定為demo()的一個副本,this也指向了aButton。
你甚至可以為多個不同的HTML元素建立不同的函數副本。每個副本的擁有者都是相對應的HTML元素,各自的this也都指向他們的擁有者,不會造成混亂。
但是,如果你這樣定義某個元素的onlick事件:
<input type="button" id="aButton" value="demo" onclick="demo()" />
點擊這個button之後,你會發現,程式又會報錯了——this又指向了window!
其實,這種方法並沒有為程式建立一個函數,而只是引用了這個函數。
具體看一下區別吧。
使用建立函數副本的方法:
<input type="button" id="aButton" value="demo" />
<script type="text/javascript">
var button = document.getElementById("aButton");
function demo() {
this.value = Math.random();
}
button.onclick= demo;
alert(button.onclick);
</script>
得到的輸出是:
function demo() {
this.value = Math.random();
}
使用函數引用的方法:
<input type="button" id="aButton" value="demo" onclick="demo()" />
<script type="text/javascript">
var button = document.getElementById("aButton");
function demo() {
this.value = Math.random();
}
alert(button.onclick);
</script>
得到的輸出是:
function onclick() {
demo();
}
這樣就能看出區別了吧。函數引用的方式中,onclick事件只是直接調用demo()函數,而demo()函數的範圍仍舊是window對象,所以this仍然指向window。
這樣就又引出了一個問題:既然函數副本這麼好用,為什麼還需要函數引用的方法呢?答案是效能。每建立一個函數的副本,程式就會為這個函數副本分配一定的記憶體。而實際應用中,大多數函數並不一定會被調用,於是這部分記憶體就被白白浪費了。而使用函數引用的方式,程式就只會給函數的本體分配記憶體,而引用只分配指標,這樣效率就高很多。程式員麼,節約為主,恩
所以我們來看一個更好的解決方案:
<script type="text/javascript">
function demo(obj) {
obj.value = Math.random();
}
</script>
<input type="button" value="demo" onclick="demo(this)" />
<input type="button" value="demo" onclick="demo(this)" />
<input type="button" value="demo" onclick="demo(this)" />
這樣,效率和需求就都能兼顧了。
最後再多講一句:在前面的文章裡,我特彆強調了“如果沒有指定this的話”。其實this是可以指定的。Function對象有兩個方法:call()和apply()。這兩個方法都支援指定一個函數中的this。有興趣的話您可以去查一下Javascript的手冊,看看這兩個函數都是幹什麼用的。而我們經常用的 new foo() 可以用以下這段虛擬碼來描述:
function new (somefunction) {
var args = [].slice.call(arguments, 1);
somefunction.prototype.constructor = somefunction;
somefunction.apply(somefunction.prototype, args);
return somefunction.prototype;
}
現在明白了,在本文開頭的第一個例子裡,new foo() 的 this 為什麼是 foo 了吧
文中部分內容引自 http://www.quirksmode.org/js/this.html