前幾天學(xué)習(xí)Standard ML時(shí)候派敷,我接觸了一個(gè)詞語,叫做“詞法作用域”,也叫“靜態(tài)作用域”。然后我發(fā)現(xiàn)了js中的一堆神奇的概念挤悉,貌似突然清晰了(明明之前我也看過的這個(gè)詞的)桶唐。
動態(tài)作用域
動態(tài)作用域現(xiàn)在不太采用缰儿,但是原理特別簡單元镀。整個(gè)程序只有一個(gè)作用域的棧,每次在定義新的綁定時(shí)候掌测,都直接push進(jìn)去内贮,每次退出函數(shù)的時(shí)候,或者退出大括號包圍的環(huán)境時(shí)候汞斧,把綁定push出來夜郁。早期的語言可能會用它。每次需要某個(gè)值時(shí)候粘勒,不斷從棧頂往下面找值竞端,直到找到某個(gè)標(biāo)識符符合的映射。
詞法作用域
現(xiàn)在大部分的語言都是采用詞法作用域了仲义。普通老百姓(排除學(xué)術(shù)派的小伙伴)用的語言都是婶熬。
無頭等函數(shù)的語言的詞法作用域
在沒有first-class function的語言,比如C中埃撵,在編譯時(shí)期就確定了每個(gè)名字對應(yīng)的作用域赵颅。并不會因?yàn)檫\(yùn)行環(huán)境的不一樣取到不一樣的值,也不會因?yàn)檎{(diào)用的順序不同而有時(shí)候存在暂刘,有時(shí)候消失饺谬。
頭等函數(shù)的語言的詞法作用域
在first-class function的語言中,比如js中谣拣。(我們只考慮不采用eval和with的js)
函數(shù)的內(nèi)部綁定可能會隨著傳出或者傳入函數(shù)對象募寨,同時(shí)把綁定也傳過去。
比如
const f = a=>b=>a+b
const m = f(1)
m
無論在什么環(huán)境下森缠,都能充當(dāng)+1的函數(shù)拔鹰。因?yàn)閮?nèi)部有a=1
的綁定。
(所以贵涵,js的垃圾回收可能會有點(diǎn)復(fù)雜)
JavaScript的函數(shù)調(diào)用
我把JavaScript的函數(shù)調(diào)用分為下面幾種情況討論
最簡單的call調(diào)用
最簡單的情況是call列肢,是毫無黑魔法的情況恰画。
函數(shù)定義時(shí)候,都會隱含著一個(gè)默認(rèn)參數(shù)瓷马,this拴还。
MDN上,寫著call的語法欧聘。
fun.call(thisArg, arg1, arg2, ...)
我們可以忽略環(huán)境地片林,顯式指定函數(shù)的this參數(shù)。
比如
const bar={
foo:function (){
console.log(this);
}
}
bar.foo.call("2333")
bar.foo.call(Object("2333"))
bar.foo.call(undefined)
bar.foo.call(null)
bar.foo()
const foo = bar.foo
foo()
由于怀骤,this一定要是Object费封,是其他類型的話,會被轉(zhuǎn)成Object的晒喷,所以前兩次調(diào)用是一樣的效果孝偎;
第三,四次由于在非嚴(yán)格模式下凉敲,會自動指向全局的global或者window;
等價(jià)于call的apply調(diào)用
apply只是把call的后續(xù)n個(gè)參數(shù)放在一個(gè)數(shù)組里了寺旺。不細(xì)述爷抓。
最常見的隱含this調(diào)用
const bar={
foo:function (){
console.log(this);
}
}
bar.foo.call("2333")
bar.foo.call(Object("2333"))
bar.foo.call(undefined)
bar.foo.call(null)
bar.foo()
const foo = bar.foo
foo()
第五次時(shí)候,不采用call調(diào)用阻塑,則會把調(diào)用的環(huán)境bar傳進(jìn)去蓝撇;
第六次時(shí)候,不采用call調(diào)用陈莽,則會把調(diào)用的環(huán)境window傳進(jìn)去渤昌。
默認(rèn)傳入的this參數(shù)的n種情況
- 當(dāng)以對象里的方法的方式調(diào)用函數(shù)時(shí),它們的 this 是調(diào)用該函數(shù)的對象.
- 在箭頭函數(shù)中走搁,this 是根據(jù)當(dāng)前的詞法作用域來決定的独柑,就是說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的this 綁定(無論this 綁定到什么)私植。在全局作用域中忌栅,它會綁定到全局對象上。
- 使用new關(guān)鍵字曲稼,它的this被綁定到正在構(gòu)造的新對象索绪。
- 該方法存在于一個(gè)對象的原型鏈上,那么this指向的是調(diào)用這個(gè)方法的對象贫悄,就好像該方法本來就存在于這個(gè)對象上
- 用作getter或setter的函數(shù)都會把 this 綁定到正在設(shè)置或獲取屬性的對象瑞驱。
- 當(dāng)函數(shù)被用作事件處理函數(shù)時(shí),它的this指向觸發(fā)事件的元素(一些瀏覽器在使用非addEventListener的函數(shù)動態(tài)添加監(jiān)聽函數(shù)時(shí)不遵守這個(gè)約定)窄坦。
- 當(dāng)代碼被內(nèi)聯(lián)處理函數(shù)調(diào)用時(shí)唤反,它的this指向監(jiān)聽器所在的DOM元素
(以上都是MDN上摘錄的晰筛,習(xí)慣了js的人應(yīng)該一看都可以想到應(yīng)用場景(吐槽:this真是一個(gè)奇葩的設(shè)計(jì),好好的和python一個(gè)搞一個(gè)顯示的self參數(shù)不是更棒拴袭?))
一個(gè)永遠(yuǎn)劫持this參數(shù)的函數(shù)的bind方法
bind可能會改變this的指向读第,這個(gè)有一點(diǎn)點(diǎn)復(fù)雜,我寫了個(gè)函數(shù)模擬拥刻,我覺得我應(yīng)該新開一篇文章講解(雖然好多人曾經(jīng)都講過了)
總結(jié)
js采用的是一直都是詞法作用域怜瞒。
this是一個(gè)隱式傳遞的參數(shù),由多條復(fù)雜規(guī)矩限定般哼。
如果我們不使用call吴汪,apply和bind,js的this的由于默認(rèn)傳入的this的特殊行為蒸眠,表現(xiàn)的行為類似于動態(tài)作用域漾橙,實(shí)質(zhì)還是詞法作用域。(這個(gè)是何幻大大教我的)
我是在理解無黑魔法的基礎(chǔ)上的call楞卡,把其他等價(jià)過去理解的霜运。另外我建議不要使用arguments,因?yàn)閑s6給了更好的解決方案蒋腮。