this 到底是個什么東西
MDN 上對于 this 如此定義:
A property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value.
上面這段話,主要講的是 this 的存在形式庙洼,這當然不太夠斑响,也許我們更想要搞明白的是瞧挤,它是為了什么存在屉凯。
事實上父虑,當我們在獲取一個變量的時候母赵,我們想要知道逸爵,它是誰的?當我們執(zhí)行一個函數(shù)的時候凹嘲,我們又得知道师倔,這一次函數(shù)的執(zhí)行,到底是針對誰的周蹭?
我想趋艘,這就是 this。
如何確定 this? call, bind, apply
它在非嚴格模式下總是一個對象凶朗,在嚴格模式下瓷胧,可以是任何值。
當我們沒有特別指定 this 的時候棚愤,this 實際上就是它所出現(xiàn)在的執(zhí)行環(huán)境所對應的對象搓萧,我們在 window 中寫個 this,代碼跑到這里的時候宛畦,編譯器就知道瘸洛,這個 this 就是指 window.
而如果我們想要(需要)主動設(shè)置 this …… 那,就要深入談一談 call / bind / apply 了
這三個方法主要是用于函數(shù)的次和,當我們調(diào)用一個函數(shù)的時候反肋,這個函數(shù)的這一次執(zhí)行,究竟是“針對誰”斯够,這是每個函數(shù)在每一次執(zhí)行的時候都要考慮的囚玫。我們所習以為常的 fn()
實際上是一個語法糖,當我們在 window 里面執(zhí)行這個函數(shù)的時候读规,它的原始面貌是:
fn.call(window)
call / bind / apply 的第一參數(shù)就的 this.
也就是說抓督,如果我們不偷懶的話, fn.call 才是真正的“函數(shù)調(diào)用”(即束亏,調(diào)用了函數(shù)的 call 方法)铃在,我們每次調(diào)用函數(shù)的時候都要告訴函數(shù) this 是什么。這里碍遍,我們只比較 call 與 bind定铜,因為,apply 與 call 很類似怕敬,只是參數(shù)的形式不一樣揣炕。
bind 是什么呢?當 bind 出現(xiàn)的時候东跪,實際上表達的意思是 “我要重新創(chuàng)一個看起來一樣的函數(shù)畸陡,但是這個函數(shù)執(zhí)行的時候 this 永遠指向我最初指定的鹰溜。”
所以丁恭,fn.bind(window)
得到曹动,不是函數(shù)的執(zhí)行,而是一個 this 被“綁”死了的函數(shù)牲览。那么墓陈,它與 call 的差異顯而易見: call 是執(zhí)行函數(shù),bind 是創(chuàng)建函數(shù)第献,就算是 bind 生成的函數(shù)在執(zhí)行的時候贡必,依然需要用到 call 方法,只不過痊硕,call 指定的 this 是無效的赊级。
let b = fn.bind(window)
b.call(a) // 實際還是 b.call(window),因為 b 變量對應的函數(shù)岔绸,在創(chuàng)建的時候 this 已經(jīng) bind.
這一點有什么用呢理逊?舉個例子:
MDN 中對于數(shù)組的 slice 方法有一個描述,
slice method can also be called to convert Array-like objects / collections to a new Array.
slice 方法可以接收一個類數(shù)組盒揉,返回一個新數(shù)組晋被。
于是我們就可以構(gòu)建一個“數(shù)組轉(zhuǎn)換器”:
let slice = Function.prototype.call.bind(Array.prototype.slice)
// 這是 MDN 中的例子
你沒看錯,我們在 call 后面在接一個 bind刚盈,因為 call 也是一個函數(shù)羡洛,
這段代碼翻譯成人話就是,slice 這個變量藕漱,被賦值了一個 call 函數(shù)欲侮,這個 call 函數(shù)的 this 綁定了 Array.prototype.slice
(有意思,不只是普通函數(shù)有 this肋联,call 函數(shù)也有 this 的)
于是威蕉,我們就可以用了:
slice({0:"a", 1:"b", length:2}) // → ["a", "b"]
好處是,上面這段代碼就等價于:
Array.prototype.slice({0:"a", 1:"b", length:2})
//為了對比橄仍,我們采用不用 call 的語法糖寫法
你看韧涨,這一下子就省了1… 2… 3 …… 好多好多字符呢~
總結(jié)下來,每當我們執(zhí)行一個函數(shù)(非箭頭函數(shù))的時候侮繁,只要看一看這個函數(shù)相關(guān)的 bind虑粥,再把它轉(zhuǎn)化為 fn.call(this) 的形式就能夠很好確定 this 了。
對了宪哩,語法糖寫法的 this 就是被調(diào)用的函數(shù)前面的一大堆東西:
a.b.c.fn() // 等價于 a.b.c.fn.call(a.b.c)
用 class 生成對象時的 this
官方說了算:this 指向新生成的對象
arrow funcion
官方說娩贷,箭頭函數(shù)里面的 this 由箭頭函數(shù)執(zhí)行時的環(huán)境 this 決定,換而言之锁孟,箭頭函數(shù)出不出現(xiàn)育勺,并不影響 this 的指向但荤,我們不需要告訴箭頭函數(shù)它是針對誰罗岖,反正它很“隨遇而安” ……
他們說了算 ……
其它
是的涧至,總是“他們說了算”,當我們調(diào)用任何一個 API 的時候桑包,實際的代碼對于我們來說就好像是藏在“黑匣子”里南蓬,this 是什么,只有開發(fā)人員知道:
dom.onclick = fn
在dom API 的事件觸發(fā)函數(shù)中哑了, this 是觸發(fā)當前事件的元素赘方。
再比如,React 在調(diào)用 onClick 的時候弱左,會強制把 this 指向 undefined …… 所以我們在 React 中做父子通信的時候常常要用到 bind窄陡。
嚴格模式
還是看回最初的那段話:
A property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value.
它是執(zhí)行環(huán)境下的一個屬性,在非嚴格模式下拆火,總是指向?qū)ο筇玻趪栏衲J较拢梢允侨魏沃怠?br>
非常有意思们镜,在非嚴格模式下币叹,我們嘗試把 this 指向 字符串、數(shù)字模狭、布爾值 的時候颈抚,瀏覽器會把這些值轉(zhuǎn)換為對象的形式。
而如果我們嘗試把 this 指向 undefined, null 的時候嚼鹉,this 會變成 window.
而在嚴格模式下贩汉,我們指定 this 是什么,它就真的是什么
寫在最后
在討論 bind & call 的時候锚赤,我們就注意到一個有去的想象:bind / call 本身就是函數(shù)匹舞,那么他們有沒有 call, bind 函數(shù)呢?肯定有宴树,因為 javascript 中這些方法都只不過是一個繼承
就像最初的例子策菜, call.bind
執(zhí)行,call.call
也存在酒贬,之所以 fn.call.call() 會報錯又憨,那是因為,默認情況下锭吨,使用 call 又沒輸入 this 參數(shù)的時候蠢莺,this 默認是 window, 而 call 的 this 必須是一個函數(shù)(別忘了,函數(shù)是一類特殊的對象):
其實還有點什么想說
當我在思考 this 的時候零如,我總不由自主地受到“作用域”的影響躏将。
現(xiàn)在看來锄弱,還是挺明了的:this 跟作用域關(guān)系不大的,作用域討論的是函數(shù)的固有屬性祸憋,而在函數(shù)里面討論到 this 是在確定函數(shù)某一次執(zhí)行的特定歸屬 ……