深入探討 Anonymous Function 與 Arrow Function 的 this
一般人印象,Arrow Function 只是 Anonymous Function 的 syntax sugur,讓我們用更精簡的語法來表達這類只使用一次的 function,但對於 this
的認知, 兩種表示法卻有很大的差別。
Version
TypeScript 2.0.7
this 在 Anonymous Function
在 ES5,class 是使用 function 來表示,我們先使用 ES5 的方式。
this_anonymous_function.ts1 1GitHub Commit : this 在 anonymous function1
2
3
4
5
6
7
8
9
10
11module MyModule {
export function Foo(name: string) {
this.name = name;
this.say = function () {
console.log(`Hello, ${this.name}`);
}
}
}
var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
第 4 行1
2
3this.say = function () {
console.log(`Hello, ${this.name}`);
}
使用 anonymous function 來定義 say()
,並在 anonymous function 使用 this
。
10 行1
2var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
建立 foo
物件,並將 foo.say
傳入 setTimeout()
。
實際執行,會發現 this.name
會 undefined
,因為 JavaScript 的特性,foo.say
會變成 setTimeout
物件的一部分,但 setTimeout
並沒有定義 name
,因此會 undefined
,這符合我們之前對 JavaScript 對 this
的認知,與一般物件導向語言的 this
並不相同,當然也與 PHP 的 $this
不相同。
this 在 Arrow Function
ES6 提供了 arrow function,大凡本來使用 anonymous function 的地方,都可以使用 arrow function 取代。
this_arrow_function.ts2 2GitHub Commit : this 在 arrow function1
2
3
4
5
6
7
8
9
10
11module MyModule {
export function Foo(name: string) {
this.name = name;
this.say = () => {
console.log(`Hello, ${this.name}`);
}
}
}
var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
第 4 行1
2
3this.say = () => {
console.log(`Hello, ${this.name}`);
}
將 anonymous function 改用 arrow function,其他都不變,一樣使用 this
。
實際執行,竟然出現 Hello, Sam
,不再是 undefined
,跟原來 anonymous function 結果不一樣!
去看編譯過的 js 檔,會發現一開始先 var _this = this
。
而 this.say
所執行的並不是 this
,而是 _this
。
也就是 ES6 的 arrow function,其 this
跟 ES5 的 anonymous function 不同,反而跟主流物件導向語言的 this
相同,當然也跟 PHP 的 $this
相同。
this 在 Class Function
ES6 多了 class
關鍵字,當然要使用 class
可讀性較高。
this_class_function.ts3 3GitHub Commit : this 在 class function1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module MyModule {
export class Foo {
private name: string;
constructor(name: string) {
this.name = name;
}
public say() {
console.log(`Hello, ${this.name}`)
};
}
}
var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
第 9 行1
2
3public say() {
console.log(`Hello, ${this.name}`)
};
使用了我們熟悉的一般物件導向語言方式定義 say()
,一樣使用 this
。
實際執行,又出現 undefined
,也就是雖然是 ES6,this
並不是全部跟主流程式語言一樣,這裡仍維持 ES5 的 this
觀念。
this 在 Class 內使用 Arrow Function
若在 ES6 的 class 使用 arrow function,又會變成怎樣呢?我們繼續看下去:
this_class_arrow_function1.ts4 4GitHub Commit : this 在 class 內使用 arrow function 第一種方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module MyModule {
export class Foo {
private name: string;
constructor(name: string) {
this.name = name;
}
public say: ()=>void = ()=> {
console.log(`Hello, ${this.name}`)
};
}
}
var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
第 9 行1
2
3public say: ()=>void = ()=> {
console.log(`Hello, ${this.name}`)
};
由於 TypeScript 是強行別語言,所以需要定義 say 的型別為 ()=>void
這種 arrow function 型別,接著才能定義 arrow function,一樣使用 this
。
這樣就不會 undefined
了。
還有另外一種寫法,讓我們在 constructor 去定義 arrow function。
this_class_arrow_function2.ts5 5GitHub Commit : this 在 class 內使用 arrow function 第二種方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module MyModule {
export class Foo {
private name: string;
public say: ()=>void;
constructor(name: string) {
this.name = name;
this.say = () => {
console.log(`Hello, ${this.name}`);
};
}
}
}
var foo = new MyModule.Foo('Sam');
setTimeout(foo.say, 1000);
第 4 行1
public say: ()=>void;
先在 field 內定義好 say
的型別為 arrow function。
第 6 行1
2
3
4
5
6constructor(name: string) {
this.name = name;
this.say = () => {
console.log(`Hello, ${this.name}`);
};
}
在 constructor 內去定義 arrow function,雖然可行,不過實務上不建議這樣寫,除非 method 真的程式碼很短,否則 constructor 會非常的大而難以維護。
實際執行,也不再 undefined
。
Conclusion
- 無論 ES5 或 ES6,
this
都維持 JavaScript 一貫的想法,也就是當 function 內掛在哪一個物件,this
就是那個物件。 - 唯獨 arrow function 例外,arrow function 會事先將
_this = this
,而之後都使用_this
,這種思維與一般物件導向語言相同,但跟傳統 JavaScript 不同。 - Anonymous function 與 arrow function 對於
this
的看法都有他的道理,要視狀況使用,大部分狀況,arrow function 與一般物件導向語言的思維一樣,但有些應用,anonymous function 的想法比較方便。
Sample Code
完整的範例可以在我的 GitHub 上找到。