前言
本人刚刚入职前端,也大大小小经历了很多面试,想把一些重要的基础知识与面试题结合起来做个总结。由于大部分面试题中都会隐藏着许多基础的JS知识,所以讲解JS基础的内容基本都会包含两部分:面试题和知识点。本人会尽力从面试题切入,然后再阐述面试题中涉及到的相关知识点,为了方便大家理解,也会尽量举出实例和画出流程图。最后,欢迎大家批评与交流。
面试题
今天讲到的知识点是变量提升,这是一个与JS执行流程相关,并且无处不在的东西。首先给出相关面试题,大家先看看能否做出正确答案吧!如果不能,那就仔细看看后续的知识点讲解哦!!
(1)
(function(){
var x = y =1;
})()
var z;
console.log(y); //1,由于y=1,y是一个全局变量,所以y能正常输出
console.log(z); //undefined
console.log(x); //Uncaught ReferenceError: x is not defined
(2)
function fn1(){
console.log('fn1')
}
var fn2
fn1()//fn1
fn2()//fn2 is not a function
fn2 = function(){
console.log('fn2')
}
fn2()//fn2
知识点
定义
变量提升是指在JavaScript代码执行过程中,JS引擎会将变量的声明部分和函数的声明部分提升到开头的“行为”。提升后,会给变量设置默认值,即undefined。
下面是几行简单的代码,将以他为例子,讲解变量提升的过程的原理。
showName()
console.log(myname)
var myname = '小趴菜'
function showName(){
console.log('函数showName被执行')
}
原理
首先在定义中说到了提升,那这到底是一种什么样的行为呢?真的是在代码层面上把声明部分提升到最前面了嘛?
实际上不是的,这就涉及到JS代码的执行流程,如下图所示:
这里的提升是指包括变量和函数在内的所有声明都会在JS代码的编译阶段被JS引擎放入内存中。
- 编译阶段是指词法分析、语法分析、代码生成等一系列过程,这里不细讲这部分
经过编译后,会生成两部分代码,执行上下文和可执行代码。执行上下文是指一段JS代码的执行环境,变量提升的内容就会存储在该执行上下文中。
编译阶段
对应的在例1中的代码在编译阶段后会生成:
执行上下文中:
var name = undefiend
function showName(){
console.log("函数showName被执行了")
}
可执行代码:
showName()
console.log(myname)
myname = "小趴菜"
执行阶段
- JS引擎开始执行“可执行代码”的时候,当执行到showName函数,JS引擎便在执行上下文中查找该函数,由于在编译阶段已经存在该函数的饮用,所以JS引擎就会执行该函数,并输出“函数showName被执行”的结果。
- 执行到console.log(myname)时,就会在其执行上下文中查找到myname变量,并输出undefined
- 最后,将“小趴菜”赋值给myname
变量提升的原因及带来的问题
原因
- 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
- 增加了容错性,如果出现了变量在声明之前使用也不会有太大的问题
问题
var string = 'xiaopacai'
for(var i=0; i < string.length; i++){
console.log(string[i])
}
console.log(i)//9
由于变量提升,i变成了全局变量,本来i只是用计数,但在循环结束后i并没有被销毁
var a = 3
function fn(){
console.log(a)
if(false){
var a = "hello"
}
}
fn()//undefined
由于变量提升,内层定义的a会进行变量提升覆盖外层的a,导致输出undefined
ES6改进
ES6就引入了let和const命令,创建块级作用域,由let和const定义的变量就不存在变量提升