ES6(二: 變數作用域)
let命令
首先來看一個例子
if(true){
var a =3;
}
console.log(a); // 3
if(true){
let b =3;
}
console.log(b); // ReferenceError: b is not defined
這是babel編譯結果
if (true) {
var a = 3;
}
console.log(a); // 3
// babel 將塊級宣告成不同的變數名
if (true) {
var _b = 3;
}
console.log(b); // ReferenceError: b is not defined
上面程式碼在程式碼塊之中,分別用 let 和 var 宣告瞭兩個變數。然後在程式碼塊之外呼叫這兩個變數,結果 let 聲
明的變數報錯, var 宣告的變數返回了正確的值。這表明, let 宣告的變數只在它所在的程式碼塊有效。
for 迴圈的計數器,也合適使用 let 命令。for(){}
再看一個比較經典的列子
var a =[];
for(var i =0;i<10;i++){
a[i] = function(){
console.log(i);
}
}
// 不管你這裡是2 3 4 5 6,輸出都一樣
a[3](); // 10
a[6](); // 10
對於這種情況,其實沒let之前,我們也是可以處理的。大概樣子就是這樣
var a =[];
for(var i =0; i<10; i++){
(function(i){ // 通過匿名函式自調
a[i] = function(){
console.log(i);
}
})(i)
}
a[3](); // 3
a[6](); // 6
上面程式碼,通過一次匿名函式自調,將i的值,做了一次儲存操作,至於為什麼,最後總結會說。
現在我們來看使用let
var a =[];
for(let i =0; i<10; i++){
a[i] = function(){
console.log(i);
}
}
a[3](); // 3
a[6](); // 6
為什麼會輸出這樣,我們來看下babel的編譯結果
"use strict";
var a = [];
var _loop = function _loop(i) { // 使用一個函式包裝
a[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
a[3](); // 3
a[6](); // 6
上面程式碼,其實和我們處理方案一樣,都是通過一個函式包裝,儲存了變數。
(1) 不存在變數提升
我們來看一個宣告提前的。
console.log(a);
var a = 3; // undefined
上面程式碼其實是這樣
var a;
console.log(a);
a = 3; // undefined
上面程式碼,是變數提升到作用域頂部,賦值留在本地。
我們來看let會怎麼樣
console.log(b); // ReferenceError: b is not defined
let b = 3;
babel官方的編譯結果,發現其實還有有點看不懂,貼出來吧
"use strict";
console.log(b); // undefined
var b = 3;
這裡就覺得有點坑。
(2) 暫時性死區
只要塊級作用域記憶體在 let 命令,它所宣告的變數就“繫結”(binding)這個區域,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError: tmp is not defined
let tmp;
}
上面程式碼中,存在全域性變數 tmp,但是塊級作用域內 let 又宣告瞭一個區域性變數 tmp,導致後者繫結這個塊級作用域,所以在 let 宣告變數前,對 tmp 賦值會報錯。
ES6 明確規定,如果區塊中存在 let 和 const 命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域。
凡是在宣告之前就使用這些變數,就會報錯。
總之,在程式碼塊內,使用 let 命令宣告變數之前,該變數都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
PS
總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變數就已經存在了,但是不可獲取,只有等到宣告變數的那一行程式碼出現,才可以獲取和使用該變數。
(3) 不允許重複宣告
let 不允許在相同作用域內,重複宣告同一個變數。
function f1() {
let a = 10;
var a = 11; // SyntaxError: Identifier 'a' has already been declared
}
// 報錯
function f2() {
let a = 10;
let a = 1;
}
因此,不能在函式內部重新宣告引數。
function func(arg) {
let arg; // SyntaxError: Identifier 'arg' has already been declared
}
function func(arg) {
{
let arg; // 不報錯
}
}
(4) 塊級作用域
ES5 只有全域性作用域和函式作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,內層變數可能會覆蓋外層變數。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = "hello world";
}
}
f(); // undefined
上面程式碼實際是這樣
var tmp = new Date();
function f() {
var tmp;
console.log(tmp);
if (false) {
tmp = "hello world";
}
}
f(); // undefined
上面程式碼其實是一個常見的宣告提前的面試題,這裡涉及js的歷史問題,在es6之前,只有函式有作用域,如果是函式裡面,宣告提前會將變數宣告提前到函式頂部;若為全域性,會提升到全域性作用域頂部。
第二種場景,用來計數的迴圈變數洩露為全域性變數
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面程式碼中,變數 i 只用來控制迴圈,但是迴圈結束後,它並沒有消失,洩露成了全域性變數。
ES6的塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
f1();
babel編譯結果為:
"use strict";
function f1() {
var n = 5;
if (true) {
var _n = 10; // 老套路,使用不一樣的變數宣告
}
console.log(n); // 5
}
f1();
上面的函式有兩個程式碼塊,都宣告瞭變數 n,執行後輸出 5。這表示外層程式碼塊不受內層程式碼塊的影響。如果使用 var 定義變數 n,最後輸出的值就是 10。因為var定義,if是沒作用域的。
ES6 允許塊級作用域的任意巢狀和內層定義外層同名變數。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
babel編譯結果:
{
{
{
{
var insane = 'Hello World';
{
var _insane = 'Hello World'; // 老套路了。
}
}
}
}
};
小結:
(1)在ES6之前,作用域分為兩種,一種是全域性作用域,一種是函式內部作用域,因為沒塊級作用域慨念,宣告提前就是很容易汙染全域性變數的問題。
(2)塊級作用域的出現,替代了匿名函式自調。儲存函式作用域的寫法。
(5) 塊級作用域與函式宣告
ES5 規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告。
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
}
但是,瀏覽器沒有遵守這個規定,還是支援在塊級作用域之中宣告函式,因此上面兩種情況實際都能執行,不會報錯。不過, ES5“嚴格模式”下還是會報錯。
ES6 引入了塊級作用域,明確允許在塊級作用域之中宣告函式。
並且 ES6 規定,塊級作用域之中,函式宣告語句的行為類似於 let,在塊級作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重複宣告一次函式 f
function f() { console.log('I am inside!'); }
}
f();
}());
上面程式碼在 ES5 中執行,會得到“I am inside!”,因為在 if 內宣告的函式 f 會被提升到函式頭部。
考慮到環境導致的行為差異太大,應該避免在塊級作用域內宣告函式。如果確實需要,也應該寫成函式表示式,而不是函式宣告語句。
// 函式宣告語句
{
let a = 'secret';
function f() {
return a;
}
}
// 函式表示式
{
let a = 'secret';
let f = function () {
return a;
};
}
babel編譯結果
'use strict';
// 函式宣告語句
{
var f = function f() {
return a;
};
var a = 'secret';
}
// 函式表示式
{
var _a = 'secret';
var _f = function _f() {
return _a;
};
}
另外,還有一個需要注意的地方。
ES6 的塊級作用域允許宣告函式的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
(5) const命令
const 宣告一個只讀的常量。一旦宣告,常量的值就不能改變。類似java的final static。
const 宣告的變數不得改變值,這意味著。
(1)const 一旦宣告變數,就必須立即初始化,不能留到以後賦值。
(2)const 的作用域與 let 命令相同:只在宣告所在的塊級作用域內有效。
(3)const 命令宣告的常量也是不提升,同樣存在暫時性死區,只能在宣告的位置後面使用。
(4)const 宣告的常量,也與 let 一樣不可重複宣告。
(5)對於複合型別的變數,變數名不指向資料,而是指向資料所在的地址。 const 命令只是保證變數名指向的地址不變,並不保證該地址的資料不變,所以將一個物件宣告為常量必須非常小心。
對最後一點舉一個例子
const foo = {};
foo.prop = 123;
// 123
foo = {}; // TypeError: "foo" is read-only
上面程式碼中,常量 foo 儲存的是一個地址,這個地址指向一個物件。
不可變的只是這個地址,即不能把 foo 指向另一個地址,但物件本身是可變的,所以依然可以為其新增新屬性。
下面是另一個例子
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 報錯
上面程式碼中,常量 a 是一個陣列,這個陣列本身是可寫的,但是如果將另一個陣列賦值給 a,就會報錯。
如果真的想將物件凍結,應該使用 Object.freeze 方法。
const foo = Object.freeze({});
// 常規模式時,下面一行不起作用;// 嚴格模式時,該行會報錯
foo.prop = 123;
上面程式碼中,常量 foo 指向一個凍結的物件,所以新增新屬性不起作用,嚴格模式時還會報錯。
除了將物件本身凍結,物件的屬性也應該凍結。下面是一個將物件徹底凍結的函式。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, value) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES5 只有兩種宣告變數的方法: var 命令和 function 命令。
ES6 除了新增 let 和 const 命令,後面章節還會提到,另外兩種宣告變數的方法: import 命令和 class 命令。所以, ES6 一共有 6 種宣告變數的方法。
相關文章
- ES6 變數作用域總結變數
- 變數作用域變數
- ES6 變數作用域與提升:變數的生命週期詳解變數
- JS變數作用域JS變數
- SCSS 變數作用域CSS變數
- golang變數作用域Golang變數
- python變數與變數作用域Python變數
- PL/SQL變數作用域SQL變數
- lisp 變數的作用域Lisp變數
- LoadRunner變數作用域變數
- C# 變數作用域C#變數
- es6和es5變數宣告和作用域的不同變數
- 變數物件與作用域鏈變數物件
- JavaScript中變數和作用域JavaScript變數
- JavaScript之變數及作用域JavaScript變數
- java中變數的作用域Java變數
- Go 語言變數作用域Go變數
- JavaScript變數作用域之殤JavaScript變數
- JavaScript 變數的作用域鏈JavaScript變數
- javascript中的作用域(全域性變數和區域性變數)JavaScript變數
- 函式(三)作用域之變數作用域、函式巢狀中區域性函式作用域、預設值引數作用域函式變數巢狀
- 現代 JavaScript 的變數作用域JavaScript變數
- Go語言中的變數作用域Go變數
- 變數、作用域與記憶體變數記憶體
- Shell變數的作用域問題變數
- js中變數作用域問題JS變數
- 變數的作用域--js閉包變數JS
- Java 8 之 lambda 變數作用域Java變數
- 理解 Javascript 中變數的作用域JavaScript變數
- Day08-常量、變數、作用域變數
- golang變數作用域問題-避免使用全域性變數Golang變數
- Go 中的動態作用域變數Go變數
- 注意for迴圈中變數的作用域變數
- c++臨時變數的作用域C++變數
- Python 函式和變數作用域Python函式變數
- Java基礎06:變數、常量、作用域Java變數
- 語法1-變數、常量、作用域變數
- Go基礎知識-02 作用域 常量 變數 作用域(持續更新)Go變數