幾乎全部語言的最基礎模型之一就是在變量中存儲值,而且在稍後取出或修改這些值的能力。 做用域就是定義如何在某些位置存儲變量,以及如何在稍後找到這些變量。javascript
編譯的三個步驟java
1.分詞/詞法分析:將一連串的字符打斷成有意義的片斷,稱爲 token,例子:var a = 2;=> var, a, =, 2, ;
。閉包
2.解析:將一個 token 的流轉換成一個嵌套元素的樹,它綜合地表示了程序的語法結構。這棵樹稱爲抽象語法樹(AST)函數
3.代碼生成:這個處理將抽象語法樹轉換爲可執行的代碼。性能
JavaScript 的編譯和其餘語言不一樣,不是提早發生在一個構建的步驟中。對於 JavaScript 來講,在許多狀況下,編譯發生在代碼執行前的僅僅幾微秒以內。爲了確保最快的性能,JS 引擎使用了 JIT等等優化
引擎:負責從始至終的編譯和執行咱們的 JavaScript 程序。 編譯器:引擎的朋友之一;處理全部解析和代碼生成的活兒。 做用域:引擎的另外一個朋友;收集並維護一張全部被聲明的標識符的列表,並對當前執行中的代碼如何訪問這些變量強制實施一組嚴格的規則。ui
對於 var a = 2;
這個語句,編譯器將會這樣處理:spa
1.遇到var a
,編譯器讓做用域去查看對於這個特定的做用域集合,變量 a 是否已經存在了。若是是,編譯器就忽略這個聲明並繼續前進。不然,編譯器就讓做用域去爲這個做用域集合聲明一個稱爲 a 的新變量。code
2.而後編譯器爲引擎生成稍後要執行的代碼,來處理賦值 a = 2
。引擎 運行的代碼首先讓做用域 去查看在當前的做用域集合中是否有一個稱爲 a 的變量能夠訪問。若是有,引擎就使用這個變量。若是沒有,引擎會喊出一個錯誤。token
嵌套的做用域就像一個代碼塊兒或函數被嵌套在另外一個代碼塊兒或函數中同樣,做用域被嵌套在其餘的做用域中。
遍歷嵌套做用域的簡單規則:引擎從當前執行的做用域開始,在那裏查找變量,若是沒有找到,就向上走一級繼續查找,如此類推。若是到了最外層的全局做用域,那麼查找就會中止,不管它是否找到了變量。
做用域的工做方式有兩種佔統治地位的模型。其中第一種是最多見的詞法做用域,另外一種是動態做用域。
JavaScript 所採用的做用域模型是詞法做用域。
詞法做用域是在詞法分析時被定義的做用域。
欺騙詞法做用域:eval 函數和 with 關鍵字的使用。
欺騙詞法做用域的使用會致使更低下的性能。由於 JS 引擎的一些優化原理都歸結在實質上在進行詞法分析時能夠靜態地分析代碼,並提早決定全部的變量和函數聲明在什麼位置。若是發現一個 eval 或是 with,它實質上就不得不假定本身知道的全部標識符的位置多是無效的。
函數中的做用域也就是聲明的每個函數都爲本身建立了一個做用域。 當即調用函數表達式能夠生成一個本身的做用域。
ES6引入了 let 和 const,它們都會建立一個塊兒做用域。 let 作出的聲明不會在他們所出現的塊兒的做用域中提高。
在代碼的任何部分被執行以前,全部的聲明,變量和函數,都會首先被處理。如下是兩個例子
a = 2;
var a;
console.log( a ); // 2
複製代碼
console.log( a ); // undefined
var a = 2;
複製代碼
函數聲明會被提高,可是函數表達式不會。
foo();
function foo() {
console.log( a ); // undefined
var a = 2;
}
複製代碼
foo(); // 不是 ReferenceError, 而是 TypeError! 由於變量 foo 被提高了,可是值爲 undefined
var foo = function bar() {
// ...
};
複製代碼
函數聲明和變量聲明都會被提高。可是函數會首先被提高,而後纔是變量。
foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
複製代碼
這個代碼被引擎解釋執行爲
function foo() {
console.log( 1 );
}
foo(); // 1
foo = function() {
console.log( 2 );
};
複製代碼
這裏的一個注意點就是 var foo 是一個重複的聲明,會被忽略。
閉包就是函數可以記住並訪問它的詞法做用域,即便當這個函數在它的詞法做用域以外執行時。 如下例子是一個典型的閉包案例
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 -- 哇噢,看到閉包了,夥計。
複製代碼
咱們生活中常用到的閉包
function wait(message) {
setTimeout( function timer(){
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
複製代碼
還有模塊就是利用了閉包的力量,咱們看下面的代碼
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
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
複製代碼