面试题之 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
复制代码

但事实却很打脸,得到的结果如下:

面试题之 let 简析

这和我们的期望结果不太一样,为什么会得到这样的结果呢? 在此可以说明一下,虽然有些跑题。

原因有二:

一个原因是: 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);
}
复制代码
面试题之 let 简析

可以看到,代码不易理解,书写也很麻烦。

于是 ES6 中新出现了 let 声明方式, 通过 let 声明的变量有了 块级作用域 的限制。 举个例子,先从 var 声明开始:

{
    var a = 1;
}
console.log(a);
复制代码

由于 var 没有块级作用域的约束,所以我们在块级作用域以外访问 a 变量的话,仍然是能够访问到的。因此,上面的定时器例子会打印出 10 个 10。

接下来,我们将 var 改为 let 进行声明:

{
    let a = 1;
}
console.log(a);
复制代码

大家可以猜测一下输出结果是什么?

事实上会报错的:

面试题之 let 简析

原因在于,按照 let 的声明特点, let 定义的变量只能在声明时所在的作用域中访问到,所以 a 被限制在了块级作用域中,在块级作用域外访问 a 的话,由于外层作用域并未定义 a 变量,所以会报上述错误。

既然 let 有了块级作用域的约束,我们就可以用 let 来改写上面的定时器例子:

for(let i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    });
}
复制代码
面试题之 let 简析

可以看到,使用let 我们就能够正常将 i 值约束在每一个循环块作用域中了,这比 ES5 中利用闭包要容易理解多了。

重复声明的隐患

以前我们用 var 声明变量的时候,可以重复进行同名变量的声明,比如

var a = 1;
var a = 2;
var a = 3;
console.log(a);
复制代码
面试题之 let 简析

以上代码,没有报错,并且 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 简析

所以,let 为我们带来了另一个好处,防止我们定义重名变量。

变量声明不再提升

仍然以一段代码为例:

console.log(a);
var a = 1;
复制代码

大家猜测一下,输出结果是什么?

程序运行不会报错,但是输出结果不是我们期望的。

面试题之 let 简析

输出结果是 undefined。

所以,这会带来一个问题,我们在声明一个变量之前,万一使用了这个变量,就会得到意料之外的结果,进而造成程序运行错误,如果能有一种机制能够强制我们在使用变量之前,必须先声明该变量就好了。

这就是 let 的第三个特点:使用一个 let 声明的变量之前,必须先声明。

console.log(a);
let a = 1;
复制代码

如上代码,a 变量的声明放在了使用之后,我们看下运行结果:

面试题之 let 简析

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


以上所述就是小编给大家介绍的《面试题之 let 简析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Black Box Society

The Black Box Society

Frank Pasquale / Harvard University Press / 2015-1-5 / USD 35.00

Every day, corporations are connecting the dots about our personal behavior—silently scrutinizing clues left behind by our work habits and Internet use. The data compiled and portraits created are inc......一起来看看 《The Black Box Society》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码