面试题之 let 简析

为何会有 let ?

ES6 已经出现很长时间了,可是做为一个初学者,仍然要仔细深刻的理解这些点,接下来我会写一个 ES6 语法系列,深刻讲解 ES6 语法产生的背景与用法,但愿能给你们带来帮助。javascript

块级做用域

你们都清楚,在 let 声明方式出现以前,咱们声明一个变量只能经过 var 来定义。html

var a = 1;
var b = 'zhangsan';
var c = {name: 1, age: 2};
var d = function(){}
var e = [];
复制代码

一切都很正常,直到有一天,咱们写出了这样的代码:java

for(var i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    });
}
复制代码

咱们指望使用这种代码,获得以下结果:闭包

0
1
2
3
4
5
6
7
8
9
复制代码

但事实却很打脸,获得的结果以下:函数

这和咱们的指望结果不太同样,为何会获得这样的结果呢? 在此能够说明一下,虽然有些跑题。ui

缘由有二:spa

一个缘由是: var 定义的变量不受块级做用域的限制。code

另外一个缘由是:JavaScript 引擎的事件循环机制在起做用。for循环的同步任务执行完毕以后,才会将从事件队列中取出回调函数,放到调用栈中执行:即setTimeout的回调函数。因此当同步任务执行完以后, i 的值已经变为了 10,此时,10 个定时器的回调开始执行,打印出 10 个 10。cdn

那么,聪明的同窗开始想办法了,利用 JS 中的闭包特性,实现指望的结果:htm

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 变量的声明放在了使用以后,咱们看下运行结果:

编译器给出了报错提示,这促使咱们在早期就能发现问题。

结语

以上就是 ES6 的 let 使用总结,虽然是一个很小的点,可是咱们也要认清它出现的背景,为了解决什么问题而生。

以后,会为你们带来 ES6 其余语法的剖析,敬请期待~~~

相关文章
相关标签/搜索