JS Advanced --this


Posted by s103071049 on 2021-07-30

先備知識:js 物件導向

this 的意義在哪

這個關鍵字是給在物件導向裡面使用。用 this 代替現在存取到的 instance,如果沒有 this 也沒有其他辦法去設定這個東西。

this 在物件導向的定義:
你是哪一個 instance 去 call 這個 function, this 就會是哪個 instance。

特別注意:this 非物件導向的使用方法,和你想的不一樣。

在沒有意義的地方呼叫 this,預設值會是什麼?

非物件導向的環境使用 this,它的預設值會是 global 的一個東西,這個 global 的東西取決於你在甚麼環境,不同環境的值不同。

依據環境不同,this 的值也會不同,但它都是全域的東西。

node js 環境

function test() {
  console.log(this)
}
test()
===
// 印出的結果
<ref *1> Object [global] {
  global: [Circular *1],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Function (anonymous)]
  },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Function (anonymous)]
  }
}

node.js this 的預設值會是一個叫 global 的變數

function test() {
  console.log(this === global) // true
}
test()

瀏覽器

瀏覽器上 this 的預設是 window

嚴格模式

為甚麼要給 this 預設值 ? this 的值明明就不該指到任何東西。所以 this 又有嚴格模式與寬鬆模式的差別。

一旦開啟嚴格模式 this 的預設值會是 undefined

以 node.js 為例

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

this 跟 function 基本上沒有太大關聯。非物件導向環境,this 值基本上皆為預設值。但有幾種情形例外:(1) 瀏覽器事件的 this 通常是實際做操作的東西。像是 click 你點到哪個按鈕,它的 this 就是哪個按鈕。

'use strict'
function test() {
  var a = 1
  function inner() {
    console.log(this) // undefined
  }
  inner()
}
test()
// 例外一
document.querySelector(".btn").addEventListener('click', function() {
  this
})

小結

不是 eventListener、不是物件導向環境,就只是純粹的 function,this 是沒有任何意義,因為本來就不該有任何東西。這種時候,this 預設為 global 的東西。在 node.js 就是一個叫 global 的變數、在瀏覽器叫 window,一旦開啟嚴格模式 this 的預設值就是 undefined。

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

  • call( ) : 傳進的第一個值是甚麼,this 的值就是甚麼。想傳甚麼就傳甚麼,它的 this 就是 call 第一個參數的值。
'use strict'
function test() {
  console.log(this)
}
test.call(123)
  • apply( ) : 同 call,第一個參數一樣是 this 的值,
'use strict'
function test() {
  console.log(this) // {}
}
test.apply({})

call 與 apply 的差別

(面試愛考題)

  • call(參數一, 參數二, 參數三, 以此類推):用逗號的方式將參數傳下去
  • apply(參數一, 參數二):參數一是 this,參數二是一個陣列。有時候傳一個陣列會比較方便

他們的共同點在於第一個參數可以改變裡面 this 的值。

'use strict'
function test(a, b, c) {
  console.log(this) // haha
  console.log(a, b, c) // 1 2 3
}
test.call('haha', 1, 2, 3)
test.apply('haha', [1, 2, 3])

用另一種角度來看 this 的值

  • this 只有在物件導向下才有意義,在這個關係以外基本上沒有甚麼意義
  • 大重點:this 的值和程式碼在哪邊無關,和你怎麼呼叫有關

當我在呼叫 obj.test( ) 這個值就是 obj 本身 => { a: 123, test: [Function: test] }

'use strict'
const obj = {
  a: 123,
  test: function() {
    console.log(this) // obj
  }
}
obj.test() // 可以看成 obj.test.call(obj)

this 的值跟他在甚麼時候被定義沒有關係,它只關注於你怎麼呼叫它。

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

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

可以拆解成這樣去理解
這個 this 都會是 inner 這個物件。

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

obj.inner.test() // 可以看成 obj.inner.test.call(obj.inner),結果是 { test: [Function: test] }
console.log(obj.inner.test() === obj.inner.test.call(obj.inner)) // true

重點是 function call,func( ) 可以看成 => func.call( undefined )

如果今天是
obj.inner.test( ) 可以看成是 obj.inner.test.call( obj.inner )
obj.test( ) 可以看成是 obj.test.call( obj )
以此類推

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

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

