this 是什麼?



在JavaScript中,函式是物件,是Function的實例,可以在變數間任意指定,可以傳給函式的參數參考,當然,要新增為物件的特性也是可以的。例如:
js> var p1 = { name : 'Justin', age  : 35 };
js> var p2 = { name : 'momor', age  : 32 };
js> function toString() {
  >     return '[' + this.name + ',' + this.age + ']';
  > }
js> p1.toString = toString;
function toString() {
    return "[" + this.name + "," + this.age + "]";
}

js> p2.toString = toString;

function toString() {
    return "[" + this.name + "," + this.age + "]";
}

js> p1.toString();
[Justin,35]
js> p2.toString();
[momor,32]
js>


在上例中定義了一個toString()函式,並分別設定為p1與p2的toString來參考,透過p1呼叫時,toString()就像是p1的方法(Method),透過p2呼叫時,toString()就像是p2的方法。

在上例中,toString()函式中使用了this,在呼叫函式時,每個函式都會有個this,然而this參考至哪個物件,其實依呼叫方式而有所不同。以上例而言,透過p1呼叫時,toString()中的this會參考至p1所參考的物件,也因此顯示p1物件的name與age值,透過p2呼叫時,toString()中的this則會參考至p2所參考的物件。

如果呼叫函式時是透過物件與點運算子的方式呼叫,則this會參考至點運算子左邊的物件。

在JavaScript中,函式是Function的實例,Function都會有個call()方法,可以讓你決定this的參考對象。舉例來說,你可以如下呼叫:
js> var p1 = { name : 'Justin', age  : 35 };
js> var p2 = { name : 'momor', age  : 32 };
js> function toString() {
  >     return '[' + this.name + ',' + this.age + ']';
  > }
js> toString.call(p1);
[Justin,35]
js> toString.call(p2);
[momor,32]
js>


這次並沒有將toString指定為物件的特性,而是直接使用call()方法來呼叫函式,call()方法的第一個參數就是用來指定函式中的this所參考的物件。如果函式原本具有參數,則可接續在第一個參數之後。例如:
js> function add(num1, num2) {
  >     return this.num + num1 + num2;
  > }
js> var o = { num : 10 };
js> add.call(o, 20, 30);
60
js>


Function也有個apply()方法,作用與call()方法相同,也可讓你在第一個參數指定this所參考的對象,不過apply()方法指定後續引數時,必須將引數收集為一個陣列,如果你有一組引數,必須在多次呼叫時共用,就可以使用apply()方法。例如:
js> function add(num1, num2) {
  >     return this.num + num1 + num2;
  > }
js> var o1 = { num : 10 };
js> var o2 = { num : 100 };
js> var args = [20, 30];
js> add.apply(o1, args);
60
js> add.apply(o2, args);
150
js>


所以,this實際參考的對象,是以呼叫方式而定,而不是它是否附屬在哪個物件而定。例如就算函式是附屬在函式上的某個特性,也可以這麼改變this所參考的對象:
js> var p1 = { name : 'Justin' };
js> var p2 = { name : 'momor' };
js> function toString() {
  >     return this.name;
  > }
js> p1.toString = toString;
function toString() {
    return this.name;
}

js> p2.toString = toString;
function toString() {
    return this.name;
}

js> p1.toString();
Justin
js> p2.toString();
momor
js> p1.toString.call(p2);
momor
js>


在最後一個測試中,是以p1.toString.call(p2)的呼叫方式,所以雖然toString()是p1的特性,但call()指定this是參考至p2,結果當然也是傳回p2的name。

在用物件實字建立物件時,也可以直接指定函式作為特性。例如:
js> var o = {
  >     name : 'Justin',
  >     toString : function() {
  >         return this.name;
  >     }
  > };
js> o.toString();
Justin
js>


由於頂層函式是全域物件上的特性,所以作為一個頂層函式呼叫時,this會參考至全域。例如:
js> function func() {
  >     return this;
  > }
js> func() == this;
true
js> this.func() == this;
true
js>


當一個內部函式直接被呼叫時,this也是參考至全域物件。例如:
js> function func() {
  >     function inner() {
  >         return this;
  >     }
  >     return inner();
  > }
js> func() == this;
true
js> var o1 = { func : func };
js> o1.func() == o1;
false
js> o1.func() == this;
true
js> func.call(o1) == this;
true
js>


在上例中,最後一個例子雖然指定外部函式的this為o1,但事實上,內部函式被呼叫時,this仍是參考至全域物件。如果是以下這個例子:
js> function func() {
  >     function inner() {
  >         return this;
  >     }
  >     this.inner = inner;
  >     return this.inner();
  > }
js> func() == this;
true
js> var o1 = { func : func };
js> o1.func() == o1;
true
js> o1.func.call(this) == this;
true
js> o1.func.call(o1) == o1;
true
js>


在JavaScript執行過程中,搞清楚this是誰有時非常重要,this的決定方式是在於呼叫,而非定義的方式

舉個例子來說,如果你想要自行實現Array的forEach方法,則可以如下:
js> var obj = {
  >     '0' : 100,
  >     '1' : 200,
  >     '2' : 300,
  >     length : 3,
  >     forEach : function(callback) {
  >         for(var i = 0; i < this.length; i++) {
  >             callback(this[i]);
  >         }
  >     }
  > };
js> obj.forEach(function(element) {
  >     print(element);
  > });
100
200
300
js>


在上例中,由於呼叫forEach時,obj參考的物件就是this所參考的物件,因而可以取得length等特性,函式是物件,所以自然可以丟給forEach作為引數,這也就是 陣列 中介紹到Array上forEach方法的實作原理。