作用域指變量所作用的范圍,在 Javascript 中有兩種作用域:
變量提升變量提升(Hoisting)被認(rèn)為是, Javascript 中執(zhí)行上下文 (特別是創(chuàng)建和執(zhí)行階段)工作方式的一種認(rèn)識(shí)。具體表現(xiàn)就是所有通過(guò) var 聲明的變量會(huì)提升到當(dāng)前作用域的最前面。 function foo() { console.log(temp); }function bar() { console.log(temp); var temp;}foo(); // ReferenceError: temp is not definedbar(); // undefined 可以看到用 var 聲明了的并不會(huì)報(bào)錯(cuò)。因?yàn)槠鋵?shí)函數(shù) bar 等同于 function bar() { var temp; console.log(temp);} 大多數(shù)類 C 語(yǔ)言語(yǔ)法的語(yǔ)言都擁有塊級(jí)作用域。在一個(gè)代碼塊(括在一對(duì)花括號(hào)中的一組語(yǔ)句)中定義的所有變量在代碼塊的外部是不可見(jiàn)的。定義在代碼塊中的變量在代碼塊被執(zhí)行結(jié)束后會(huì)變釋放掉。這是件好事。 糟糕的是,盡管 Javascript 的代碼貌似支持塊級(jí)作用域,但實(shí)際上 Javascript 并不支持(就是因?yàn)橛凶兞刻嵘?。這個(gè)混淆之處可能成為錯(cuò)誤之源。 所以在 ES6 中規(guī)定了 let 和 const 來(lái)支持塊級(jí)作用域。但是,是不是真的提升就不存在了呢,可以看下面暫時(shí)性死區(qū)這部分。 letlet 可以理解為『更完美的 var』,使用方法很簡(jiǎn)單; let foo = 3; 使用方法基本和 var 相同,而且聲明的變量只在其塊和子塊中可用,這點(diǎn)也與 var 相同。 二者之間最主要的區(qū)別在于 var 聲明的變量的作用域是整個(gè)封閉函數(shù)。 function foo() { if(true) { var temp = 5; console.log(temp); } console.log(temp);}function bar() { if(true) { let temp = 5; console.log(temp); } console.log(temp);}foo(); // 5 和 5bar(); // 5 和 'ReferenceError: temp is not defined let 聲明的變量的作用域只是外層塊,而不是整個(gè)外層函數(shù)。 我們可以利用這個(gè)特性來(lái)替代立即執(zhí)行函數(shù)(IIFE)。 // IIFE(function(){ var temp = xxx; /* other code */}())// 塊級(jí){ let temp = xxx; /* other code */} constconst 的用法跟 let 差不多,但是 const 一定要初始化, 不初始化是會(huì)報(bào)錯(cuò)的。 const temp = 4;// 沒(méi)有初始化報(bào)錯(cuò)const t; // SyntaxError: Missing initializer in const declaration const 是塊級(jí)作用域,const 跟 let 的語(yǔ)義相似,就是用來(lái)聲明常量的,一旦聲明了就不能更改。值得注意的是 const 聲明的變量記錄的是指針,不可更改的是指針,如果 const 所聲明的是對(duì)象,對(duì)象的內(nèi)容還是可以修改的。 // 重新賦值聲明導(dǎo)致報(bào)錯(cuò)const PI = 3.14;PI = 3.1415926; // TypeError: Assignment to constant variable.// 給對(duì)象增加屬性不會(huì)導(dǎo)致 obj 的指針變化,所以不會(huì)報(bào)錯(cuò)const obj = { foo: 2 };obj.bar = 3;console.log(obj); // {foo: 2, bar: 3} 暫時(shí)性死區(qū)使用 let 或 const 聲明的變量,在聲明沒(méi)有到達(dá)之前,訪問(wèn)該變量都會(huì)導(dǎo)致報(bào)錯(cuò),就連一直以為安全的 typeof 也不再安全。 // TDZ1function foo() { // TDZ 開(kāi)始 console.log(typeof temp); let temp = 5; // TDZ 結(jié)束}foo(); // ReferenceError: temp is not defined 報(bào)的錯(cuò)是 ReferenceError,如果使用 var 聲明的話,temp 輸出應(yīng)該是 undefined,從 let 聲明的變量的塊的第一行,到聲明變量之間的這個(gè)區(qū)域被稱作暫時(shí)性死區(qū)(TDZ)。凡是在這個(gè)區(qū)域使用這些變量都會(huì)報(bào)錯(cuò)。 // TDZ2function bar() { console.log(typeof temp);}bar(); // undefined 看到上面兩個(gè)例子仔細(xì)思考有沒(méi)有覺(jué)得想到點(diǎn)什么? 在函數(shù)里沒(méi)有用 let 聲明 temp 的時(shí)候,temp 是 undefined,講道理在 let 聲明前也應(yīng)該是 temp,然而 foo 函數(shù)卻報(bào)了錯(cuò),證明了就算是在未到達(dá) let 聲明的地方,但是在用 let 之前已經(jīng)起到了作用。這是不是說(shuō)明其實(shí) let 也有提升(這個(gè)提升并不是 var 的那種提升,只是有影響),只是在 TDZ 使用的時(shí)候報(bào)錯(cuò)了,而不是 undefined。 事實(shí)上,當(dāng) JS 引擎檢視下面的代碼塊有變量聲明時(shí),對(duì)于 var 聲明的變量,會(huì)將聲明提升到函數(shù)或全局作用域的頂部,而對(duì) let 或 const 的時(shí)候會(huì)將聲明放在暫時(shí)性死區(qū)內(nèi)。任何在暫時(shí)性死區(qū)內(nèi)訪問(wèn)變量的企圖都會(huì)導(dǎo)致“運(yùn)行時(shí)”錯(cuò)誤(runtime error)。只有執(zhí)行到變量的聲明語(yǔ)句時(shí),該變量才會(huì)從暫時(shí)性死區(qū)內(nèi)被移除并可以安全使用。 禁止重復(fù)聲明在同一個(gè)塊內(nèi),let 和 const 不能聲明相同的標(biāo)識(shí)符。禁止的情況包括:
// let 和 letlet foo = 1;let foo = 2;// let 和 constlet foo = 1;const foo = 1;// var 與 letvar foo = 1;let foo = 1;// 函數(shù)參數(shù)與 letfunction bar(foo) { let foo = 1;} 以上情況都是會(huì)報(bào) SyntaxError。但是在嵌套的作用域內(nèi)使用 let 聲明同一變量是被允許的。 var foo = 1;{ // 不會(huì)報(bào)錯(cuò) let = 2; // other code} 同時(shí)因?yàn)槭?let 和 const 是塊級(jí)作用域,聲明的變量在當(dāng)前塊使用完之后就會(huì)被釋放,所以就算使用相同的標(biāo)識(shí)符也不會(huì)覆蓋外部作用域的變量, 而 var 是會(huì)覆蓋外部作用域的變量的。 function foo() { var bar = 1; { let bar = 2; } console.log(bar);}function zoo() { var bar = 1; { var bar = 2; } console.log(bar);}foo(); // 1zoo(); // 2 最佳實(shí)踐在 ES6 的發(fā)展階段,被廣泛認(rèn)可的變量聲明方式是:默認(rèn)情況下應(yīng)當(dāng)使用 let 而不是 var。對(duì)于多數(shù) JS 開(kāi)發(fā)者來(lái)說(shuō), let 的行為方式正是 var 本應(yīng)有的方式,因此直接用 let替代 var 更符合邏輯。在這種情況下,你應(yīng)當(dāng)對(duì)需要受到保護(hù)的變量使用 const 。 在默認(rèn)情況下使用 const ,而只在你知道變量值需要被更改的情況下才使用 let 。這在代碼中能確保基本層次的不可變性,有助于防止某些類型的錯(cuò)誤。 思考題兩個(gè)思考題,可以在評(píng)論里面回答,我后面會(huì)把我的想法放在評(píng)論中。 // 思考題 1switch (x) { case 0: let foo; break; case 1: let foo; // TypeError for redeclaration. break;}// 思考題 2function bar(){ var foo = 1; if (true) { let foo = (foo + 2); }}bar();
|
|