Function 內的 this 到底是哪個 this

每次看到 ES6 的相關教學,都搞不太清楚 arrow function 中的 this 到底是綁定到哪兒去,後來發現其實我連傳統 function 宣告的 this 怎麼綁的也不太明白,直到最近看了卡斯伯老師的文章以及 Wes Bos 的 ES6 課程才稍稍弄清楚。

直接呼叫函式

如果是一般的 function call,this 在嚴格模式下是 undefined,sloppy mode 下是全域物件( 瀏覽器中就是 window ) 。

以下範例,無論把 function 宣告在哪邊,只要是一般的 function callthis 指的都是全域物件window

function testFunc() {
  console.log(this); // window
  
  function testInsideFunc () {
    console.log(this); //window
  }
  testInsideFunc();
}
testFunc();

以下的例子,即使將 function 定義在物件中,呼叫的時候是直接呼叫函式,得到的 this 一定會是 window

let obj = {
    func: function(){
        function testFunc(){
            console.log(this)
        }
        testFunc() //這裡直接呼叫
    }
}

obj.func()
// 印出 window

上面這兩個例子我們可以知道,只要是直接呼叫 function,一般模式下 this 一定是全域物件

透過物件呼叫函式

也就是呼叫物件的屬性方法。這個時候物件本身就是一個 receiver,我們把要呼叫的函式名稱丟給這個 receiver 去執行。這時候 this 就會綁定在這個 receiver 上。

function testFunc(){
  console.log(this)
}
let testObj = {
  testFunc: testFunc
}
testFunc() //window
testObj.testFunc() //testObj

由上面的例子可得知,如果透過 obj.func() 這樣的方式去執行函式,this 就會綁定成 obj

另外還可以使用如 callapplybind的方法強制綁定 this,或者是用建構子(constructor)的方式綁定 this 到新物件上,但這不在本篇討論的範圍。

Arrow function 的 this

arrow function 的 this 是依據語彙環境的父層區域(parent scope)來綁定。
白話的說,arrow function 定義位置(不是呼叫順序)的上一層 this 代表誰,arrow function 內的 this 就代表誰

範例

objA = {
  funcA: function(){
    console.log(this) 
    let arrA = ()=> console.log(this)
    // 這時候他的 parent 是 funcA, 所以 funcA 的 this 是誰, 
    // arrA 的 this 就是誰
	funcA()
  }
}

objA.funcA()
//objA 
//objA 

再看一個測試
如果 parent scope 因為呼叫方式的不同,this 不一樣,而內層的 arrow function 也會跟著 parent scope 改變 this

function funcA(){
	console.log(this)
    let arrA = ()=> console.log(this)
    arrA()
}
let objA = {
	funcA: funcA
}
//直接呼叫的狀況,this 是 window,裡面的 arrow function 也抓到 window
funcA() 
// window
// window

// 改為透過 objA 去呼叫,this 變成 objA,arrow function 也抓到 objA
objA.funcA()
// objA
// objA

範例

這是 Wes Bos 的 ES6 課程範例,我有稍做修改
不得不佩服這是目前我看到最簡單明瞭的例子

const box = document.querySelector('.box')
box.addEventListener('click', function(){
  // 這裡的 this 是 box
  this.classList.toggle('opening');
  setTimeout(()=>{
    // 如果這裡用傳統function,這邊的 this 會變成 window
    // 但如果是使用 arrow function,就會跟語彙環境的parent使用相同的 this
    // 也就是 obj
    this.classList.toggle('open');
  },500)
})

範例請看:

See the Pen arrow this by spreered (@spreered) on CodePen.