函數做用域的含義是指,屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用(事實上在嵌套的做用域中也可使用)。bash
function foo(a){
var b = 2;
// 一些代碼
function bar(){
// ...
}
// 更多的代碼
var c = 3;
}
bar(); // 失敗
console.log(a,b,c); // 三個全都失敗
複製代碼
能夠把變量和函數包裹在一個函數的做用域中,而後用這個做用域來「隱藏」它們。函數
爲何「隱藏」變量和函數是一個有用的技術?ui
最小特權原則:在軟件設計中應該最小限度地暴露必要內容,而將其它內容都「隱藏」起來,好比某個模塊或對象的API設計。spa
function doSomething(a){
b = a + doSomethingElse(a * 2){
console.log(b * 3);
}
}
function doSomethingElse(a){
return a - 1;
}
var b;
doSomething(2); // 15
// 最小特權原則改進
function doSomething(a){
function doSomethingElse(a){
return a - 1;
}
var b;
b = a + doSomethingElse(a * 2){
console.log(b * 3);
}
}
doSomething(2); // 15
複製代碼
「隱藏」做用域中的變量和函數所帶來的另外一個好處,是能夠避免同名標識符之間的衝突。設計
function foo(){
function bar(a){
// var i = 3;
i = 3; // 修改for循環所屬做用域中的i
console.log(a+i);
}
for(var i = 0; i < 10; i++){
bar(i * 2); // 糟糕,無限循環了!
}
}
複製代碼
可使用var i = 3修改上面的代碼,獲得正確的結果。在這種狀況下使用做用域來「隱藏」內部聲明是最佳選擇。code
變量衝突的一個典型例子存在於全局做用域中。當程序中加載了多個第三方庫時,若是它們沒有妥善地將內部私有的函數或變量隱藏起來,就會很容易引起衝突。對象
這些庫一般會在全局做用域中聲明一個名字足夠獨特的變量,一般是一個對象,被用做命名空間,全部須要暴露給外界的功能都是該對象的屬性。ip
var MyReallyCoolLibrary = {
awesome: "stuff",
doSomething: function(){
// ...
}
doAnotherThing: function(){
// ...
}
}
複製代碼
另一種避免衝突的方法和現代的模塊機制很接近,就是從衆多模塊管理器中挑選一個來使用。作用域
在任意代碼片斷外部添加包裝函數,能夠將內部的變量和函數定義「隱藏」起來,外部做用域沒法訪問包裝函數內部的任何內容。string
var a = 2;
function foo(){ // 添加這一行
var a = 3;
console.log(a);
} // 以及這一行
foo(); // 以及這一行
console.log(a);
複製代碼
這樣會致使的問題是,首先必須聲明一個具名函數foo(),意味着foo這個名稱會「污染」所在做用域(在這個例子中是全局做用域)。其次,必須顯示地經過函數名(foo())來調用才能運行。
JavaScript提供了可以同時解決函數不須要函數名,而且可以自動運行的方案。
var a = 2;
(function foo(){
var a = 3;
console.log(a); // 3
})()
console.log(a); // 2
複製代碼
函數聲明和函數表達式的區別是function是不是聲明的的第一個詞,若是是就是函數聲明。
(function foo(){..})做爲函數表達式意味着foo只能在..所表明的位置中被訪問。外部做用域則不行。foo變量被隱藏在自身中意味着不會非必要地污染外部做用域。
始終給函數表達式命名是一個最佳實踐。
setTimeout(function timeoutHandler(){ // 快看,我有名字了!
console.log("I waited 1 second");
},1000)
複製代碼
(function(){...})(),第一個()將函數變成表達式,第二個()執行了這個函數。
IIFE的進階用法是把它們當作函數調用並傳遞參數進去。
var a = 2;
(function IIFE(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window)
console.log(a); // 2
複製代碼
IIFE的另外一種用途是倒置代碼的運行順序。
var a = 2;
(function IIFE(){
def(window);
})(function def(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})
複製代碼
函數表達式def定義在片斷的第二部分,而後當作參數(這個參數也叫作def)被傳遞進IIFE函數定義的第一部分中。最後,參數def(也就是傳遞進去的函數)被調用,並將window傳入當作global參數的值。
let 關鍵字能夠將變量綁定到所在的任意做用域中(一般是{..}內部)。let爲其聲明的變量隱式地劫持了所在的塊做用域。
var foo = true;
if(foo){
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
console.log(bar); // ReferenceError
複製代碼
var foo = true;
if(foo){
{ // 顯式的塊
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
}
console.log(bar); // ReferenceError
複製代碼
let 進行的聲明不會在塊做用域中進行提高。
{
console.log(bar); // ReferenceError
let bar = 2;
}
複製代碼
var foo = true;
if(foo){
var a = 2;
const b = 3; // 包含在if中的塊做用域常量
a = 3; // 正常
b = 4; // 錯誤!
}
console.log(a); // 2
console.log(b); // ReferenceError
複製代碼
函數是JavaScript中最多見的做用域單元。本質上,聲明在一個函數內部的變量或函數會在所處的做用域中「隱藏」起來,這是有意爲之的良好的軟件設計原則。
但函數不是惟一的做用域單元。塊做用域指的是變量和函數不只能夠屬於所處的做用域,也能夠屬於某個代碼塊(一般指{...}內部)。
關於標籤
一朋友在我對本身產生嚴重質疑的時候對我說,不要輕易給本身加標籤,固然也不要輕易給別人下定義。聊這個事情的時候,並無醍醐灌頂,卻是最近經歷的一些事,讓我對他說的話,有了一些不同的感悟。好比,你某件事沒有作好,你的上級可能會以爲你沒作好的緣由是不夠認真,經驗不足,能力不夠等,一旦他把其中的某個詞加在你身上,你的第一反應可能他是對的,我就是這樣的,甚至還會自我加註更多相似的。我想說這樣是不對的,由於你可能無心中就多了一個困擾本身的負面標籤。正確的作法應該是,他也許是對的,我還有須要提高,改正的地方,儘可能避免正面的去贊成他的話,而是從積極的角度說能夠改正,提高的地方,不少事,是沒有絕對的對錯和好壞的,一個原則是,儘可能不要給本身加註負面的具體的標籤,由於會給人更深的印象,讓人永遠記住黑歷史。