大厂面试题系列(三)-变量提升
前言
本文会讲到的主要内容也是前端面试绕不开的一大话题变量提升,看到这篇文章的朋友们想必已经对作用域和作用域链有了一个基本的了解了,如果还不了解的话建议看看小编的面试系列一和二,对理解本文会有一点帮助,那么话不多说,我们步入主题。
一.代码是怎么运行的
首先我们来看看代码是怎么运行的,大家都知道JavaScript中执行顺序是从上到小的,那么在运行的过程中,是否真的是简单的从上往下执行呢?我们来看一段代码:
例1.1
var myName = "小明"
function showName() {
console.log('hello');
}
console.log(myName);
showName()
1.变量提升发生的场景
这段代码很简单,打印结果为 小明 hello ,好,那我们再看接下来的一段代码:
例1.2
console.log(myName);
var myName = "小明"
function showName() {
console.log('hello');
}
showName()
那么这段代码的运行结果又是什么呢?按照代码从上往下执行的原则,结果会报错,为什么呢?因为打印myName的时候还没有声明myName,所以会报错,那么结果真的会是这样吗?
不,这段代码的运行结果是 undefined hello , 哎,很多同学到这可能就有很多疑问了,问什么会是undefined呢? 好,先不要着急,我们接着看一段代码:
2.函数提升发生的场景
例1.3
showName()
console.log(myName);
var myName = "小明"
function showName() {
console.log('hello');
}
很多小伙伴看到这里就会说了,哎在例1.2中打印的是undefined,那么这里shouName(),同样也是在声明之前调用,那么结果一定是两个undefined吧,实则不然,这段代码的打印结果是 hello undefined ,看到这里想必有些小伙伴已经脑袋晕了吧,不要着急,我们先做个小总结:
3.代码运行小结
-
在执行过程中,若使用未声明的变量,Js 执行会报错
-
在一个变量定义之前使用它,不会报错,但是该变量的值为undefined ,而不是定义的值
-
在一个函数定义之前使用它,是不会报错的,且函数能正确执行
二.变量提升现象
首先,什么是变量提升呢?变量提升就是: javascript 代码在执行过程中,javascript 引擎会把变量声明部分和函数声明部分提升到代码的最前面的"行为"
在例1.2中,在编译阶段会发生变量提升现象,所以引擎执行时候的代码可以理解成以下样子:
例2.1
function showName() {
console.log('hello');
}
var myName;
console.log(myName);
myName = "小明"
showName()
这样一看,是不是就能合理的解释为什么运行结果是 undefined 和 hello 了。这就是JavaScript当中存在的变量提升,从例2.1当中我们看到函数整体和变量myName的声明提升到了当前作用域的顶端,相信细心的小伙伴此时就会发现,函数是整体提升,而变量只有声明部分被提升了,赋值部分仍然留在原地。另外一点,也是一个小细节:函数声明提升的优先级是高于变量声明提升的。
1.变量提升优先级
来看下面这段代码:
例2.2
function fn () {
console.log(a); //
var a = 123
console.log(a); //
function a() {}
console.log(a); //
var b = function() {}
console.log(b); //
function d() {}
}
fn()
我把代码运行结果写在下面,因为可以让大家先自己想一想运行结果是个什么样子,然后再来到下面对比一下看看自己有没有写对。那么以上代码按顺序打印的结果是:[Function: a] , 123 , 123 , [Function: b] 。我觉得只要理解了上文讲的变量提升,想必大家都能理解这段代码的运行结果,那么仍然不太理解的小伙伴也不要着急,我们一起来看看:
例2.3
function fn () {
function a () {}
function d () {}
var b ;
console.log(a); // function () {}
a = 123
console.log(a); //123
console.log(a); //123
b = function () {}
console.log(b); //function () {}
}
fn()
好了,这么一看,大家是不是就能完全理解变量提升了。如果还不能理解的话,我建议可以多看看上面这两段代码,直到能完全理解它。
2.用let和const声明的变量是否存在变量提升?
初步理解了变量提升之后,那么问题又来了,用let和const声明的变量是否存在变量提升呢?来看一段代码:
例2.4
function foo() {
var a = 1
if (true) {
var a = 2
console.log(a);
}
console.log(a);
}
foo()
以上代码运行结果为2,2。相信大家应该能理解,那么我们再来看一段代码:
例2.5
function foo() {
let a = 1
if (true) {
let a = 2
console.log(a);
}
console.log(a);
}
foo()
那么这段代码的运行结构还会是2,2,吗?不,在这段代码中,由于变量a都是由let声明的,而用let声明的变量是支持块级作用域的,所以在if{}内部声明的let a = 2和外面声明的let a = 1 是在两个不同的作用域中,所以if当中声明的变量a 无法提升到外部,所以这么来看的话let声明的变量是不存在变量提升的,为什么呢?这就涉及到另一个知识点了。
3.暂时性死区
暂时性死区(temporal dead zone,简称 TDZ)是指:ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。其次在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。
正是因为这种机制,所以用let声明的变量是不会发生变量提升现象的,更准确的来说,也可以这么理解,let声明的变量是存在变量提升的,但是由于存在暂时性死区,导致变量提升的效果不存在了,所以这么来看的话,我们可以直接理解为用let声明的变量不存在变量提升。
暂时性死区方便了我们日常使用,防止变量泄露到外部,导致运行出错。
三.变量提升小结
-
变量提升是javascript 代码在执行过程中,javascript 引擎会把变量声明部分和函数声明部分提升到代码的最前面的"行为"
-
变量提升只是变量声明部分提升
-
函数提升是函数整体提升
-
变量提升发生在编译环节,而赋值操作发生在执行阶段
-
函数声明提升会优先于变量声明提升(导致变量声明被覆盖)(若是还有函数名相同的函数需要提升,则会覆盖之前提升的函数声明)
-
用let声明的变量不存在变量提升现象
好的,记住以上内容,如果还有不理解的地方的话可以收藏本文,先往下看,以后可以回过头来看看,就会理解的更深刻一些。
本系列会由浅入深,全面的讲解到前端面试所需要具备的知识体系,所以看完的小伙伴觉得作者写的还不错的话可以点点不要钱的赞,支持一下,同时也方便以后查找,我们下期见~