引言
JavaScript代码是按照顺序执行的吗?为什么有些代码不是按照预期执行的?本来将会详细说明浏览器是如何运行JavaScript的。
变量提升
先来看个例子,先想下会输出什么?
showName()
console.log(myname)
var myname = 'hello world!'
function showName() {
console.log('函数showName被执行');
}
输出:
函数showName被执行和undefined
如果不了解JavaScript的执行机制中的变量提升,一定不清楚为什么会这样。
为什么要提升?
因为JavaScript代码在执行之前需要先编译,在编译阶段变量和函数会被存到变量环境中。编译阶段为什么这么处理,其实跟JavaScript作用域有关系。
在es6之前作用域有全局作用域和函数作用域,为了让整个函数体内部的任何地方都可以被访问到,就在编译阶段提取到执行上下文的变量环境中,这么设计是比较简单的方式。
什么是变量提升?
变量提升是指javascript引擎把变量的声明部分(不包括赋值)和函数的声明部分提升到代码开头的行为。
变量提升规则是什么?
变量提升后,会给变量设置默认值undefined。
如果存在两个相同的函数或者变量和函数同名,如何执行?看个例子
showName()
function showName() {
console.log(1)
}
function showName() {
console.log(3)
}
var showName = function() {
console.log(2)
}
答案是打印3,结论如下:
- 如果是同名的函数,JavaScript编译阶段会选择最后声明的那个,因此是输出3的函数。
- 如果变量和函数同名,那么在编译阶段,变量的声明会被忽略,变量是后定义的并没有覆盖函数。
变量提升缺点?
由于变量提升,可能会导致非预期的结果,例如被覆盖或者没有及时销毁。
如何解决变量提升带来的问题?
es6引入了let和const关键字,让JavaScript有了块级作用域。JavaScript引擎是如何同时支持变量提升和块级作用域的?看个例子:
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
我们已经知道JavaScript引擎是通过变量环境实现函数作用域的,对于块级作用域是通过词法环境的栈结构来实现的。参考如下图:
遇到变量具体查找过程:沿着词法环境的栈顶向下查询,如果找的就返回给JavaScript引擎,没有找到继续在变量环境中查找。如下图所示:
扩展
看看下面代码输出什么?
let myname= 'hello world'
{
console.log(myname)
let myname= 'hi world'
}
输出:Uncaught ReferenceError: Cannot access 'myname' before initialization
词法变量在初始化之前被访问,该错误可以发生于任何语句块中,当使用 let 或 const 修饰的变量在初始化之前被访问的时候。
通过这个题目来说明let和const要在声明之后才能使用,很多人称这个为“暂时性死区”。
总结
通过本文我们知道了变量提升相关的知识,也明白了变量环境和词法环境的作用,再也不怕类似题目了。