What on earth is THIS?


Posted by Christy on 2021-10-08

本篇為 [JS201] 進階 JavaScript:那些你一直搞不懂的地方筆記之六。

重點整理:

1. 為什麼要用 this? 在物件導向裡面,this 用來對應到 instance

2. this 的值說清楚

a. 物件導向:this 代表 instance

b. 非物件導向:

b.1: 嚴格模式下為 undefined

b.2: 非嚴格模式下,browser 為 window;node.js 為 global

b.3: 例外: DOM 裡面,點擊到誰,this 就是誰

c. 物件:跟函式怎麼被呼叫有關;可以用 .call() 小撇步判斷

d. 例外: 箭頭函式裡面,跟在哪裡被定義有關,很像 scope 的概念

3. 強制綁定 this

先學完物件導向,學 this 才有意義

一、this 的意義在哪

在物件導向裡面,可以用 this 代替、對應到 instance,不然也沒有其他方法可以用了。

this 之所以不好理解,是因為「在非物件導向」的情況下,不太容易準確的判斷它的值。

接下來我們來看在「在非物件導向」的情況下,this 的值是什麼。

二、在沒有意義的地方(非物件導向)呼叫 this,預設值會是什麼?

1. 非物件導向,this 分別的值

在非物件導向環境下,this 跟函式沒有什麼關聯性(但 DOM 是一個例外)

a. 在寬鬆模式下的預設值:

function test() {
  console.log(this)
}
test() // global or window

a.1 在瀏覽器,this 為 window

a.2 在 node.js,this 為 global

b. 在嚴格模式下,this 的值會是 undefined

'use strict'
function test() {
  console.log(this)
}
test() // undefined

2. 例外情形:用 DOM 的時候

document.querySelector('.btn').addEventListener('click', function() {
  this // 點到哪個按鈕,this 就是哪個按鈕
})

三、另外兩種呼叫 function 的方法:call 與 apply

a. .call()

'use strict'
function test() {
  console.log(this)
}
test.call('123') // 裡面傳什麼,this 就是什麼

b. .apply()

'use strict'
function test() {
  console.log(this)
}
test.apply(123) // 裡面傳什麼,this 就是什麼

c. 兩者差別

'use strict'
function test(a, b, c) {
  console.log(this)
  console.log(a, b, c)
}

test.call(123, 1, 2, 3) // 第一個參數為 this,接著傳 a, b, c 三個參數

test.apply(123, [1, 2, 3]) // 第一個參數為 this,第二個參數為陣列

四、用另一種角度來看 this 的值

1. 來看在「物件」的情況下,this 的值會是什麼呢?這裡說的是物件喔,跟物件導向不一樣,要特別注意。

this 的值跟在哪裡被宣告或定義沒有關係,取決於「怎麼呼叫」

const obj = {
  a: 123,
  test: function() {
    console.log(this) // 就是 obj 本身
  }
}
obj.test() // { a: 123, test: [Function: test] }

什麼叫做「取決於在哪裡被呼叫呢」,例如說,下面這個方式,this 就會是 undefined

'use strict'
const obj = {
  a: 123,
  test: function() {
    console.log(this)
  }
}

var func = obj.test
func() // undefined

2. 判斷 this 小撇步: 在呼叫的函式上加上 .call()

例如一開始的例子,可以看作這樣 obj.test() => obj.test.call(obj)

const obj = {
  a: 123,
  test: function() {
    console.log(this)
  }
}
obj.test()

或者是看另一個範例,可以看作 obj.inner.test() => obj.inner.test.call(obj.inner)

'use strict'
const obj = {
  a: 123,
  inner: {
    test: function() {
      console.log(this)
    }
  }
}

obj.inner.test()

再看一個例子,可以看作 func.call(),出來就是 undefined

'use strict'
const obj = {
  a: 123,
  inner: {
    test: function() {
      console.log(this)
    }
  }
}

const func = obj.inner.test
func() // undefined

五、強制綁定 this:bind

利用 .bind() 強制改變 this 的值

綁定完以後,不管呼叫方式為何,this 的值都不會變

.bind() 以後,會回傳一個函式

'use strict'
const obj = {
  a: 123,
  test: function() {
    console.log(this)
  }
}

const bindTest = obj.test.bind(obj)
bindTest() // { a: 123, test: [Function: test] }

綁完以後,不管用任何方法,都不會影響 this 的值

'use strict'
const obj = {
  a: 123,
  test: function() {
    console.log(this)
  }
}

const bindTest = obj.test.bind(obj)
bindTest.apply('111') // 仍然是 obj
bindTest.call('111') // 仍然是 obj

六、arrow function 的 this

箭頭函式限定:箭頭函式裡面的 this,跟你怎麼呼叫沒有關係,跟 scope 比較像,跟你定義在哪裡有關係。

因此下面兩個都會是 Test {}

class Test {
  run () {
    console.log('run this:', this)
    setTimeout(() => {
      console.log(this)
    }, 100);
  }
}

const t = new Test()
t.run()

假設不用箭頭函式呢?

class Test {
  run () {
    console.log('run this:', this) // Test {}
    setTimeout( function() {
      console.log(this) // undefined
    }, 100);
  }
}

const t = new Test()
t.run()

以下為閱讀 淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂 筆記

一但脫離了物件導向,其實 this 就沒有什麼太大的意義

怎麼更改 this 的值?

可以用 call、apply 與 bind 改變 this 的值

待讀文章:

What's THIS in JavaScript ?

JavaScript深入之从ECMAScript规范解读this










Related Posts

Entry-level Software Engineer Quantrix

Entry-level Software Engineer Quantrix

Python Table Manners - Commitizen: 規格化 commit message

Python Table Manners - Commitizen: 規格化 commit message

關於 React 小書:React props

關於 React 小書:React props


Comments