- 所謂
編譯
的三個階段- 分詞/詞法分析:將字符組成的字符串分解成有意義的代碼塊芳室。
var a = 1;
會被分解成:var,a,=,2,;. - 解析/語法分析:將詞法單元流轉換成一個“抽象語法樹”(Abstract Syntax Tree闷游,AST)
- 代碼生成:將AST轉換為可執(zhí)行代碼
- 分詞/詞法分析:將字符組成的字符串分解成有意義的代碼塊芳室。
- 作用域是根據名稱查找變量的一套規(guī)則创千。
- “詞法作用域”就是定義在詞法階段的作用域虐唠。換句話說久锥,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的辆床,因此當詞法分析器處理代碼時會保持作用域不變说订。
-
eval
:eval()函數接受一個字符串為參數听诸,并將其中的內容視為好像在書寫時就存在于程序中這個位置的代碼坐求。function foo(str, a){ eval(str); console.log(a,b) } var b = 2; foo("var b = 3;", 1) //也就是永遠都找不到外部的b,好像就是在動態(tài)的寫代碼一樣
-
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只能在...所代表的位置中被訪問执俩,外部作用域則不行,優(yōu)點是不污染外部作用域 - 進階----引出匿名函數(作為回調函數)癌刽,優(yōu)點顯而易見役首,不污染外部作用域
- 立即執(zhí)行函數表達式(Immediately Invoked Function Expression,IIFE),函數被包在一對()內部妒穴,因此成了一個表達式宋税,通過在末尾加上另一對()可以立即執(zhí)行這個函數。當然函數名并不是必須的
(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循環(huán)內部使用的變量i污染到整個函數作用域中呢蛤迎?
-
with
,是塊作用域的一個例子??!:选L骜伞!使用with從對象中創(chuàng)建出的作用域僅在with聲明中而非外部作用域中有效窘问。 -
try/catch
,try/catch的catch分句會創(chuàng)建一個塊作用域辆童,其中聲明的變量僅在catch內部有效
tyr{ undefined();//執(zhí)行一個非法操作來強制制造一個異常 } cathc(err){ console.log(err)//正常執(zhí)行 } console.log(err);//ReferenceError:err not found
-
let
,let關鍵字可以將變量綁定到所在的任意作用域中,也就是let為其聲明的變量隱式
的創(chuàng)建了一個塊作用域惠赫,這樣在if中用let聲明的變量就不會污染到全局作用域,那么如何顯式
的創(chuàng)建一個塊把鉴?加大括號
就行了。
if(foo){ { let bar = foo * 2; bar = something(bar); console.log(bar) } }// 這樣就顯示的創(chuàng)建了一個塊儿咱,只要聲明是有效的庭砍,在聲明中的任意位置都可以使用{...}來為let創(chuàng)建一個用于綁定的塊
let進行的聲明不回在塊作用域中進行提升,也就是let聲明的變量混埠,在let這一行運行之前是不存在的
-
const
,同樣可以用來創(chuàng)建塊作用域變量怠缸,但其值是固定的(常量)
-
-
- 提升
a = 2; var a; console.log(a);//輸出2 *=================* console.log(a); //輸出undefined var a = 2;
- 只有聲明會被提升,而賦值或其他運行邏輯會留在原地岔冀。函數聲明會被提升凯旭,但是
函數表達式不會被提升概耻!
foo(); var foo = function(){ ... }//錯誤使套,TypeError
- 函數優(yōu)先,函數聲明和變量聲明都會被提升鞠柄,但是函數首先被提升侦高,然后才是變量,后聲明的會覆蓋掉先聲明的厌杜,比如一個函數叫foo奉呛,一個變量叫foo,那么肯定是變量會優(yōu)先夯尽。
- 只有聲明會被提升,而賦值或其他運行邏輯會留在原地岔冀。函數聲明會被提升凯旭,但是
- 作用域閉包
- 閉包的一次理解
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2--閉包扒谱场!3孜铡咆槽!
函數bar()可以訪問foo()的內部作用域,然后將bar()函數本身當作一個值類型進行傳遞圈纺,foo()執(zhí)行后秦忿,其返回值賦值給變量baz并調用baz(),實際上只是通過不同的標識符調用了內部函數bar()麦射。神奇的地方在于bar()函數在自己定義的詞法作用域以外的地方執(zhí)行了。
灯谣,foo()執(zhí)行之后潜秋,通常會把foo()的整個內部作用域銷毀,但是閉包可以阻止被銷毀胎许,也就是foo()的內部作用域還在峻呛,并且由bar()本身在引用,這個引用就是閉包辜窑。
- 本質上杀饵,無論何時何地,如果將函數當做第一級的值類型并到處傳遞(就是將函數作為參數傳入另一個函數中谬擦,比如回調函數切距,定時器,事件監(jiān)聽器)惨远,你就會看到閉包在這些函數中的應用谜悟。
- 一段神奇代碼----對閉包二次理解
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扬蕊,這又是為什么呢搀别??尾抑?