一周一章前端書·第3周:《你不知道的JavaScript(上)》S01E03

第3章:函數(shù)作用域和塊級(jí)作用域

3.1 函數(shù)中的作用域

  • JavaScript變量的查找規(guī)則是由內(nèi)到外的,閱讀如下代碼:
function foo(a){
    var b = 2;
    function bar(){
        //...
    }
    var c = 3;
}

bar();  //失敗
console.log(a,b,c); //失敗
  • foo()函數(shù)作用域聲明了變量ab利耍、c以及函數(shù)bar笔刹,如果在外部使用這些函數(shù)內(nèi)的變量或函數(shù)都會(huì)失敗。
  • 函數(shù)作用域的含義就是說稳懒,函數(shù)內(nèi)的變量只允許在函數(shù)范圍內(nèi)使用(也包括在內(nèi)部嵌套函數(shù))。

3.2 隱藏內(nèi)部實(shí)現(xiàn)

  • 既然函數(shù)中變量的活動(dòng)范圍只限制在函數(shù)內(nèi)慢味,反過來其實(shí)也可以這么理解:
  • 我們挑選一個(gè)代碼片段场梆,用函數(shù)作用域?qū)⑵浒b起來了,而 函數(shù)內(nèi)的具體細(xì)節(jié)(變量)對(duì)外不可見贮缕,被“隱藏”起來了辙谜。
  • 這種做法就是 軟件設(shè)計(jì)中的最小授權(quán)最小暴露原則
  • 如果所有變量和函數(shù)都聲明到全局作用域中感昼,可能會(huì)發(fā)生無法預(yù)知的情況:
var b;
function doSomething(a){
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}

function doSomethingElse(a){
    return a - 1;
}

doSomething(2); //15

通過函數(shù)作用域修改后装哆,是不是更安心了:

function doSomething(a){
    var b;
    function doSomethingElse(a){
        return a - 1;
    }
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}
doSomething(2); //15
  • 另外,通過作用域隱藏變量還有一個(gè)好處:可以避免因?yàn)橥斐傻淖兞繘_突定嗓。
function foo(){
    function bar(a){
        i = 3;
        console.log(a + i);
    }
    
    for(var i=0;i<10;i++){
        bar(i * 2);
    }
};

foo();  //因?yàn)檎{(diào)用的i是通一個(gè)蜕琴,造成無限循環(huán)
  • 針對(duì)同名變量沖突,大概有兩種處理方式:

(1) 全局命名空間

  • 將本來暴露在全局作用域的諸多變量宵溅,統(tǒng)一放到全局的某個(gè)對(duì)象中凌简,這個(gè)對(duì)象看做一個(gè)命名空間。
  • jQuery采用的就是此類做法恃逻,將所有和jQuery關(guān)聯(lián)的第三方插件雏搂,都放到j(luò)Query對(duì)象下。

(2) 模塊管理

  • 如同seaJS寇损、requireJS等現(xiàn)代化的模塊機(jī)制一樣
  • 首先將JS第三方庫統(tǒng)一注冊(cè)到模塊管理器中凸郑,當(dāng)調(diào)用的方法需要某個(gè)庫時(shí),從模塊管理器中將該庫挑選出來矛市,顯式的注入到函數(shù)作用域里芙沥。
  • 這樣不僅避免了全局作用域的污染,更實(shí)現(xiàn)了按需加載。

3.3 函數(shù)作用域

  • 上文說到而昨,通過函數(shù)作用域來隱藏變量能避免一系列的問題救氯,但這并不是最理想的:
    • 為了隱藏幾個(gè)變量,我還得聲明一個(gè)具名函數(shù)歌憨,那這個(gè)函數(shù)本身着憨,不也在污染全局作用域嗎?
    • 其次务嫡,聲明了函數(shù)還得去調(diào)用才能運(yùn)行起來享扔。
  • 是否有可以 不用指定函數(shù)名,并且還能自動(dòng)運(yùn)行的函數(shù) 呢植袍?答案是肯定的:
var a = 2;
(function foo(){
    var a = 3;
    console.log(a); //3
})();
console.log(a); //2 函數(shù)作用域?qū)?nèi)部的a做了隱藏,不影響全局的作用域
  • 當(dāng)以()中括號(hào)包含函數(shù)時(shí)籽懦,則是函數(shù)表達(dá)式于个,而不是標(biāo)準(zhǔn)的函數(shù)聲明。
  • 不要對(duì)函數(shù)表達(dá)式感到陌生暮顺,其實(shí)早在定時(shí)函數(shù)setTimeout()我們就有用到過:
setTimeout(function(){
    console.log('I waited 1 second!');
},1000);
  • 定時(shí)函數(shù)中用到的叫匿名函數(shù)表達(dá)式厅篓,因?yàn)闆]有指定函數(shù)名。
  • 注意捶码,函數(shù)表達(dá)式是可以匿名的羽氮,但函數(shù)聲明則不允許匿名。
  • 使用函數(shù)表達(dá)式需要注意的幾點(diǎn):
    • 匿名函數(shù)不便于調(diào)試惫恼;
    • 當(dāng)函數(shù)需要引用自身的時(shí)候比較困難档押,只能引用已經(jīng)過期的arguments.callee
    • 由于匿名函數(shù)缺乏函數(shù)名祈纯,代碼的可讀性較差令宿;
  • ()包含一個(gè)函數(shù)可以構(gòu)造一個(gè)函數(shù)表達(dá)式,而 在末尾緊跟著一個(gè)()可以立即執(zhí)行這個(gè)函數(shù) 腕窥。
  • JS社區(qū)將它命名為 IIFE(Immediately Invoked Function Expresion)粒没,代表立即執(zhí)行的函數(shù)表達(dá)式
  • IIFE還有另外一種寫法:
