第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ù)作用域聲明了變量a
、b
利耍、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/catch
的catch
分句擁有塊作用域旱眯。 - ES6的
let
和const
關(guān)鍵字能在任意代碼段中創(chuàng)建塊作用域變量晨川。