執行上下文是javascript最重要的一個概念,執行上下文定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。而在javascript中有三種執行上下文: 全局執行上下文, 函數執行上下文, eval執行上下文。代碼在其執行上下文中執行。在javascript中只有一個全局執行環境(根據宿主環境的不一樣,全局執行環境的對象也不同)。能夠有許多函數和eval執行環境的實例,每次調用一個函數或eval,都會進入對應執行環境執行代碼。注意一個函數可能會產生無限上下文集合,由於每次函數調用自身都會產生一個新的執行上下文javascript
執行上下文能夠激活另外一個執行上下文,例如函數調用另外一個函數(或者全局執行上下文調用全局函數)等等,邏輯上就成了一個堆棧。這被稱爲執行上下文堆棧。
當執行流進入一個函數時,函數的上下文就會被推入一個棧中,若是在當前函數中調用另外一個函數,當前函數就會暫停執行,並將執行流傳遞給被調用函數(被調用函數同事多是其餘函數的調用者),被調用者被推入堆棧。當被調用者的上下文結束後,將執行流交還給調用者,調用者的繼續運行代碼,直到結束後,棧將上下文彈出。java
每一個執行上下文能夠抽象成一個對象,都包含了一組屬性。函數
每一個執行上下文都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象。可是不包含函數表達式和this(由於他不是一個變量)。this
var foo = 10; function bar(){}; (function baz(){}) console.log(baz); //eror
全局上下文的變量對象spa
若是執行上下文是一個函數,則將其活動對象做爲變量對象,除了變量和函數聲明以外,他還存儲形式參數和arguments對象。code
function foo(x, y) { var z = 30; function bar() {} (function baz() {}); } foo(10, 20);
咱們發現baz
不在活動對象裏。對象
做用域鏈本質上就是根據名稱查找變量(標識符名稱)的一套規則。規則很是簡單,在本身的變量對象裏找不到變量,就上父級的變量對象查找,當抵達最外層的全局上下文中,不管找到仍是沒找到,查找過程都會中止。查找會在找到第一個匹配的變量時中止,被稱爲遮蔽效應ip
var x = 10; (function foo(){ var y = 10; (function bar(){ var z = 10; console.log(x+y+z)![scope-chain.png][6] }) })
以下圖:作用域
詞法做用域就是定義在詞法階段的做用域。換句話說,詞法做用域是由你在寫代碼時將變量和塊做用域寫在哪裏決定的,不管函數在哪裏被調用,也不管他如何被調用,他的詞法做用域只由函數被聲明位置決定。字符串
在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
做用域實際上是有執行上下文中的變量對象和做用域鏈共同構成的。