先備知識: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
小結
- 物件導向無關的地方 call this,this 的值視當時的環境與是否為嚴格模式為 undefined / window / global
- 物件導向 call this,this 就是自己的 instance
- 物件的情況 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 的特例看待