JavaScript中的变量提升机制

我们都知道JavaScript是一门比较 “神奇” 的语言,为什么这么说呢?因为它的一些原理是我们所不能直接理解的,今天就讲下一个关于JavaScript的一个 “神奇” 原理——变量提升。

首先,我们看下面的一段代码:

showName()
console.log(myname)
var myname = '这是个神奇的语言'
function showName () {
	console.log('看,JavaScript运行了')
}

使用过JavaScript开发的程序都知道,JavaScript是顺序执行的。若按照这个逻辑来理解的话,那么:

  • 当执行到第一行代码的时候,由于函数showName()还没有定义,所以执行应该报错;
  • 同样执行第2行的时候,由于变量myname也未定义,所以同样也会报错

然后实际的执行结果是:
在这里插入图片描述
第1行输出了 “看,JavaScript运行了”,第2行输出了undefined,这个前面想像中的顺序执行有点不一样啊。

那么怎么理解呢?

通过上面的执行结果,你应该知道了函数或者变量可以在定义之前使用,那如果使用没有定义的变量或者函数,JavaScript代码还能继续执行吗?我们来实验一下:

showName()
console.log(myname)
function showName() {
	console.log('看,报错了')
}

在这里插入图片描述
这里代码执行时报错了,由此我们可以得到下面三个结论:

  • 在执行过程中,若使用了未声明的变量,那么JavaScript执行会报错。
  • 在一个变量定义之前使用它,不会出错,但是该变量的值会为undefined,而不是定义时的值。
  • 在一个函数定义之前使用它,不会出错,且函数能正确执行。

第一个很好理解,因为变量没有定义,这样在执行JavaScript代码时,就找不到该变量,所以JavaScript会抛出错误。

但第二个、第三个结论就比较费解了:

  • 变量和函数为什么能在其定义之前使用?这似乎表明JavaScript代码并不是一行一行执行的。
  • 同样的方式,变量和函数的处理结果为什么不一样?比如上面的执行结果,提前使用的showName函数能打印完整结果,但是提前使用的myname变量值确是undefined,而不是定义时的 "看,JavaScript运行了"这个值呢?

接下来进入我们的今天的主题——变量提升(Hoisting)

在v8中,对于JavaScript代码中的初始化语句,也就是类似于 var msg = '这是个信号'; 这样的语句,其实是由两部分组成的:

var msg = undefined;  //声明部分
msg = '这是个信号';

如下图所示:
在这里插入图片描述
上面是变量的声明和赋值,接下来我们看看函数的声明和赋值,结合如下代码;

function foo () {
	console.log('foo')
}

var bar = function () {
	console.log('bar')
}

第一个函数foo是一个完整的函数声明,也就是说没有涉及到赋值操作;第二个函数是先声明变量bar,再把function(){…}赋值给bar。为了直观理解,来看下图:
在这里插入图片描述
所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的 “行为” 。变量被提升后,会给变量设置默认值,这个默认值就是undefined。

上面代码的本质就是:

var myname = undefined  //变量提升部分
function showName() {
	console.log('看,JavaScript代码被执行了')
}

showName()
console.log(myname)
myname = '测试变量'

OK,理解了这个概念之后,对于JavaScript中这些未定义就能使用变量和函数就见怪不怪了。

下面我们附带讲下JavaScript代码的执行流程

JavaScript代码的执行流程

通常书上会说JavaScript是一种解释型语言,就是不经过编译直接运行的语言,其实这是不准备的。我们来看下面的图:
JavaScript的执行流程图
1.编译阶段

编译阶段和变量提升有什么关系呢?

我们将上面的代码分为两个部分:

第一部分:变量提升部分的代码:

var myname = undefined
function showName() {
	console.log('看,JavaScript执行了')
}

第二部分:执行部分代码

showName()
console.log(myname)
myname = '测试变量'

我们通过图来看下具体的运行环境:
在这里插入图片描述
这里涉及到一些复杂的概念,我会在后面的文章中进行说明。这里就简单说明一下:

v8中编译一段JavaScript代码,会生成两部分内容:执行上下文(Execution context)和可执行代码。在我的文章《Node.js的C++扩展教程(四)》中也提到过上下文,这里是同一个概念。简单来说:执行上下文就是JavaScript执行一段代码的运行环境。比如调用一个函数,就会进入这个函数的上下文,确定该函数在执行期间用到的诸如this、变量、对象以及函数等。

我们结合代码来分析下JavaScript的编译操作:

 - showName()
 - console.log(myname)
 - var myname = '测试变量'
 - function showName () {
 -   console.log('函数showName被执行')
 - }
  • 第1行和第2行,由于这两行代码不是声明操作,所以JavaScript引擎不会做任何处理。
  • 第3行,由于这行是经过var声明的,因此JavaScript引擎将在变量环境对象中创建一个名为myname的属性,并使用undefined来对其初始化。
  • 第4行,JavaScript引擎发现了一个通过function定义的函数,所以它将函数定义存储到堆(HEAP)中,并在变量环境对象中创建一个showName的属性,然后将该属性值指向堆中函数的位置。

这样,就生成了变量环境对象。接下来JavaScript引擎会把声明以外的代码编译为字节码。

2.执行阶段

JavaScript引擎开始执行编译后的 “可执行代码”,按照一行一行地执行。

  • 当执行到showName函数时,JavaScript引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以JavaScript引擎便开始执行该函数,并输出相应的结果。
  • 接下来打印 “myname” 信息,JavaScript引擎继续在变量环境对象中查找该对象,由于变量环境存在myname变量,并且其值为undefined,所以这个时候就输出undefined。
  • 接下来执行第3行,把 “测试变量” 赋值给 myname变量,赋值后变量环境对象中的myname属性值改编为"测试变量",环境变量如下所示:
VariableEnvironment:
	myname-> "测试变量"
	showName-> function : {console.log(myname)}

以上就是JavaScript引擎对JavaScript代码的编译和执行过程。

那有同学就会问:如果代码中出现了相同的变量或者函数怎么办?

JavaScript引擎给出的答案是覆盖

比如下面的代码:

function showName () {
	console.log('小鸡一枚')
}

function showName () {
	console.log('小鸟一只')
}
showName()

它的输出就是 ‘小鸟一只’,这是在编译阶段就会被处理。

发布了12 篇原创文章 · 获赞 0 · 访问量 384

猜你喜欢

转载自blog.csdn.net/weixin_42071117/article/details/104953426