本文爲《你不知道的JavaScript(上卷)》中關於做用域相關的知識點的總結。es6
變量的賦值操做實際上有兩個動做,首先編譯器會在當前做用域中聲明一個變量(若是以前沒有聲明過),而後在運行時引擎會在做用域中查找該變量,若是可以找到就對它進行賦值。瀏覽器
在運行時引擎會在做用域中查找該變量性能優化
引擎對變量所作的查找分爲LHS查詢
以及RHS查詢
,L
和R
分別表明一個賦值操做的左側以及右側。閉包
講的稍微精確一點:RHS
查詢與簡單地查找某個變量的值別無二致,而LHS
則是試圖查找到變量的容器自己,從而對其進行賦值。函數
RHS
能夠理解成retrieve his source value
(取到其源值),這意味着「獲得某某的源值」。性能
深刻一點:優化
console.log(a);
上訴代碼對於a
的引用就是一個RHS
引用,即查找而後取到a
的值。ui
相比之下,this
a = 2;
這裏的a
就是一個RHS
引用。插件
咱們能夠簡單的記憶:
當變量出如今賦值操做的左側時進行
LHS查詢
,出如今賦值操做的右側時進行RHS查詢
.
注意:做用域查找會在找到第一個匹配的標識符時中止
做用域是根據名稱查找變量的一套規則
做用域嵌套的定義以下:
當一個塊或者函數嵌套在另外一個塊或函數中時,就發生了做用域的嵌套。
理解做用域嵌套這一機制,咱們就能夠理解變量查找的順序:
在當前做用域查找變量。若是沒有,則進行下一步
判斷是不是全局做用域。若是是,則中止查找過程;若是不是,則進行下一步
進入當前做用域的外層做用域,並進行第一步
形象一點,咱們能夠把做用域查找想象成在大樓中找人。
第一層表明當前做用域,大樓的頂層表明全局做用域。
首先在當前樓層查找,若是沒有找到,則上一樓進行查找,一直到找到這我的或者找完整個大樓依然沒有找到爲止。
若是能將LHS
以及RHS
進行很好的區分,那咱們就可以很好的理解瀏覽器所拋出的各類異常。
下舉幾種特別常見的報錯:
ReferenceError
:
RHS
查詢變量未找到值
嚴格模式LHS
查詢失敗
TypeError
:
RHS
找到該變量值,但嘗試對這個變量的值進行不合理的操做(例如,引用null
或者undefined
類型的值中的屬性)
詞法做用域徹底由寫代碼期間函數所聲明的位置來定義
注意:欺騙詞法做用域會致使性能降低
eval
eval() 是一個危險的函數, 他執行的代碼擁有着執行者的權利。若是你運行eval()伴隨着字符串,那麼你的代碼可能被惡意方(不懷好意的人)影響, 經過在使用方的機器上使用惡意代碼,可能讓你失去在網頁或者擴展程序上的權限。更重要的是,第三方代碼能夠看到做用域在某一個eval()被調用的時候,這有可能致使一些不一樣方式的攻擊。類似的Function就是不容易被攻擊的。
with
根據你所傳遞給它的對象憑空建立了一個全新的詞法做用域
欺騙詞法做用域會致使性能降低,其緣由在於編譯階段的性能優化不起做用。
JavaScript引擎會在即時編譯階段(during the compilation phase)進行數項的性能優化。其中的某些優化依賴於可以根據代碼的詞法進行靜態分析,並預先肯定全部變量和函數的定義位置,才能在執行的過程當中快速找到標識符。
可是,編譯到含有eval
和with
的代碼時,編譯器沒法知道eval
或者with
會接受什麼代碼,天然沒法作代碼優化。
函數做用域:屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用(事實上在嵌套的做用域中也可使用)。
開發者最主要是利用函數做用域實現隱藏組件或者API的內部實現,最小限度的暴露必要內容。
好比對於一些組件的開發,你們習慣於利用當即執行函數(function() {})()
進行內部實現的封裝。
利用函數做用域將變量保持在私有、無衝突的做用域中,這樣能夠有效規避掉全部的衝突。
舉個例子,underscore
這個庫裏面有跟原生js同樣的方法map
,那怎麼區分這兩個方法呢?經過將map
當作一個屬性掛載在underscore
上面,這樣能夠避免二者的衝突。
形式以下:
(function() {...})()
(function() {...})()
上面兩種形式沒有區別,可依我的興趣隨意使用。
當即執行函數表達式的一種進階用法就是把它們當作函數調用並傳遞參數進去。
各類類庫常見的用法是:
(function(global) { ... })(window)
塊做用域目前在ES6
中有以下體現:
let
const
with
:用with
從對象建立出的做用域僅在with
聲明而非外部做用域中有效。
try/catch
:catch
分句會建立一個塊做用域,其中聲明的變量僅在catch
內部有效。
例如:
for (let i; i < 4; i ++) { ... } console.log(i) // Uncaught ReferenceError: i is not defined
try { undefined(); } catch (err) { console.log(err); } console.log(err); // Uncaught ReferenceError: err is not defined
知乎上面有關於閉包的問題:什麼是閉包?
其中寸志老師的解釋我認爲是比較好的。
對於閉包,《你不知道的JavaScript(上卷)》這本書的解釋是:
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包。
咱們實際上來理解閉包時,須要特別注意是兩個點:函數
和做用域
。
簡單的來講,就是函數以及做用域的結合,注意,做用域必須是封閉的,其主要的表現形式就是函數中返回一個函數。
閉包在類庫、組件封裝中有太多的示例了,本文就不拓展了。
首先看一個單純的閉包的代碼:
for (var i = 0; i <= 5; i++) { (function() { var j = i; setTimeout(function timer(){ console.log(j); }, j * 1000) })() }
這段代碼就是在每次循環的時候建立一個新的封閉做用域,保存當次循環的i值。
再看一下下面的代碼:
for (let i = 0; i <= 5; i++) { setTimeout(function timer(){ console.log(i); }, i*1000) }
利用let建立塊做用域,當塊做用域與閉包結合以後,咱們能夠減小建立新的封閉做用域這一操做(var j = i
);
that's cool!
動態做用域鏈是基於調用棧的,而不是代碼中的做用域嵌套。
對於JavaScript
,不存在動態做用域。若是必定要找一個點與動態詞法做用域扯上關係的話,那就是this
值了。this
值打算在下一篇文章中詳解。
舉個最簡單的例子:
alert(a); // undefined var a = 12;
有一樣做用的是函數聲明function
,例如:
alert(func); // function func(){} function func() {};
可是函數表達式不會提高:
foo(); // TypeError var foo = function bar() { ... }
注意:僅有var
和函數聲明function
才能夠變量提高。
函數聲明與函數表達式的區別:
區別函數聲明和函數表達式最簡單的方法是看
function
關鍵字出如今聲明中的位置(不只僅是一行代碼,而是整個聲明中的位置)。若是function
是聲明中的第一個詞,那麼就是一個函數聲明,不然就是一個函數表達式。
ES6
中新增的let
以及const
關鍵字不能夠進行變量提高,咱們能夠嘗試一下:
// 1. let alert(a); // Uncaught ReferenceError: a is not defined let a = 'abc'; // 2. const alert(b); // Uncaught ReferenceError: b is not defined const b = 123;
先來看下面的代碼:
foo(); // 1 var foo; function foo() { console.log(1); } foo = function() { console.log(2); }
上面的例子說明:
函數會被首先提高,而後纔是變量
上面的代碼實際等於:
function foo() { console.log(1); } foo(); // 1 var foo; foo = function() { console.log(2); }
模塊這一利器,在之前封裝插件用的很是多,示例以下:
var foo = (function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log(something) } function doAnother() { console.log(another.join("!")); } return { doSomething: doSomething, doAnother: doAnother } })()
模塊模式必備條件以下:
必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的莫模塊實例)。
封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或者修改私有的狀態。
固然,說到模塊,咱們不得不提到CMD
、AMD
、ES6 module
等模塊機制了。
知乎上有提到AMD 和 CMD 的區別有哪些?
我這裏簡單提一下二者的區別:
AMD:
early executing(提早執行)
推薦依賴前置
示例:requireJs
CMD:
as lazy as possible(延遲執行)
推薦依賴就近
示例:seaJs
繼續聊一下ES6
的模塊機制(import
、export
)。
import
能夠將一個模塊中的一個或多個API導入到當前的做用域中,並分別綁定在一個變量上。
export
會將當前模塊的一個標識符(變量、函數)導出爲公共API。
Github
有不少基於es6
實現的代碼功能,請自行查閱。
好了,做用域相關的點整理完了,我將其中主要分紅三部分:
做用域
提高
模塊
若是有遺漏,歡迎指正~