ES6新增了let指令,用來宣告變數。它的用法類似var,但是所宣告的變量,只在let指令所在的程式碼區塊內有效
let
指令
#基本用法
# #ES6新增了let指令,用來宣告變數。它的用法類似var,但是所宣告的變量,只在let指令所在的程式碼區塊內有效。
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
上面程式碼在程式碼區塊之中,分別用let和var宣告了兩個變數。然後在程式碼區塊之外呼叫這兩個變量,結果let宣告的變數報錯,var宣告的變數回傳了正確的值。這表明,let聲明的變數只在它所在的程式碼區塊有效。
for迴圈的計數器,就很適合使用let指令。
for (let i = 0; i < 10; i++) {} console.log(i); //ReferenceError: i is not defined
上面程式碼中,計數器i只在for迴圈體內有效,在迴圈體外引用就會報錯。
下面的程式碼如果使用var,最後輸出的是10。
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面程式碼中,變數i是var宣告的,在全域範圍內都有效。所以每一次循環,新的i值都會覆蓋舊值,導致最後輸出的是最後一輪的i的值。
如果使用let,宣告的變數只在區塊層級作用域內有效,最後輸出的是6。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面程式碼中,變數i是let宣告的,目前的i只在本輪迴圈有效,所以每一次迴圈的i其實都是一個新的變量,所以最後輸出的是6。
不存在變數提升let不像var那樣會發生「變數提升」現象。所以,變數一定要在宣告後使用,否則報錯。
console.log(foo); // 输出undefined console.log(bar); // 报错ReferenceError var foo = 2; let bar = 2;
上面程式碼中,變數foo用var指令聲明,會發生變數提升,也就是當腳本開始執行時,變數foo已經存在了,但是沒有值,所以會輸出undefined。變數bar用let指令聲明,不會發生變數提升。這表示在宣告它之前,變數bar是不存在的,這時如果用到它,就會拋出錯誤。
暫時性死區只要在區塊層級作用域內有let指令,它所宣告的變數就「綁定」(binding)這個區域,不再受外部的影響。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面程式碼中,存在全域變數tmp,但是區塊級作用域內let又宣告了一個局部變數tmp,導致後者綁定這個區塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。
ES6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在程式碼區塊內,使用let指令宣告變數之前,變數都是不可用的。這在語法上,稱為「暫時性死區」(temporal dead zone,簡稱TDZ)。
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
上面程式碼中,在let指令宣告變數tmp之前,都屬於變數tmp的「死區」。
「暫時性死區」也意味著typeof不再是百分之百安全的操作。
typeof x; // ReferenceError let x;
上面程式碼中,變數x使用let指令聲明,所以在宣告之前,都屬於x的“死區”,只要用到該變數就會報錯。因此,typeof運行時就會拋出一個ReferenceError。
作為比較,如果一個變數根本沒有被聲明,使用typeof反而不會報錯。
typeof undeclared_variable // "undefined"
上面程式碼中,undeclared_variable是一個不存在的變數名,結果回傳「undefined」。所以,在沒有let之前,typeof運算子是百分之百安全的,永遠不會報錯。現在這一點不成立了。這樣的設計是為了讓大家養成良好的程式設計習慣,變數一定要在聲明之後使用,否則就報錯。
有些「死區」比較隱蔽,不太容易發現。
function bar(x = y, y = 2) { return [x, y]; } bar(); // 报错
上面程式碼中,呼叫bar函數之所以報錯(某些實作可能不會報錯),是因為參數x預設值等於另一個參數y,而此時y還沒有聲明,屬於」死區「。如果y的預設值是x,就不會報錯,因為此時x已經宣告了。
function bar(x = 2, y = x) { return [x, y]; } bar(); // [2, 2]
ES6規定暫時性死區和let、const語句不出現變量提升,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。這樣的錯誤在ES5是很常見的,現在有了這種規定,避免這類錯誤就很容易了。
總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變數就已經存在了,但是不可獲取,只有等到聲明變數的那一行程式碼出現,才可以取得和使用該變數。
不允許重複宣告let不允許在相同作用域內,重複宣告同一個變數。
// 报错 function () { let a = 10; var a = 1; } // 报错 function () { let a = 10; let a = 1; }
因此,不能在函數內部重新宣告參數。 function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
為什麼需要區塊層級作用域?
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]); } console.log(i); // 5
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6的块级作用域
let实际上为JavaScript新增了块级作用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。
ES6允许块级作用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。
{{{{ {let insane = 'Hello World'} console.log(insane); // 报错 }}}};
内层作用域可以定义外层作用域的同名变量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}};
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// IIFE 写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... }
块级作用域与函数声明
函数能不能在块级作用域之中声明,是一个相当令人混淆的问题。
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
// 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { }
上面代码的两种函数声明,根据ES5的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。不过,“严格模式”下还是会报错。
// ES5严格模式 'use strict'; if (true) { function f() {} } // 报错
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。
// ES6严格模式 'use strict'; if (true) { function f() {} } // 不报错
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会被提升到函数头部,实际运行的代码如下。
// ES5版本 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); }());
ES6 的运行结果就完全不一样了,会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响,实际运行的代码如下。
// ES6版本 function f() { console.log('I am outside!'); } (function () { f(); }());
很显然,这种行为差异会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
前面那段代码,在 Chrome 环境下运行会报错。
// ES6的浏览器环境 function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
上面的代码报错,是因为实际运行的是下面的代码。
// ES6的浏览器环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句 { let a = 'secret'; function f() { return a; } } // 函数表达式 { let a = 'secret'; let f = function () { return a; }; }
另外,还有一个需要注意的地方。ES6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错 'use strict'; if (true) { function f() {} } // 报错 'use strict'; if (true) function f() {}
do 表达式
本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
{ let t = f(); t = t * t + 1; }
上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。
现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。
let x = do { let t = f(); t * t + 1; };
上面代码中,变量x会得到整个块级作用域的返回值。
JavaScript ES6 的 let 和 var 的比较
在javascript 1.7中, let 关键词被添加进来, 我听说它声明之后类似于”本地变量“, 但是我仍然不确定它和 关键词 var 的具体区别。
回答:
不同点在于作用域, var关键词的作用域是最近的函数作用域(如果在函数体的外部就是全局作用域), let 关键词的作用域是最接近的块作用域(如果在任何块意外就是全局作用域),这将会比函数作用域更小。
同样, 像var 一样, 使用let 声明的变量也会在其被声明的地方之前可见。
下面是Demo 例子。
全局(Global)
当在函数体之外它们是平等的。
let me = 'go'; //globally scoped var i = 'able'; //globally scoped
函数(Function)
当瞎下面这种, 也是平等的。
function ingWithinEstablishedParameters() { let terOfRecommendation = 'awesome worker!'; //function block scoped var sityCheerleading = 'go!'; //function block scoped };
块(Block)
这是不同点, let 只是在 for 循环中, var 却是在整个函数都是可见的。
function allyIlliterate() { //tuce is *not* visible out here for( let tuce = 0; tuce < 5; tuce++ ) { //tuce is only visible in here (and in the for() parentheses) }; //tuce is *not* visible out here }; function byE40() { //nish *is* visible out here for( var nish = 0; nish < 5; nish++ ) { //nish is visible to the whole function }; //nish *is* visible out here };
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是javascript ES6 新增了let指令使用介紹(圖文教學)的詳細內容。更多資訊請關注PHP中文網其他相關文章!