(function(){
    console.log('test');
}())
  • IIFE可以傳遞參數(shù)
var a = 2;
(function(global){
    var a = 3;
    console.log(a); //3
    console.log(global.a);  //2
})(window);
console.log(a); //2
  • IIFE 還有一種 可以倒置代碼的運(yùn)行順序 的玩法簇爆,這種模式在UMD(Universal Module Definition)項(xiàng)目中運(yùn)用廣泛:
var a = 2;
(function iife(def){
    def(window);
})(function(global){
    var a = 3;
    console.log(a); //3
    console.log(global.a);  //2
});

3.3 塊作用域

  • 雖然函數(shù)作用域是最常見的作用域癞松,但JavaScript也有其他的作用域單元,甚至在很多場景下入蛆,甚至用這些非函數(shù)的作用域單元實(shí)現(xiàn)功能响蓉,代碼會(huì)更簡潔、更優(yōu)雅安寺。
//雖然i聲明于for循環(huán)的花括號(hào)范圍內(nèi)
//但注意i是用var來聲明的厕妖,實(shí)際上它是全局作用域下的變量
for (var i=0;i<10;i++){
    consooe.log(i);
}
  • for循環(huán)中的i經(jīng)常會(huì)由于忽略,會(huì)被綁定到全局作用域,而JavaScript的塊級(jí)作用域就可以將變量限制在花括號(hào)的范圍內(nèi)言秸。

with

  • 前文提到的with就可以構(gòu)造塊級(jí)作用域软能,但不提倡使用。

try/catch

  • catch分句中举畸,也會(huì)創(chuàng)建一個(gè)塊級(jí)作用域查排。在其中聲明的變量僅在catch內(nèi)部有效。
try{
    makeError();
}catch(err){
    console.log(err);
}
console.log(err);   //ReferenceError: err not found

let

  • ES6引入了 let關(guān)鍵字抄沮,可以將變量綁定到所在代碼的作用域中 (通常是花括號(hào){}內(nèi))跋核。換句話說,let為其聲明的變量隱式的劫持了所在的塊作用域叛买。
var foo = true;
if(foo){
    {
        let bar = foo * 2;
        bar = something(bar);
        console.log(bar);
    }
}
console.log(bar);   //ReferenceError
  • 注意:但 let關(guān)鍵字不會(huì)進(jìn)行變量聲明的提升 砂代,請(qǐng)確保代碼在運(yùn)行時(shí)已進(jìn)行聲明。
{
    console.log(bar);   //ReferenceError!
    let bar = 2;
}

const

  • ES6中的 const同樣也可以創(chuàng)建塊級(jí)作用域變量率挣,但變量值是固定的(常量)刻伊。
var foo = true;
if(foo){
    var a = 2;
    const b = 3;    //在if塊級(jí)作用域下的常量
    
    a = 3;  //賦值正常
    b = 4;  //賦值失敗椒功!常量不能改變
}

console.log(a); //3
console.log(b); //ReferenceError!

3.5 小結(jié)

  • 函數(shù)是JavaScript中最常見的作用域單位捶箱,聲明在函數(shù)內(nèi)的變量會(huì)被函數(shù)作用域“隱藏”起來,這是軟件設(shè)計(jì)的最小暴露原則动漾。
  • 函數(shù)不是唯一的作用域單位丁屎,塊級(jí)作用域是指變量屬于某個(gè)代碼段(通常擁有花括號(hào){})下。
  • try/catchcatch分句擁有塊作用域旱眯。
  • ES6的letconst關(guān)鍵字能在任意代碼段中創(chuàng)建塊作用域變量晨川。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市键思,隨后出現(xiàn)的幾起案子础爬,更是在濱河造成了極大的恐慌,老刑警劉巖吼鳞,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件看蚜,死亡現(xiàn)場離奇詭異,居然都是意外死亡赔桌,警方通過查閱死者的電腦和手機(jī)供炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾党,“玉大人音诫,你說我怎么就攤上這事⊙┪唬” “怎么了竭钝?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我香罐,道長卧波,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任庇茫,我火速辦了婚禮港粱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旦签。我一直安慰自己查坪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布宁炫。 她就那樣靜靜地躺著偿曙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羔巢。 梳的紋絲不亂的頭發(fā)上遥昧,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音朵纷,去河邊找鬼。 笑死永脓,一個(gè)胖子當(dāng)著我的面吹牛袍辞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播常摧,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼搅吁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了落午?” 一聲冷哼從身側(cè)響起谎懦,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溃斋,沒想到半個(gè)月后界拦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梗劫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年享甸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳侨。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛉威,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出走哺,到底是詐尸還是另有隱情蚯嫌,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站择示,受9級(jí)特大地震影響束凑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜对妄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一湘今、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剪菱,春花似錦摩瞎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至构灸,卻和暖如春上渴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喜颁。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工稠氮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人半开。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓隔披,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寂拆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奢米,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容