this 在 OOP 相對單純,都是代表固定 Object,但在 ECMAScript 則是嚴肅課題,主要是 ECMAScript 是以 Function 為核心,OOP 是由 Function 實現,因此 this 觀念大異其趣。
Version
ECMAScript 2015
Execution Context
this 在 ES5 稱為 execution context,也因為是 context,表示 this 並不是代表固定 Object,而會隨環境而變動。
Global
let foo = function() {
return this
}
foo() // ?
若在 global scope 的 function 使用 this,則 this 為 window。

Object
let foo = function() {
return this
}
let obj = {
foo
}
obj.foo() // ?
若 function 為掛在 Object 上的 method,則 this 指向 Object。
因為是 foo() 透過 obj 執行,所以 execution context 是 Object。

apply()
let foo = function() {
return this
}
let obj = {}
foo.apply(obj) // ?
apply() 為 Function.prototype.apply() 所提供,因此每個 function 都天生自帶 apply()。
apply() 的特色是當執行 function 時,可動態傳入 Object 指定 function 的 execution context,也就是 Object 將動態取代 this。

call()
let foo = function() {
return this
}
let obj = {}
foo.call(obj) // ?
call() 為 Function.prototype.apply() 所提供,因此每個 function 都天生自帶 call()。
call() 的特色是當執行 function 時,可動態傳入 Object 指定 function 的 execution context,也就是 Object 將動態取代 this。
若不傳入第二個 argument,則
apply()與call()功能完全相同,都是傳入 Object 取代this,並且執行 function。若傳入第二個 argument,則
apply()與call()就不同:
apply()以 Array 形式傳入 argument,如foo.apply(obj, [1, 2, 3])call()以傳統形式傳入 argument,如foo.call(obj, 1, 2, 3)

bind()
let foo = function() {
return this
}
let obj = {}
foo.bind(obj)() // ?
bind() 為 Function.prototype.bind() 所提供,因此每個 function 都天生自帶 bind()。
bind() 與 apply() 與 call() 很類似,也可動態傳入 Object 指定 function 的 execution context,也就是 object 動態取代 this。
但 apply() 與 call() 是直接執行 function,而 bind() 是回傳一個新 function。
因此 apply() 之後還要加上 () 才能執行 function。

New
function Foo(name) {
this.name = name
}
let foo = new Foo('Sam')
foo.name // ?
ES5 為了能如 OOP 使用 new 建立 Object,當 new 遇到普通 function 時,搖身一變成為 constructor function,其 this 代表 new 所建立的 object,如此就能使用 this 存取 Object 的 property。

class Foo {
constructor(name) {
this.name = name
}
}
let foo = new Foo('Sam')
foo.name // ?
ES6 迎來了 class 語法,costructor function 則由 constructor() method 取代,this 也如同一般 OOP 指向 Object。

Arrow Function
let obj = {
foo: () => this
}
obj.foo()
ES6 迎來新的 arrow function,但其 this 與傳統 function 觀念不一樣。
傳統 function 由執行時的 Object 決定 exection context,也就是 this 會隨環境而變 (global、Object、new),甚至 apply()、call() 與 bind() 也可動態改變 this。
Arrow function 的 this 則 維持使用 當時建立 arrow function 的 function 的 execution context,且一旦決定後,就不能再由 apply()、call() 或 bind() 改變。
func() 在 obj 內以 arrow function 建立,因此其 this 固定不變,且因為 () => this 並不在任何 function 內,因此其 execution context 為 window,this 不再如普通 function 內代表 obj。

let obj = {
increment: 1,
foo: function(a) {
return a.map(function(x) {
return x + this.increment
})
}
}
let data = [1, 2, 3]
obj.foo(data) // ?
ECMAScript 是以 function 為核心的語言,強調 first-class function,因此 argument 常是 function。
如著名的 Array.prototype.map() 就要我們傳入 function。
第 4 行
return a.map(function(x) {
return x + this.increment
})
在 map function 內我們希望存取 obj 的 increment property,直覺會使用 this。

最後結果為不預期的 NaN。
因為 map() 傳入一個全新 function,該 function 並非掛在 obj 下,因此其 this 並非指向 obj,而是 window。
let obj = {
increment: 1,
foo: function(a) {
var self = this
return a.map(function(x) {
return x + self.increment
})
}
}
let data = [1, 2, 3]
obj.foo(data) // ?
ES5 常常看到 var self = this ,map function 則透過 closure 讀取到 self,間接也獲得 this,如此則可存取 obj 的 increment property。

這種寫法缺點是 map function 的 this 都要改成 self。
let obj = {
increment: 1,
foo: function(a) {
return a.map(function(x) {
return x + this.increment
}.bind(this))
}
}
let data = [1, 2, 3]
obj.foo(data) // ?
另外一個方式則透過 bind(),重新將 this 綁定到 map 的 callback。

這種寫法優點是 map function 的 this 不用修改,但 bind() 也很醜。
let obj = {
increment: 1,
foo: function(a) {
return a.map(x => x + this.increment)
}
}
let data = [1, 2, 3]
obj.foo(data) // ?
ES6 的 arrow function 就是為了解決這個問題。
Arrow function 的 this 由定義該 arrow function 的 function 決定,也就是將使用 func() 的 this,其 this 指向 obj,因此 map function 的 this 也繼續指向 obj,可順利讀取到 increment property。

Arrow function 不僅簡短,且可讀性又佳。
let obj = {
increment: 1,
foo: a => a.map(x => x + this.increment)
}
let data = [1, 2, 3]
obj.foo(data) // ?
但 foo() 不可再用 arrow function!!
因為 arrow function 沒有自己的 this,而是由定義該 arrow function 之處決定,foo 的 arrow function 為 free function,因此 this 指向 window,結果再次回到 NaN。

Conclusion
this在 ECMAScript 頗為混亂,但仍應對this有清楚觀念,畢竟this已經成為 ECMAScript 的 featurethis對於 FP 而言為 implicit argument,來自於 side effect,且 function 結果會隨 context 而變,也不符合 pure function 要求,因此 FP 不會使用this- 但
this在 OOP 是必須的,因為 ECMAScript 就是利用 function 與this實現 OOP,因此還是必須了解 - 實務上建議 callback 都使用 arrow function,可避免
this指向window
Reference
Vegard Sandnes, How does the “this” keyword work in JavaScript