你不知道的JavaScript -"this"
作者:棕熊[http://www.cnblogs.com/ruxpinsp1]
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();
- }
- < SPAN>script>
如果直接呼叫demo() 函式,程式就會報錯,因為demo函式是在window物件中定義的,所以demo的擁有者(作用域)是window,demo的this也是window,而window是沒有value屬性的,所以就報錯了。
- document.getElementById("aButton").onclick = demo;
這樣就將aButton的onlick屬性設定為demo()的一個副本,this也指向了aButton。
但是如果這樣定義某個元素的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);
- < SPAN>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);
- < SPAN>script>
得到的輸出是:
- function onclick() {
- demo();
- }
這樣就能看出區別了。函式引用的方式中,onclick事件只是直接呼叫demo()函式,而demo()函式的作用域仍舊是window物件,所以this仍然指向window。
這樣就又引出了一個問題:既然函式副本這麼好用,為什麼還需要函式引用的方法呢?答案是效能。每新建一個函式的副本,程式就會為這個函式副本分配一定的記憶體。而實際應用中,大多數函式並不一定會被呼叫,於是這部分記憶體就被白白浪費了。而使用函式引用的方式,程式就只會給函式的本體分配記憶體,而引用只分配指標,這樣效率就高很多。
程式設計師麼,節約為主,所以我們來看一個更好的解決方案:
- <script type="text/JavaScript">
- function demo(obj) {
- obj.value = Math.random();
- }
- < SPAN>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);
- somefunctionsomefunction.prototype.
constructor = somefunction; - somefunction.apply(somefunction.prototype, args);
- return somefunction.prototype;
- }
現在明白在本文開頭的第一個例子裡,new foo() 的 this 為什麼是foo了吧。