首頁 >web前端 >js教程 >js ES6中let指令的使用詳解

js ES6中let指令的使用詳解

零下一度
零下一度原創
2017-07-09 09:34:561689瀏覽

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 = &#39;abc&#39;; // ReferenceError
 let tmp;
}

上面程式碼中,存在全域變數tmp,但是區塊級作用域內let又宣告了一個局部變數tmp,導致後者綁定這個區塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。

ES6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。

總之,在程式碼區塊內,使用let指令宣告變數之前,變數都是不可用的。這在語法上,稱為「暫時性死區」(temporal dead zone,簡稱TDZ)。

if (true) {
 // TDZ开始
 tmp = &#39;abc&#39;; // 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

上面代码中,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

var s = &#39;hello&#39;;

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 = &#39;Hello World&#39;}}}}};

上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。

{{{{
 {let insane = &#39;Hello World&#39;}
 console.log(insane); // 报错
}}}};

内层作用域可以定义外层作用域的同名变量。

{{{{
 let insane = &#39;Hello World&#39;;
 {let insane = &#39;Hello World&#39;}
}}}};

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIFE 写法
(function () {
 var tmp = ...;
 ...
}());

// 块级作用域写法
{
 let tmp = ...;
 ...
}

块级作用域与函数声明

函数能不能在块级作用域之中声明,是一个相当令人混淆的问题。

ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
 function f() {}
}

// 情况二
try {
 function f() {}
} catch(e) {
}

上面代码的两种函数声明,根据ES5的规定都是非法的。

但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。不过,“严格模式”下还是会报错。

// ES5严格模式
&#39;use strict&#39;;
if (true) {
 function f() {}
}
// 报错

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。


// ES6严格模式
&#39;use strict&#39;;
if (true) {
 function f() {}
}
// 不报错

ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 if (false) {
  // 重复声明一次函数f
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());

上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。

// ES5版本
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 function f() { console.log(&#39;I am inside!&#39;); }
 if (false) {
 }
 f();
}());

ES6 的运行结果就完全不一样了,会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响,实际运行的代码如下。

// ES6版本
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 f();
}());

很显然,这种行为差异会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

前面那段代码,在 Chrome 环境下运行会报错。

// ES6的浏览器环境
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 if (false) {
  // 重复声明一次函数f
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());
// Uncaught TypeError: f is not a function

上面的代码报错,是因为实际运行的是下面的代码。

// ES6的浏览器环境
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 var f = undefined;
 if (false) {
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());
// Uncaught TypeError: f is not a function

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 函数声明语句
{
 let a = &#39;secret&#39;;
 function f() {
  return a;
 }
}

// 函数表达式
{
 let a = &#39;secret&#39;;
 let f = function () {
  return a;
 };
}

另外,还有一个需要注意的地方。ES6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

// 不报错
&#39;use strict&#39;;
if (true) {
 function f() {}
}

// 报错
&#39;use strict&#39;;
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 = &#39;go&#39;; //globally scoped 
var i = &#39;able&#39;; //globally scoped

函数(Function)
当瞎下面这种, 也是平等的。

function ingWithinEstablishedParameters() { 
  let terOfRecommendation = &#39;awesome worker!&#39;; //function block scoped 
  var sityCheerleading = &#39;go!&#39;; //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 
};

以上是js ES6中let指令的使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn