全面瞭解JS做用域

執行上下文(也稱執行環境)堆棧

執行上下文是javascript最重要的一個概念,執行上下文定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。而在javascript中有三種執行上下文: 全局執行上下文, 函數執行上下文, eval執行上下文。代碼在其執行上下文中執行。在javascript中只有一個全局執行環境(根據宿主環境的不一樣,全局執行環境的對象也不同)。能夠有許多函數和eval執行環境的實例,每次調用一個函數或eval,都會進入對應執行環境執行代碼。注意一個函數可能會產生無限上下文集合,由於每次函數調用自身都會產生一個新的執行上下文javascript

ec-stack.png

執行上下文能夠激活另外一個執行上下文,例如函數調用另外一個函數(或者全局執行上下文調用全局函數)等等,邏輯上就成了一個堆棧。這被稱爲執行上下文堆棧。
當執行流進入一個函數時,函數的上下文就會被推入一個棧中,若是在當前函數中調用另外一個函數,當前函數就會暫停執行,並將執行流傳遞給被調用函數(被調用函數同事多是其餘函數的調用者),被調用者被推入堆棧。當被調用者的上下文結束後,將執行流交還給調用者,調用者的繼續運行代碼,直到結束後,棧將上下文彈出。java

ec-stack-changes.png

執行上下文

每一個執行上下文能夠抽象成一個對象,都包含了一組屬性。
execution-context.png函數

變量對象(variable object)

每一個執行上下文都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象。可是不包含函數表達式和this(由於他不是一個變量)。this

var foo = 10;
function bar(){};
(function baz(){})

console.log(baz); //eror

全局上下文的變量對象
variable-object.pngspa

若是執行上下文是一個函數,則將其活動對象做爲變量對象,除了變量和函數聲明以外,他還存儲形式參數和arguments對象。code

function foo(x, y) {
  var z = 30;
  function bar() {} 
  (function baz() {}); 
}
 
foo(10, 20);

activation-object.png

咱們發現baz不在活動對象裏。對象

做用域鏈(scope chain)

做用域鏈本質上就是根據名稱查找變量(標識符名稱)的一套規則。規則很是簡單,在本身的變量對象裏找不到變量,就上父級的變量對象查找,當抵達最外層的全局上下文中,不管找到仍是沒找到,查找過程都會中止。查找會在找到第一個匹配的變量時中止,被稱爲遮蔽效應ip

var x = 10;
(function foo(){
    var y = 10;
    (function bar(){
        var z = 10;
        console.log(x+y+z)![scope-chain.png][6]
    })
})

以下圖:
scope-chain.png作用域

做用域知識點

詞法做用域

詞法做用域就是定義在詞法階段的做用域。換句話說,詞法做用域是由你在寫代碼時將變量和塊做用域寫在哪裏決定的,不管函數在哪裏被調用,也不管他如何被調用,他的詞法做用域只由函數被聲明位置決定字符串

欺騙詞法

在javascript中的eval函數能夠接受一個字符串爲參數,並將其中的內容視爲在書寫時就存在於程序中這個位置的代碼。

function foo(str, a){
    eval(str); //欺騙
    console.log(a, b);
}
var b = 2;
foo("var b = 3;", 1); //1, 3
函數做用域

函數做用域有兩種方式

//函數聲明
function foo(){
    var a = 3;
    console.log(a);
}
//函數表達式
(function foo(){
    var a = 2;
    console.log(a);
})

二者的區別在於它們的名稱標識符會被綁定到何處,第一段代碼中會被綁定到所在做用域中,第二段代碼被綁定在函數表達式自身的函數中而不是所在做用域中。

塊做用域

在javascript中沒有塊做用域,也就是說在{...}中聲明的變量會泄露到外面做用域

if(true){
    var foo = 'dog'
}
console.log(foo); //dog

function dosomething(i){
    console.log(i);
}

for(var i = 0; i < 10; i++){
    dosomething(i);
}
console.log(i);

而ES6中新增的let能夠將變量綁定到所在的任意做用域(一般是{...}內部),換句話說,let爲其聲明的變量隱式的劫持了所在的塊做用域。

if(true){
    var foo = 'dog'
}
console.log(foo); //dog

function dosomething(i){
    console.log(i);
}

for(let i = 0; i < 10; i++){
    dosomething(i);
}
console.log(i); //error

總結

做用域實際上是有執行上下文中的變量對象和做用域鏈共同構成的。

相關文章
相關標籤/搜索