2017年12月25日 星期一

[ JavaScript Gossip ] JavaScript 核心 : 一切都與函式有關 (this 是什麼?)

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

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

this 是什麼 : 
在上例中,toString() 函式中使用了this,在呼叫函式時,每個函式都會有個 this,然而 this 參考至哪個物件,其實依呼叫方式而有所不同。以上例而言,透過 p1 呼叫時,toString() 中的 this 會參考至 p1 所參考的物件,也因此顯示 p1 物件的 name  age 值,透過 p2 呼叫時,toString() 中的 this 則會參考至 p2 所參考的物件. 如果呼叫函式時是透過物件與點運算子的方式呼叫,則 this 會參考至點運算子左邊的物件. 在 JavaScript 中,函式是 Function 的實例,Function 都會有個 call() 方法,可以讓你決定 this 的參考對象. 舉例來說,你可以如下呼叫 : 
> var p1 = { name : 'Justin', age : 35 }; 
> var p2 = { name : 'momor', age : 32 }; 
> function toString(){ return '[' + this.name + ',' + this.age + ']'; } 
> toString.call(p1); 
[Justin,35] 
> toString.call(p2); 
[momor,32]

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

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

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

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

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

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

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

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

JavaScript 執行過程中,搞清楚 this 是誰有時非常重要,this 的決定方式是在於呼叫,而非定義的方式! 舉個例子來說,如果你想要自行實現 Array  forEach 方法,則可以如下 : 
> var obj = { 
> '0' : 100, 
> '1' : 200, 
> '2' : 300, 
> length : 3, 
> forEach : function(callback) { 
> for(var j = 0; j < this.length; j++) { 
> callback(this[j]); 
> } 
> } 
> };
 
> obj.forEach(function(element) { 
> print(element); 
> });
 
100 
200 
300

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

This message was edited 16 times. Last update was at 26/12/2017 13:51:09

沒有留言:

張貼留言

[Linux 常見問題] What's the best way to send a signal to all members of a process group?

Source From  Here   Question   I want to  kill a whole process tree.  What is the best way to do this using any common scripting languages? ...