2012-07-05

Javascript function context - this 指的是誰?

在 Java 裡,this 指的是所在的 class instance,多半是在 method 裡呼叫 this;但是在 Javascript 裡,function 可以是 top-level object,也可以指派給某個物件,那 function 裡的 this 指的到底是誰?

要看 context,執行時的 context,不是定義時的 context,更白話點,就是「掛在誰身上執行」。


掛在誰身上執行?

一般的 function 有兩種用法:
function a() {
    return 'a';
}

var b = {
    c: function() {
        return 'c';
    }
}

// 呼叫方式分別為
a();
var b1 = new b();
b1.c();
 function c 是掛在 b 身上執行,所以 c 裡面的 this 指的就是 b,那 function a 呢?是掛在誰身上? window 物件,所以 a 裡的 this 指的就是 window。

懂了嗎?接下來讓大腦冒煙一下吧!

var a = 'window.a'; // global 變數
var that = this; // 就是指 window
$(function(){

neil.log(this); // 0.[object HTMLDocument]
neil.log('-');

var a = 'document.ready.a';
var b = 'document.ready.b';
this.a = 'document.a'
this.c = 'document.c';

neil.log(a); // 1.document.ready.a
neil.log(window.a); // 2.window.a
neil.log(that.a); // 3.window.a
neil.log(this.a); // 4.document.a
neil.log('-');

neil.log(f1()); // 5.window.a
neil.log(window.f1()); // 6.window.a
neil.log(that.f1()); // 7.window.a
neil.log('-');

neil.log(f1.call(this)); // 8.document.a
neil.log(f1.apply(this)); // 9.document.a
neil.log('-');

neil.log(f2()); // 10.window.a
neil.log(window.f2()); // 11.window.a
neil.log(that.f2()); // 12.window.a
neil.log('-');

neil.log(f2.call(this)); // 13.window.a
neil.log(f2.apply(this)); // 14.window.a
neil.log('-');

neil.log(b); // 15.document.ready.b
neil.log(this.b); // 16.undefined
neil.log('-');

neil.log(this.c); // 17.document.c
neil.log(c); // 18.error - undefined
neil.log('-');
});
function f1() {
return this.a;
}
function f2() {
return a;
}

宣告一個 global 變數 a,以及一個 ready function 裡的 local 變數 a。

再強調一下,兩條規則:
  1. this 指的是掛在誰身上執行,不是在哪個環境執行! 
  2. 如果沒有指明掛在誰身上,那就是從所在的 scope 往上找,直到找到為止。

neil.log(this); // 0.[object HTMLDocument]

先驗明正身,得知 ready function 是掛在 document 身上執行。


var a = 'document.ready.a';
var b = 'document.ready.b';
this.a = 'document.a';
this.c = 'document.c';

在 function 裡用 var 或 this 宣告變數,結果差很大。
  • 用 var 表示該變數為 function 裡的 local 變數。
  • 用 this 表示該變數為該 function 所屬物件的屬性,簡單說,this 宣告的變數和這個 function 是同一個物件下的屬性與 function。


neil.log(a); // 1.document.ready.a

沒說掛在誰身上,套用規則2,先看 function裡有沒有 a,若沒有再看 window 裡有沒有,但是不會看物件的屬性,也就是 document.a。


neil.log(window.a); // 2.window.a
neil.log(that.a); // 3.window.a

有頭有尾,沒問題。


neil.log(this.a); // 4.document.a

在 doucment.ready 裡呼叫 this,指的當然是 document。


neil.log(f1()); // 5.window.a

沒說掛在誰身上,套用規則2,在 window 裡找到 f1,f1 裡的 this當然是 window。。


neil.log(window.f1()); // 6.window.a
neil.log(that.f1()); // 7.window.a

指名道姓,沒問題。


neil.log(f1.call(this)); // 8.document.a
neil.log(f1.apply(this)); // 9.document.a

Javascript 特有的「張冠李戴」功能,因為 function 為獨立的物件,可以到處掛在別人身上,就有了這兩個「借腹生子」 的 function,call 與 apply 只差在餵參數的方式不一樣,都是將 function 掛在第一個參數上執行,以上面為例,就是將 f1 掛在 document 上執行。


neil.log(f2()); // 10.window.a
neil.log(window.f2()); // 11.window.a
neil.log(that.f2()); // 12.window.a

第一個因為沒有頭,所以套用規則2,得到跟後兩個一樣的頭,而 f2 裡的 a 又是沒有頭,一樣套用規則2。


neil.log(f2.call(this)); // 13.window.a
neil.log(f2.apply(this)); // 14.window.a

使用「借腹生子」掛在 document 下執行,但 f2 裡的 a 沒有頭讓我很困擾,我以為就是指 document.a,但結果卻是 window.a,唯一的解釋是「規則2不會找物件屬性,只會找 local 變數」!


neil.log(b); // 15.document.ready.b
neil.log(this.b); // 16.undefined
neil.log(this.c); // 17.document.c
neil.log(c); // 18.error - undefined

用來證明在 function裡用 var 或 this 宣告變數是不一樣的。

沒有留言:

張貼留言