- 所謂
編譯
的三個階段- 分詞/詞法分析:將字符組成的字符串分解成有意義的代碼塊。
var a = 1;
會被分解成:var,a,=,2,;. - 解析/語法分析:將詞法單元流轉換成一個“抽象語法樹”(Abstract Syntax Tree,AST)
- 代碼生成:將AST轉換為可執行代碼
- 分詞/詞法分析:將字符組成的字符串分解成有意義的代碼塊。
- 作用域是根據名稱查找變量的一套規則。
- “詞法作用域”就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的,因此當詞法分析器處理代碼時會保持作用域不變。
-
eval
:eval()函數接受一個字符串為參數,并將其中的內容視為好像在書寫時就存在于程序中這個位置的代碼。function foo(str, a){ eval(str); console.log(a,b) } var b = 2; foo("var b = 3;", 1) //也就是永遠都找不到外部的b,好像就是在動態的寫代碼一樣
-
with
:with通常被當作重復引用同一個對象中的多個屬性的更快捷方式
with可以將一個沒有或有多個屬性的對象處理為一個完全隔離的詞法作用域,因此這個對象的屬性也會被處理為定義在這個作用域中的詞法標識符。但是這個塊內部正常的var聲明并不會被限制在這個塊的作用域中,而是被添加到with所在的函數作用域中,所以就有一個新的問題,變量會被泄露。eg:var obj = { a:1, b:2 } obj.a = 2; obj.b = 3; 相當于: with(obj){ a:2; b:33; } //可以方便的訪問對象屬性
- 函數作用域
-
函數作用域
,函數作用域的含義是指,屬于這個函數的全部變量都可以在整個函數的范圍內使用及復用(及嵌套的作用域中也可以使用)。 -
函數作用域
的利用:在任意代碼片段外部添加包裝函數,可以將內部的變量和函數定義“隱藏”起來,外部作用域無法訪問包裝函數內部的任何內容。 -
(function foo(){...})
,作為函數表達式意味著foo只能在...所代表的位置中被訪問,外部作用域則不行,優點是不污染外部作用域 - 進階----引出匿名函數(作為回調函數),優點顯而易見,不污染外部作用域
- 立即執行函數表達式(Immediately Invoked Function Expression,IIFE),函數被包在一對()內部,因此成了一個表達式,通過在末尾加上另一對()可以立即執行這個函數。當然函數名并不是必須的
(function foo(){ ... })()
- IIFE進階用法:把它們當做函數調用并且傳遞參數進去
將window對象的引用傳遞進去,但將形參命名為global,因此在代碼風格上對全局對象的引用變的比引用一個沒有“全局”字樣的變量更加清晰。var a = 2; (function IIFE(global){ var a = 3; console.log(a); console.log(global.a); })(window); console.log(a);
- 塊作用域,javascript是沒有塊作用域的,只要用var來聲明變量,最終都會屬于外部作用域,比如在if語句塊中聲明的變量,(函數當然是另外一回事)
for(var i =0; i<10; i++){ ... }//i已經被泄漏到外部了
-
思考:為什么要把一個只在for循環內部使用的變量i污染到整個函數作用域中呢?
-
with
,是塊作用域的一個例子??!?。?!使用with從對象中創建出的作用域僅在with聲明中而非外部作用域中有效。 -
try/catch
,try/catch的catch分句會創建一個塊作用域,其中聲明的變量僅在catch內部有效
tyr{ undefined();//執行一個非法操作來強制制造一個異常 } cathc(err){ console.log(err)//正常執行 } console.log(err);//ReferenceError:err not found
-
let
,let關鍵字可以將變量綁定到所在的任意作用域中,也就是let為其聲明的變量隱式
的創建了一個塊作用域,這樣在if中用let聲明的變量就不會污染到全局作用域,那么如何顯式
的創建一個塊?加大括號
就行了。
if(foo){ { let bar = foo * 2; bar = something(bar); console.log(bar) } }// 這樣就顯示的創建了一個塊,只要聲明是有效的,在聲明中的任意位置都可以使用{...}來為let創建一個用于綁定的塊
let進行的聲明不回在塊作用域中進行提升,也就是let聲明的變量,在let這一行運行之前是不存在的
-
const
,同樣可以用來創建塊作用域變量,但其值是固定的(常量)
-
-
- 提升
a = 2; var a; console.log(a);//輸出2 *=================* console.log(a); //輸出undefined var a = 2;
- 只有聲明會被提升,而賦值或其他運行邏輯會留在原地。函數聲明會被提升,但是
函數表達式不會被提升!
foo(); var foo = function(){ ... }//錯誤,TypeError
- 函數優先,函數聲明和變量聲明都會被提升,但是函數首先被提升,然后才是變量,后聲明的會覆蓋掉先聲明的,比如一個函數叫foo,一個變量叫foo,那么肯定是變量會優先。
- 只有聲明會被提升,而賦值或其他運行邏輯會留在原地。函數聲明會被提升,但是
- 作用域閉包
- 閉包的一次理解
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2--閉包啊?。。?
函數bar()可以訪問foo()的內部作用域,然后將bar()函數本身當作一個值類型進行傳遞,foo()執行后,其返回值賦值給變量baz并調用baz(),實際上只是通過不同的標識符調用了內部函數bar()。神奇的地方在于bar()函數在自己定義的詞法作用域以外的地方執行了。
,foo()執行之后,通常會把foo()的整個內部作用域銷毀,但是閉包可以阻止被銷毀,也就是foo()的內部作用域還在,并且由bar()本身在引用,這個引用就是閉包。
- 本質上,無論何時何地,如果將函數當做第一級的值類型并到處傳遞(就是將函數作為參數傳入另一個函數中,比如回調函數,定時器,事件監聽器),你就會看到閉包在這些函數中的應用。
- 一段神奇代碼----對閉包二次理解
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
輸出的結果是五個六,頻率為1秒,這是為什么呢???
for(var i=1; i<=5; i++){
(function(j){
setTimeout(function timer() {
console.log(j);
},j*1000);
})(i);
}
輸出的結果是每秒輸出一個數字,分別是1,2,3,4,5,這又是為什么呢???