ES6 let详解

在ES6中新增了 let 用于声明变量

作用域

在ES6之前js不存在块级作用域概念,只有函数作用域和全局作用域
ES6之后引入 let 实现块级作用域
块级作用域的意思其实就是一个{}(代码块),变量只在{}中有效

{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

一个经典的例子

for (var i = 0; i <10; i++) {  
  setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
    console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
  }, 100);
}
// 输出结果
1010// 这里面的知识点: JS的事件循环机制,setTimeout的机制等

这个代码本来的目的是要实现每隔100毫秒,分别打印0~9,但最后确打印了10个“10”,要解释原因就不得不提到 异步宏任务队列,详情可以看我之前写的JS执行机制,这里我就简单的讲一下
当JS代码中遇到setTimeout()时,JS会将这个任务交由相应的进程来执行,此时JS会继续执行下面的代码,等待设置的时间到了之后,setTimeout中的回调会被放到一个叫任务队列的队列之中,当代码执行完毕后,任务队列中的任务会被再次放回来执行。这就是为什么这里setTimeout中的回调最后打印了十个10,因为当setTimeout中的回调执行的时候,i已经累加到10了。
当我们把这里的var换成let

for (let i = 0; i <10; i++) {  
  setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
    console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
  }, 100);
}
// 输出结果
0  1  2  3  4  5  6  7  8 9
// 这里面的知识点: JS的事件循环机制,setTimeout的机制等

为什么换成let 就可以了呢?
因为在for中,设置循环的那一部分是一个父级作用域,而循环体内部是一个单独的子作用域,所以当前的i只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量。 这里可能有点懵,我的理解是,当使用var时,全局只有一个 i,且所以循环体中的 i 值都会因为之后 i 的累加而改变。
当使用 let 时,每轮循环都会单独申明一个i,每个 i 的值都不同,都不互相影响。

let 不存在变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

这里因为作用域中申明了tmp,所以tmp绑定了这个作用域,不受父级作用域影响,所以这里对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
隐蔽的死区问题

function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 报错
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined

不允许重复申明

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

块级作用域与函数声明

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

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

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

以上都是非法的

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

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会被提升到函数头部,实际运行的代码如下。

// 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 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
    上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
    浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下。
// 浏览器的 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

所以为了环境的差异带来的影响,建议使用函数表达式,而不是声明函数

ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
  let x = 1;
}

第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。
函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。

顶层对象属性

在ES6中,varfunction 声明的全局变量属于顶层对象属性
let const class 声明的全局变量属于顶级对象属性

参考1
参考2

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值