前言
let
和const
命令是ES6
新增的命令,用來聲明變量,這兩個變量跟ES5
中的var
有許多不同,同時let
和const
也有不一樣的地方。並且在ES6
中也添加了塊級作用域來解決ES5中作用域存在的問題。
作用域
官方解釋是:“一段程序代碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。”
舉個例子來形象的解析下上面的定義:
function fn () {
// 聲明變量
var name = 'marry'
// 定義內部函數
function innerFn () {
console.log(name) // 可以訪問到name變量
}
}
console.log(name) // undefined
ES5作用域分爲全局作用域和函數作用域。
- 全局作用域
var a = 0;
if (true) {
var b = 1;
}
console.log(b); // 輸出1
上面代碼中,在全局中定義變量a,稱爲全局變量,在任何一個地方都可以訪問到變量a。
- 局部作用域
局部作用域也可以稱之爲函數作用域。
function fn () {
var c = 2;
}
console.log(c); // c is not defined
- 作用域鏈
Function對象有一個僅供 JavaScript
引擎存取的內部屬性。
這個屬性就是[[Scope]]
。[[Scope]]
包含了一個函數被創建的作用域中對象的集合。這個集合被稱爲函數的作用域鏈,它決定了哪些數據能被函數訪問。
關於作用域鏈,局部作用域可以訪問到全局作用域中的變量和方法,而全局作用域不能訪問局部作用域的變量和方法。
var a = 0;
function fn () {
var b = 1;
console.log(a); // 輸出 1
}
// 全局作用域並不能訪問 fn 函數中定義的 b 變量
console.log(b); // b is not defined
fn();
塊級作用域
ES6
新增塊級作用域,塊作用域由 { }
包括,函數內部,if
語句和 for
語句裏面的{ }
也屬於塊作用域。
塊級作用域的出現是爲了解決ES5
中作用域的問題:
- 內層變量可能覆蓋外層變量
- 用來計數的循環變量泄露爲全局變量。
var tmp = new Date();
function f() {
console.log(tmp); // 想打印外層的時間作用域
if (false) {
var tmp = 'hello world'; // 這裏聲明的作用域爲整個函數
}
}
f(); // undefined
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // i應該爲此次for循環使用的變量
}
console.log(i); // 5 全局範圍都可以讀到
塊級作用域
通過var
聲明的變量存在變量提升的特性
// var 的情況
console.log(foo); // 輸出undefined
{ var foo = 2; }
console.log(foo) // 2
// 運行時,真正運行的是下面的代碼
var = foo
console.log(foo); // 輸出undefined
{ foo = 2; }
console.log(foo) // 2
- JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然後再一行一行地運行。這造成的結果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升。
- 對於var命令來說,JavaScript的單獨的{}區塊不構成單獨的作用域。但是在JavaScript語言中,單獨使用區塊並不常見。
除此之外,在for循環中:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
上面代碼中,變量i是var命令聲明的,在全局範圍內都有效,所以全局只有一個變量i。
let 和 const
let 和 const 命令
ES6 新增了let命令,用來聲明變量。它的用法類似於var,但是所聲明的變量,只在let命令所在的代碼塊內有效。
- let在{}中聲明的變量只在代碼塊中有效(形成塊級作用域)
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
- 不存在變量提升(let和const)
let命令所聲明的變量一定要在聲明後使用,否則報錯。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;
- 重複聲明報錯(let和const)
let value = 2;
let value = 2; //Uncaught SyntaxError: Identifier 'value' has already been declared
- 變量不會掛在頂層對象下面(let和const)
瀏覽器環境頂層對象是: window;
node環境頂層對象是: global
var a = 1;
// 如果在 Node環境,可以寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
const命令需要注意點
- 一旦聲明,必須馬上賦值
let p; var p1; // 不報錯
const p3 = 'abc'
const p3; // 報錯 沒有賦值
- 一旦聲明值就不能改變
const p = '不能改變';
p = '報錯' // Assignment to constant variable
- const本質是變量指針不能變
const foo = {};
// 爲 foo 添加一個屬性,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
const所說的一旦聲明值就不能改變,實際上指的是:變量指向的那個內存地址所保存的數據不得改動
- 對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,因此等同於常量。
- 對於複合類型的數據(主要是對象和數組),變量指向的內存地址,地址保存的是一個指針,const只能保證指針是固定的(總是指向同一個地址),它內部的值是可以改變的(不要以爲const就安全了!)
暫時性死區
只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,纔可以獲取和使用該變量。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
總之,在代碼塊內,使用let
命令聲明變量之前,該變量都是不可用的。這在語法上,稱爲“暫時性死區”(temporal dead zone,簡稱 TDZ)。
暫時性死區和不能變量提升的意義在於:
爲了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行爲。
本章內容總結
var 和 let、const的區別
- 塊級作用域
- 不存在變量提升
- 暫時性死區
- 不可重複聲明
- let、const聲明的全局變量不會掛在頂層對象下面
const需要注意點
- let可以先聲明稍後再賦值,而const在 聲明之後必須馬上賦值,否則會報錯。
- const 簡單類型一旦聲明就不能再更改,複雜類型(數組、對象等)指針指向的地址不能更改,內部數據可以更改。