本文共 1700 字,讀完只需 7 分鐘javascript
變量
,編程語言中咱們用來模擬現實概念的工具,比方說,變量能夠表示對象,數組,數字,字符。既然是工具,那麼就用工具的適用範圍,這個工具在這個適用範圍中才有效,在編程語言中,咱們稱這個適用範圍叫做用域(scope)
。java
本文會總結 JS 中做用域的相關概念。編程
做用域
, 英文意思是 scope
, 我本身的話來理解就是:windows
變量訪問規則的有效範圍數組
先看一段代碼:瀏覽器
foo = "bar";
console.log(window.foo); // bar
複製代碼
在瀏覽器環境中聲明變量,該變量會默認成爲全局 windows 對象的屬性。閉包
再看下面這段代碼:app
function foo() {
name = "bar"
}
foo();
console.log(window.name) // bar
複製代碼
在函數中,若是不加 bar
聲明一個變量,那麼這個變量會默認被聲明爲全局變量,若是是嚴格模式則會報錯。編程語言
全局變量能夠在任何地方訪問到,可是有很大的問題存在。函數
全局變量會形成命名污染,若是在多處對同一個全局變量進行操做,那麼就會覆蓋全局變量的定義。同時全局變量數量過多,很是不方便管理。
這也是爲何像jQuery 和 underscore 這樣的類庫,要在全局創建 $ 和 _ 變量,其他私有方法屬性掛載到該全局變量下。
JS 是函數做用域,在函數中定義一個局部變量,那麼該變量只能夠在該函數做用域中被訪問。
function doSomething() {
var thing = "吃早餐";
}
console.log(thing); // Uncaught ReferenceError: thing is not defined
複製代碼
嵌套函數做用域:
function outter() {
var thing = "吃早餐";
function inner() {
console.log(thing);
}
inner();
}
outter(); // 吃早餐
複製代碼
在外層函數中,嵌套一個內層函數,那麼這個內層函數能夠向上訪問到外部做用域的變量。
那麼,既然內層函數能夠訪問到外層函數的變量,那麼把內層函數返回後呢?
function outter() {
var thing = "吃晚餐";
function inner() {
console.log(thing);
}
return inner;
}
var foo = outter();
foo(); // 吃晚餐
前面咱們提到了,函數執行完後,函數做用域的變量會被垃圾回收,以上代碼能夠看出當咱們返回了一個訪問了外部函數變量的內部函數,最後外部函數的變量得以保存。
這種當變量存在的函數已經執行結束,但仍在能夠訪問的方式就是`閉包`。
閉包的具體實踐,後續文章會詳細說明。
複製代碼
JS 在 ES6 以前只有函數做用域,沒有塊級做用域的概念。
看一下代碼:
function doSomething() {
for (var i = 0; i < 10; i++) {
...
}
console.log(i)
}
doSomething(); // 10
複製代碼
因爲 JS 沒有塊級做用域,變量 i 在函數做用域中只有一個,每次 for 循壞都在改變這一個變量。
再看阮一峯老師 ES6 教程裏的一段代碼:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10;
複製代碼
以上代碼中,因爲沒有塊級做用域,i 變量全局只有一個,當 for 循壞結束,變量 i 的值等於 10, 因此 a[6]()
對應函數內的變量 i 的打印值就是 10。
ES 6 中經過 let
和 const
關鍵字 引用了塊級做用域的概念,所謂塊級做用域,就是以 {}
包裹的區域。
咱們將阮一峯老師 ES6 教程裏的一段代碼改爲 let 的形式:
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6;
複製代碼
這時,數組內的索引爲6函數內的變量打印值爲6
,每次循環,會建立新的塊級做用域,而後從新聲明一個新的變量 i;JS 的解釋引擎會記住上次循環的變量值,因此可以返回正確的結果。
let
和 const
會聲明一個塊級做用域的變量及常量,不易發生變量命名污染的問題,能規避衝突,幫助你寫出簡潔優雅的代碼,建議一直使用。
詞法做用域,也能夠叫作靜態做用域,是什麼意思呢?
不管函數在哪裏調用,詞法做用域都只由函數被聲明時所處的位置決定。
既然有靜態做用域,那麼也有動態做用域。
而動態做用域的做用域則是由函數被調用執行的位置所決定。
var a = 123;
function func1() {
console.log(a);
}
function func2() {
var a = 456;
func1();
}
func2(); // 123
複製代碼
以上代碼,最後輸出結果 a 的值,來自於 func1 聲明時所在位置訪問到的 a 值 123。
因此 JS 的做用域是靜態做用域,也叫詞法做用域。
在 JS 引擎中,經過標識符查找標識符的值,會從當前做用域向上尋找,直到做用域找到第一個匹配的標識符爲止。就是 JS 的做用域鏈
若是嵌套做用域有多個相同標識符,那麼,最內部的標識符會覆蓋外層標識符,這叫作「遮蔽效應」
var a = 1;
function func1() {
var a = 2;
function func2() {
var a = 3;
console.log(a); // 3
}
func2();
}
func1(); // 3
複製代碼
func2 中變量 a,會從內部開始向外部上層尋找,找到最近的 a 標識符的聲明爲止。
JS 是一門基於詞法做用域(靜態做用域)的語言,JS 會沿着做用域鏈像氣泡同樣向外部尋找變量聲明。
JS 又是函數做用域的語言,在 ES6 中,使用 let
和 const
關鍵字後,能讓變量處於塊做用域中,並且不存在聲明提高。
後面的文章會介紹 JS 中的聲明提高和閉包,敬請期待。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。