面试题之 let 简析
栏目: JavaScript · 发布时间: 5年前
内容简介:ES6 已经出现很长时间了,但是作为一个初学者,仍然要仔细深入的理解这些点,接下来我会写一个 ES6 语法系列,深入讲解 ES6 语法产生的背景与用法,希望能给大家带来帮助。大家都清楚,在 let 声明方式出现之前,我们声明一个变量只能通过 var 来定义。一切都很正常,直到有一天,我们写出了这样的代码:
ES6 已经出现很长时间了,但是作为一个初学者,仍然要仔细深入的理解这些点,接下来我会写一个 ES6 语法系列,深入讲解 ES6 语法产生的背景与用法,希望能给大家带来帮助。
块级作用域
大家都清楚,在 let 声明方式出现之前,我们声明一个变量只能通过 var 来定义。
var a = 1; var b = 'zhangsan'; var c = {name: 1, age: 2}; var d = function(){} var e = []; 复制代码
一切都很正常,直到有一天,我们写出了这样的代码:
for(var i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); }); } 复制代码
我们期望使用这种代码,得到如下结果:
0 1 2 3 4 5 6 7 8 9 复制代码
但事实却很打脸,得到的结果如下:
这和我们的期望结果不太一样,为什么会得到这样的结果呢? 在此可以说明一下,虽然有些跑题。
原因有二:
一个原因是: var 定义的变量不受块级作用域的限制。
另一个原因是:JavaScript 引擎的事件循环机制在起作用。for循环的同步任务执行完毕之后,才会将从事件队列中取出回调函数,放到调用栈中执行:即 setTimeout
的回调函数。所以当同步任务执行完之后, i 的值已经变为了 10,此时,10 个定时器的回调开始执行,打印出 10 个 10。
那么,聪明的同学开始想办法了,利用 JS 中的闭包特性,实现期望的结果:
for(var i = 0; i < 10; i++){ (function(t){ setTimeout(function(){ console.log(t); }); })(i); } 复制代码
可以看到,代码不易理解,书写也很麻烦。
于是 ES6 中新出现了 let 声明方式, 通过 let 声明的变量有了 块级作用域
的限制。 举个例子,先从 var 声明开始:
{ var a = 1; } console.log(a); 复制代码
由于 var 没有块级作用域的约束,所以我们在块级作用域以外访问 a 变量的话,仍然是能够访问到的。因此,上面的定时器例子会打印出 10 个 10。
接下来,我们将 var 改为 let 进行声明:
{ let a = 1; } console.log(a); 复制代码
大家可以猜测一下输出结果是什么?
事实上会报错的:
原因在于,按照 let 的声明特点, let 定义的变量只能在声明时所在的作用域中访问到,所以 a 被限制在了块级作用域中,在块级作用域外访问 a 的话,由于外层作用域并未定义 a 变量,所以会报上述错误。
既然 let 有了块级作用域的约束,我们就可以用 let 来改写上面的定时器例子:
for(let i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); }); } 复制代码
可以看到,使用let 我们就能够正常将 i 值约束在每一个循环块作用域中了,这比 ES5 中利用闭包要容易理解多了。
重复声明的隐患
以前我们用 var 声明变量的时候,可以重复进行同名变量的声明,比如
var a = 1; var a = 2; var a = 3; console.log(a); 复制代码
以上代码,没有报错,并且 a 的值得到了篡改,以最后一次赋值为准。
大家可能觉得没有问题。
我们再举一个例子:
假设 A、B、C 三个同学需要共同完成一个页面功能。
A同学写了一个 JS 文件 A.js,A同学再这个文件中定义了一个name变量,赋值为 章三
,他想在页面上将这个名字打印出来。
var name = '章三'; 复制代码
B同学写了另一个 JS 文件 B.js,也定义了一个变量,也叫 name,赋值为 '李四',他也想在页面上将这个名字打印出来。
var name = '李四'; 复制代码
A 同学告诉 C 同学,取出 name 属性,打印出来就可以了。
B 同学告诉 C 同学,取出 name 属性,打印出来就可以了。
可惜 C 同学不是一个细心的同学,他没有意识到两个变量重名了,于是他写了一个 html 文件,引入了 A.js 和 B.js,然后将 name 属性打印出来。
<script src="./A.js"></script> <script src="./B.js"></script> <body> <div id="name"> </div> <script> document.querySelector('#name').innerHTML = name; </script> </body> 复制代码
然后 C 同学将页面发给 A 同学和 B 同学,让他们看一下结果对不对。
结果 A 同学一看,发现打印出来的名字不是 章三
,而是 李四
,他就怒气冲冲地去质问 C 同学,C同学说,我就是按照你告诉我的方式去打印的呀。
故事进行到这里,矛盾出现了:
同一个作用域下定义多个重名变量,JavaScript 引擎不会报错,但是会为程序的正确性带来隐患。
幸运的是,let 的出现很好地解决了这个问题:
同一个作用域下,let 声明的变量不能和已经声明的变量重名,否则引擎会报错。
let a = 1; let a = 2; 复制代码
或者
var a = 1; let a = 2; 复制代码
所以,let 为我们带来了另一个好处,防止我们定义重名变量。
变量声明不再提升
仍然以一段代码为例:
console.log(a); var a = 1; 复制代码
大家猜测一下,输出结果是什么?
程序运行不会报错,但是输出结果不是我们期望的。
输出结果是 undefined。
所以,这会带来一个问题,我们在声明一个变量之前,万一使用了这个变量,就会得到意料之外的结果,进而造成程序运行错误,如果能有一种机制能够强制我们在使用变量之前,必须先声明该变量就好了。
这就是 let 的第三个特点:使用一个 let 声明的变量之前,必须先声明。
console.log(a); let a = 1; 复制代码
如上代码,a 变量的声明放在了使用之后,我们看下运行结果:
编译器给出了报错提示,这促使我们在早期就能发现问题。
以上所述就是小编给大家介绍的《面试题之 let 简析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。