原文: 【你不知道的JavaScript上卷】——作用域與閉包
JS語言萬變不離其宗,其中最常用、最重要的也就是常用的幾個大概念。數據類型、作用域、原型鏈、閉包、this指針、異步,不同的人理解不一樣,不一樣的書講解的也不一樣。但這本《你不知道的JavaScript》系列言簡意賅,直指本質,值得反覆閱讀,每次閱讀都感覺代碼的設計實現精妙之處
一、瀏覽器如何運行代碼?
我們每天寫那麼多代碼來實現各種特效和自動化工程,難道不想知道每行代碼究竟是怎樣被編譯和執行的嗎?
簡單的例子:var a = 2;
變量的賦值操作執行兩個動作:
1、編譯器會在當前作用域中聲明一個變量(如果之前未聲明過)
2、運行時引擎會在作用域中查找該變量,如果查找到就對其賦值
作用域鏈查找原則:
1、引擎從當前的執行作用域開始查找變量,如果查找不到,就會向上一級繼續查找
2、當查找到最外層的全局作用域時,仍未找到則會停止查找
異常判斷:
1、如果RHS查詢在所有作用域中無法找到變量,引擎就會拋出 ReferenceError 異常
2、如果LHS查詢在所有作用域中無法找到變量,在非嚴格模式下會創造一個具有該名稱的變量
3、如果RHS查詢查找到變量,但是對其進行不合理的操作,如對非函數類型調用、查找undefined\null 類型的屬性,則會拋出 TypeError
編譯過程對變量聲明的處理:
1、包括變量和函數在內的所有聲明都會在任何代碼被執行前首先被處理
2、函數聲明能夠被提升,函數表達式不能被提升
3、函數聲明首先被提升,然後是變量提升
二、作用域
作用域:
負責收集並維護由所有聲明的標識符(變量)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前的代碼對這些標識符的訪問權限。
規則:
引擎沿着作用域鏈進行查找標識符
注意:
作用域分兩種,一種爲在詞法階段即確定的作用域,即詞法作用域;另一種爲在代碼運行時確定的作用域,即動態作用域。
三、閉包
閉包是個很不好理解的概念,但用處很大。我一般通常理解爲一個包裝函數中嵌套了函數,函數一直保存着對包裝函數作用域的引用即產生了閉包
閉包:
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前作用域之外執行。
閉包的作用很多,尤其是在ES5時代原生語言沒有模塊,通常都用閉包來營造模塊化,以保持變量的私有化。
面試常見坑:
1、全局作用域 + 閉包
2、IIFE模式封閉for循環
3、塊級作用域 + 閉包
模塊要點:
1、必須有外部的封閉函數,該函數至少被調用一次(每次調用都會創建一個新的模塊實例)
2、封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有作用域中形成閉包,並且可以訪問或者修改私有的狀態
模塊例子:
1、單例模式
2、多例模式