鑑於this
風騷的運作方式,對this
的理解是永不過時的話題,本文試圖通過將其大卸六塊來釘住這個磨人的妖精。
首先
this
is all about context.
this
說白了就是找大佬,找擁有當前上下文(context)的物件(context object)。
大佬可以分為六層,層數越高權力越大,this
只會認最大的。
第一層:世界盡頭
權力最小的大佬是作為備胎的存在,在普通情況下就是全域性,瀏覽器裡就是window
;在use strict
的情況下就是undefined
。
1 2 3 4 5 6 7 8 9 |
function showThis () { console.log(this) } function showStrictThis () { 'use strict' console.log(this) } showThis() // window showStrictThis() // undefined |
第二層:點石成金
第二層大佬說白了就是找這個函式前面的點.
。
如果用到this
的那個函式是屬於某個 context object 的,那麼這個 context object 繫結到this
。
比如下面的例子,boss
是returnThis
的 context object ,或者說returnThis
屬於boss
。
1 2 3 4 5 6 7 |
var boss = { name: 'boss', returnThis () { return this } } boss.returnThis() === boss // true |
下面這個例子就要小心點咯,能想出答案麼?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var boss1 = { name: 'boss1', returnThis () { return this } } var boss2 = { name: 'boss2', returnThis () { return boss1.returnThis() } } var boss3 = { name: 'boss3', returnThis () { var returnThis = boss1.returnThis return returnThis() } } boss1.returnThis() // boss1 boss2.returnThis() // ? boss3.returnThis() // ? |
答案是boss1
和window
哦,猜對了嗎。
只要看使用this
的那個函式。
在boss2.returnThis
裡,使用this
的函式是boss1.returnThis
,所以this
繫結到boss1
;
在boss3.returnThis
裡,使用this
的函式是returnThis
,所以this
繫結到備胎。
要想把this
繫結到boss2
怎麼做呢?
1 2 3 4 5 6 7 8 9 10 11 |
var boss1 = { name: 'boss1', returnThis () { return this } } var boss2 = { name: 'boss2', returnThis: boss1.returnThis } boss2.returnThis() //boss2 |
沒錯,只要讓使用this
的函式是屬於boss2
就行。
第三層:指腹為婚
第三層大佬是Object.prototype.call
和Object.prototype.apply
,它們可以通過引數指定this
。(注意this
是不可以直接賦值的哦,this = 2
會報ReferenceError
。)
1 2 3 4 5 6 7 |
function returnThis () { return this } var boss1 = { name: 'boss1' } returnThis() // window returnThis.call(boss1) // boss1 returnThis.apply(boss1) // boss1 |
第四層:海誓山盟
第四層大佬是Object.prototype.bind
,他不但通過一個新函式來提供永久的繫結,還會覆蓋第三層大佬的命令。
1 2 3 4 5 6 7 8 |
function returnThis () { return this } var boss1 = { name: 'boss1'} var boss1returnThis = returnThis.bind(boss1) boss1returnThis() // boss1 var boss2 = { name: 'boss2' } boss1returnThis.call(boss2) // still boss1 |
第五層:內有乾坤
一個比較容易忽略的會繫結this
的地方就是new
。當我們new
一個函式時,就會自動把this
繫結在新物件上,然後再呼叫這個函式。它會覆蓋bind
的繫結。
1 2 3 4 5 6 7 8 9 10 11 |
function showThis () { console.log(this) } showThis() // window new showThis() // showThis var boss1 = { name: 'boss1' } showThis.call(boss1) // boss1 new showThis.call(boss1) // TypeError var boss1showThis = showThis.bind(boss1) boss1showThis() // boss1 new boss1showThis() // showThis |
第六層:軍令如山
最後一個法力無邊的大佬就是 ES2015 的箭頭函式。箭頭函式裡的this
不再妖豔,被永遠封印到當前詞法作用域之中,稱作 Lexical this ,在程式碼執行前就可以確定。沒有其他大佬可以覆蓋。
這樣的好處就是方便讓回撥函式的this
使用當前的作用域,不怕引起混淆。
所以對於箭頭函式,只要看它在哪裡建立的就行。
如果對 V8 實現的詞法作用域感興趣可以看看這裡。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function callback (cb) { cb() } callback(() => { console.log(this) }) // window var boss1 = { name: 'boss1', callback: callback, callback2 () { callback(() => { console.log(this) }) } } boss1.callback(() => { console.log(this) }) // still window boss1.callback2(() => { console.log(this) }) // boss1 |
下面這種奇葩的使用方式就需要注意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var returnThis = () => this returnThis() // window new returnThis() // TypeError var boss1 = { name: 'boss1', returnThis () { var func = () => this return func() } } returnThis.call(boss1) // still window var boss1returnThis = returnThis.bind(boss1) boss1returnThis() // still window boss1.returnThis() // boss1 var boss2 = { name: 'boss2', returnThis: boss1.returnThis } boss2.returnThis() // boss2 |
如果你不知道最後為什麼會是 boss2,繼續理解“對於箭頭函式,只要看它在哪裡建立”這句話。
參考
- Mozilla Developer Network
- Kyle Simpson, this & object prototypes
- Axel Rauschmayer, Speaking JavaScript