《你不知道的Javascript--上卷 學習總結》(做用域)

做用域是什麼?

編譯原理

一、分詞/詞法分析瀏覽器

將字符組成的字符串分解成有意義的代碼塊,稱爲詞法單元bash

二、解析/語法分析閉包

將詞法單元流轉換成一個由元素逐級嵌套所組成的抽象語法樹(AST)。函數

三、代碼生成工具

將AST轉換爲可執行的過程被稱爲代碼生成。ui

理解做用域

做用域是一套規則,用於肯定在何處以及如何查找變量(標識符)。如何查找的目的是對變量進行賦值,那麼就是使用LHS查詢。若是目的是獲取變量的值,就會使用RHS查詢this

=操做符及調用函數時傳入參數的操做都會致使賦值操做,也就會致使LHS查詢spa

Javascript引擎首先會在代碼執行前對其進行編譯,在這個過程當中,像var a = 2這樣的聲明會被分解成兩個獨立的步驟code

  • 首先,var a 在其做用域中聲明新變量。這會在最開始的階段,也就是代碼執行前執行。
  • 接下來,a = 2會查詢(LHS)查詢變量a並對其進行賦值。

LHS和RHS查詢都會在當前執行做用域中開始,若是沒有找到查詢的標識符,就會向上級做用域繼續查找目標標識符,最終抵達全局做用域,不管找到仍是沒找到都將暫停。(這就造成了一條做用域鏈對象

不成功的RHS引用會致使ReferenceError異常.不成功的LHS引用會致使自動隱式地建立一個全局變量(非嚴格模式下),該變量使用LHS引用的目標做爲標識符,或者拋出ReferenceError異常(嚴格模式下)

詞法做用域

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

全局變量會自動成爲全局對象(好比瀏覽器中的window對象)的屬性,所以能夠不直接經過全局對象的詞法名稱,而是間接地經過對全局對象屬性的引用來對其進行訪問。window.a經過這種技術能夠訪問那些被同名變量所遮蔽的全局變量。但非全局的變量若是被遮蔽了,不管如何都沒法被訪問到。

不管函數在哪裏被調用,也不管它如何被調用,它的詞法做用域都只由函數被聲明時所處的位置(這裏就會涉及到this的問題了)

欺騙詞法

若是想修改詞法做用域,js中有兩種機制來實現這個目的,可是並不推薦。

  • eval/new Function()

    eval() 函數能夠接受一個字符串參數,並將其中的內容視爲好像在書寫時就存在於程序中的這個位置的代碼。下面代碼就實現了使用eval來達到欺騙的目的,來遮蔽外部變量。(注意嚴格模式下會報 b is not defined

    new Function() 函數的行爲也很相似,最後一個參數能夠接受代碼字符串,並將其轉化爲動態生成的函數(前面的參數是這個新生成的函數的形參)。

function foo(str,a){
        eval(str)//欺騙!
        console.log(a,b);
    }
    var b = 2;
    foo("var b = 3;",1)//1,3
複製代碼
var sum = new Function('a', 'b', 'return a + b');

    console.log(sum(2, 6));
    // expected output: 8

複製代碼
  • with

    with一般被看成重複引用同一個對象中的多個屬性的快捷方式,能夠不須要重複引用對象自己。例如:

var obj = {
        a:1,
        b:2,
        c:3
    }
    obj.a=2;
    obj.b=3;
    obj.c=4;
    //簡單的快捷方式
    with(obj){
        a=3;
        b=4;
        c=5;
    }
複製代碼

但實際上這不只僅是爲了方便地訪問對象屬性。考慮以下代碼:

function foo(obj){
        with(obj){
            a = 2;
        }
    }
    
    var o1 = {
        a:3
    }
    var o2 = {
        b:3
    }
    
    foo(o1)
    console.log(o1.a) //2
    
    foo(o2); // o2沒有a屬性,所以不會建立這個屬性。
    console.log(o2.a) // undefined
    console.log(a) //2   已經放在了全局上了
複製代碼

上述當咱們傳遞o1給with時,with所聲明的做用域是o1,而這個做用域中含有一個同o1.a屬性相符的標識符。但當咱們將o2做爲做用域時,其中並無a標識符,所以進行了正常的LHS標識符查詢o2的做用域,所以當a=2執行時,自動建立了一個全局變量。

函數做用域和塊做用域

函數中的做用域

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

全局命名空間

變量衝突的一個典型例子存在於全局做用域中。一般不少庫在全局做用域中聲明一個名字足夠獨特的變量,一般是一個對象。這個對象被用做庫的命名空間,全部須要暴露給外界的功能都會成爲這個對象的屬性,而不是將本身的標識符暴露在頂級詞法做用域中。

塊做用域

  • with能夠建立一個塊做用域,其建立的做用域僅在with聲明中而非外部做用域中有效。
  • try/catch的catch分句會建立一個塊做用域,其中聲明的變量僅在catch內部有效。
  • let。 let關鍵字能夠將變量綁定到所在的任意做用域中(一般是{...}內部)。換句話說,let爲其聲明的變量隱式地建立在所在的塊做用域 (注意:使用let進行的聲明不會在塊做用域中進行提高。聲明的代碼被運行以前,聲明並不‘存在’使用let爲變量顯示聲明塊做用域能夠及時通知引擎來進行垃圾回收,並對變量進行本地綁定是很是有用的工具。而且這個變量不是定義在window上的
{
        console.log(bar); // ReferenceError! 暫時性死區
        let bar = 1;
    }
複製代碼
  • const。 const也能夠建立塊做用域,可是其建立值以後是固定的,不能夠進行更改。

提高

使用var聲明的變量和函數在內的全部聲明都會在任何代碼被執行前首先被處理。會跑到當前做用域的最上面。這就是變量提高

函數聲明會被提高,可是函數表達式卻不會被提高。即便是具名的函數表達式,名稱標識符在賦值以前也沒法在所在做用域中使用。

foo(); // TypeError
    bar() //  ReferenceError
    
    var foo = function bar() {
        
    }
複製代碼

同名的函數和變量提高,函數的優先級更高,而且多個同名的函數,後者會覆蓋前者

做用域閉包

當函數可以記住並訪問其所在的詞法做用域,就產生了閉包,即便函數是在當前詞法做用域以外執行。

循環和閉包

for(var i = 1;i<=5;i++){
        setTimeout(function(){
            console.log(i) // 輸出5個6
        },i*1000)
    }
    
    // 解決方案1: 閉包
    for(var i = 1;i<=5;i++){
        (function(j){
           setTimeout(function(){
                console.log(j) 
            },j*1000) 
        })(i)
    }
    // 解決方案2: 塊級做用域
    for(let i = 1;i<=5;i++){
        setTimeout(function(){
            console.log(i) 
        },i*1000)
    }
複製代碼
相關文章
相關標籤/搜索