小結

  1. 物件導向無關的地方 call this,this 的值視當時的環境與是否為嚴格模式為 undefined / window / global
  2. 物件導向 call this,this 就是自己的 instance
  3. 物件的情況 call this,可以轉成 function.call( ) 的形式,它第一個參數是甚麼 this 就是甚麼,所以與 function 怎麼被呼叫有關,用不同方式呼叫 this 的值就會不同,要看呼叫的方式才會知道 this 的值是甚麼。

強制綁定 this:bind

自我檢測

'use strict'
function log() {
  console.log(this);
}

var a = { a: 1, log: log };
var b = { a: 2, log: log };

log(); // undefined
a.log(); //{ a: 1, log: [Function: log] } 這個 this 就是 a

b.log.apply(a) // { a: 1, log: [Function: log] } this 的值還是 a 因為被 apply 蓋過

this 的值不同,一個是 a 一個是 undefined。前面說過,跟他呼叫的方法有關

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

obj.test() // { a: 1, test: [Function: test] }
const func = obj.test 
func() // undefined

想要一個 function 不管怎麼 call 它的 this 值都不變,我們可以先將他 this 的值決定好

透過 bind 實現這個功能

將 obj 當作 this 放進 obj.test,接著無論怎麼 call,它 this 的結果都會是 obj,因為已經將它 this 的結果給綁定住了

'use strict'
const obj = {
  a: 1,
  test: function() {
    console.log(this)
  }
}
const bindTest = obj.test.bind(obj)
bindTest() // { a: 1, test: [Function: test] }

同理,bind(想放甚麼就放甚麼),可以強制改變 this 裡面的值

'use strict'
const obj = {
  a: 1,
  test: function() {
    console.log(this)
  }
}
const bindTest = obj.test.bind('six')
bindTest() // six
bindTest.call() //six

bind vs call & apply

bind 完之後會回傳 function,call( )、apply( ) 是直接呼叫 function,用 call() 也無法影響 bind 的值。無論用甚麼呼叫方法都不會改變 this 的值,因為他已經綁定好了。

用途

.bind( ) 就是去綁定 this 的值用的。不想 this 值變動,就用 bind 的方式把 this 給綁定進那個 function,再去呼叫那個綁定好的 function。呼叫它時就可以預設它的值是你當初綁定好的那個值,就不需要擔心 this 值因為呼叫方法不同而不同。

特例:arrow function 的 this

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

setTimeout 過一百毫秒會執行這個 function,在 node.js 上它的值是狀況一、在瀏覽器上它的值是 undefined => 因為非同步,一百秒過後執行,它就只是一般執行 function 的方法,同狀況二

'use strict'
class Test {
  run() {
    console.log('run this: ', this) //  Test {} 就是 t 這個 instance
    setTimeout(function() {
      console.log(this)
    }, 100)
  }
}
const t = new Test()
t.run()
===
// 狀況一
Timeout {
  _idleTimeout: 100,
  _idlePrev: null,
  _idleNext: null,
  _idleStart: 53,
  _onTimeout: [Function (anonymous)],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(refed)]: true,
  [Symbol(kHasPrimitive)]: false,
  [Symbol(asyncId)]: 5,
  [Symbol(triggerId)]: 1
}
===
// 狀況二

function test() {
  console.log(this)
}

改成箭頭函式執行發現它 log 出的值會不一樣,它會去用它定義那邊的 this 值

再重申一次,箭頭函式裡面的 this 和你怎麼呼叫沒有關係,它跟 scope 比較像。它跟你定義在程式碼的哪裡有關係。

同樣的代碼,我用 function 它的值是 undefined,我用箭頭函式它的值是 Test {},因為我在 run 裡面定義了 setTimeout 這個函式,所以它的 this 就會是 console.log('run this: ', this) 這邊的 this。

'use strict'
class Test {
  run() {
    console.log('run this: ', this) //  Test {} 就是 t 這個 instance
    setTimeout(() => {
      console.log(this) // Test {}
    }, 100)
  }
}
const t = new Test()
t.run()

小結

箭頭函式的 this 和你怎麼呼叫無關,與你在哪裡定義有關。可以當作 this 的特例看待


#this







Related Posts

[21] 強制轉型 - ToNumber、ToPrimitive、StringNumericLiteral、NonDecimalIntegerLiteral

[21] 強制轉型 - ToNumber、ToPrimitive、StringNumericLiteral、NonDecimalIntegerLiteral

在 Express 上面把資料變美吧

在 Express 上面把資料變美吧

筆記、[BE201] 後端中階:Express

筆記、[BE201] 後端中階:Express


Comments