JavaScript中做用域相關的那些點

本文爲《你不知道的JavaScript(上卷)》中關於做用域相關的知識點的總結。es6

做用域

賦值操做

變量的賦值操做實際上有兩個動做,首先編譯器會在當前做用域中聲明一個變量(若是以前沒有聲明過),而後在運行時引擎會在做用域中查找該變量,若是可以找到就對它進行賦值。瀏覽器

LHS以及RHS

在運行時引擎會在做用域中查找該變量性能優化

引擎對變量所作的查找分爲LHS查詢以及RHS查詢LR分別表明一個賦值操做的左側以及右側。閉包

講的稍微精確一點:RHS查詢與簡單地查找某個變量的值別無二致,而LHS則是試圖查找到變量的容器自己,從而對其進行賦值。函數

RHS能夠理解成retrieve his source value(取到其源值),這意味着「獲得某某的源值」。性能

深刻一點:優化

console.log(a);

上訴代碼對於a的引用就是一個RHS引用,即查找而後取到a的值。ui

相比之下,this

a = 2;

這裏的a就是一個RHS引用。插件

咱們能夠簡單的記憶:

當變量出如今賦值操做的左側時進行LHS查詢,出如今賦值操做的右側時進行RHS查詢.

注意:做用域查找會在找到第一個匹配的標識符時中止

做用域嵌套

做用域是根據名稱查找變量的一套規則

做用域嵌套的定義以下:

當一個塊或者函數嵌套在另外一個塊或函數中時,就發生了做用域的嵌套。

理解做用域嵌套這一機制,咱們就能夠理解變量查找的順序:

  1. 在當前做用域查找變量。若是沒有,則進行下一步

  2. 判斷是不是全局做用域。若是是,則中止查找過程;若是不是,則進行下一步

  3. 進入當前做用域的外層做用域,並進行第一步

形象一點,咱們能夠把做用域查找想象成在大樓中找人。

第一層表明當前做用域,大樓的頂層表明全局做用域。

首先在當前樓層查找,若是沒有找到,則上一樓進行查找,一直到找到這我的或者找完整個大樓依然沒有找到爲止。

異常報錯的種類

若是能將LHS以及RHS進行很好的區分,那咱們就可以很好的理解瀏覽器所拋出的各類異常。

下舉幾種特別常見的報錯:

  • ReferenceError:

    1. RHS查詢變量未找到值

    2. 嚴格模式LHS查詢失敗

  • TypeError:

    1. RHS找到該變量值,但嘗試對這個變量的值進行不合理的操做(例如,引用null或者undefined類型的值中的屬性)

詞法做用域

詞法做用域徹底由寫代碼期間函數所聲明的位置來定義

欺騙詞法做用域

注意:欺騙詞法做用域會致使性能降低

eval

eval() 是一個危險的函數, 他執行的代碼擁有着執行者的權利。若是你運行eval()伴隨着字符串,那麼你的代碼可能被惡意方(不懷好意的人)影響, 經過在使用方的機器上使用惡意代碼,可能讓你失去在網頁或者擴展程序上的權限。更重要的是,第三方代碼能夠看到做用域在某一個eval()被調用的時候,這有可能致使一些不一樣方式的攻擊。類似的Function就是不容易被攻擊的。

with

根據你所傳遞給它的對象憑空建立了一個全新的詞法做用域

性能問題

欺騙詞法做用域會致使性能降低,其緣由在於編譯階段的性能優化不起做用

JavaScript引擎會在即時編譯階段(during the compilation phase)進行數項的性能優化。其中的某些優化依賴於可以根據代碼的詞法進行靜態分析,並預先肯定全部變量和函數的定義位置,才能在執行的過程當中快速找到標識符。

可是,編譯到含有evalwith的代碼時,編譯器沒法知道eval或者with會接受什麼代碼,天然沒法作代碼優化。

函數做用域以及塊做用域

函數做用域:屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用(事實上在嵌套的做用域中也可使用)。

隱藏組件內部實現

開發者最主要是利用函數做用域實現隱藏組件或者API的內部實現,最小限度的暴露必要內容。

好比對於一些組件的開發,你們習慣於利用當即執行函數(function() {})()進行內部實現的封裝。

規避衝突

利用函數做用域將變量保持在私有、無衝突的做用域中,這樣能夠有效規避掉全部的衝突。

舉個例子,underscore這個庫裏面有跟原生js同樣的方法map,那怎麼區分這兩個方法呢?經過將map當作一個屬性掛載在underscore上面,這樣能夠避免二者的衝突。

當即執行函數表達式

形式以下:

  1. (function() {...})()

  2. (function() {...})()

上面兩種形式沒有區別,可依我的興趣隨意使用。

當即執行函數表達式的一種進階用法就是把它們當作函數調用並傳遞參數進去。

各類類庫常見的用法是:

(function(global) {
    ...
})(window)

塊做用域

塊做用域目前在ES6中有以下體現:

  1. let

  2. const

  3. with:用with從對象建立出的做用域僅在with聲明而非外部做用域中有效。

  4. try/catchcatch分句會建立一個塊做用域,其中聲明的變量僅在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
    }
})()

模塊模式必備條件以下:

  1. 必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的莫模塊實例)。

  2. 封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或者修改私有的狀態。

固然,說到模塊,咱們不得不提到CMDAMDES6 module等模塊機制了。

知乎上有提到AMD 和 CMD 的區別有哪些?

我這裏簡單提一下二者的區別:

  • AMD:

    • early executing(提早執行)

    • 推薦依賴前置

    • 示例:requireJs

  • CMD:

    • as lazy as possible(延遲執行)

    • 推薦依賴就近

    • 示例:seaJs

繼續聊一下ES6的模塊機制(importexport)。

import能夠將一個模塊中的一個或多個API導入到當前的做用域中,並分別綁定在一個變量上。

export會將當前模塊的一個標識符(變量、函數)導出爲公共API。

Github有不少基於es6實現的代碼功能,請自行查閱。

結語

好了,做用域相關的點整理完了,我將其中主要分紅三部分:

  1. 做用域

  2. 提高

  3. 模塊

若是有遺漏,歡迎指正~

相關文章
相關標籤/搜索