面试题之 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 简析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

零基础学算法

零基础学算法

戴艳 / 机械工业出版社 / 2009-1 / 59.80元

零基础学算法,ISBN:9787111284048,作者:戴艳 等编著一起来看看 《零基础学算法》 这本书的介绍吧!

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

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具