深入理解JavaScript在浏览器中的执行机制:从变量提升到编译执行
本文深入解析JavaScript在浏览器中的执行机制,重点阐述变量提升、编译阶段与执行阶段的交互过程。通过分析var、let、const的区别及暂时性死区概念,揭示JavaScript引擎如何先编译生成执行上下文,再逐行执行代码的底层原理,帮助开发者理解函数覆盖、作用域及闭包等核心特性。
讨论JavaScript时,执行上下文是一个无法回避的核心概念。只有透彻理解执行上下文,才能更准确地把握JavaScript的运行逻辑,例如变量提升、栈溢出、作用域以及闭包等关键机制。
本文并非专门讲解JavaScript语言基础,因此不会对语言本身做过多的理论铺陈,而是聚焦于JavaScript的执行顺序,深入剖析其运行原理。
首先,我们观察以下代码示例:
showName()
console.log(myname)
var myname = '掘金'
function showName() {
console.log('function showName run')
}作为前端开发者,大家通常认为上述代码是同步执行的,因此预期输出结果应该是:
- 当执行第一行时,由于
showName方法尚未定义,按照直觉应该会报错。 - 同理,执行第二行时,因为变量
name也未定义,预期同样会报错。
然而,实际的执行结果却与我们的预期大相径庭:

第1行输出了“function showName run”,第2行输出了“undefined”。这种结果与之前设想的顺序执行逻辑存在显著差异。
上述结果表明,函数或变量可以在定义之前被使用。那么,如果代码中完全未定义这些变量,执行会正常进行吗?可以参考下面这段去除了第三行 myName 定义的代码:
showName()
console.log(myname)
function showName() {
console.log('function showName run')
}
使用了未定义的变量——执行报错
从上述两段代码的执行结果对比中,我们可以归纳出以下三个结论。
- 在执行过程中,若使用了未声明的变量,JavaScript引擎会抛出错误。
- 在变量定义之前使用它,不会导致错误,但该变量的值将表现为undefined,而非定义时的实际值。
- 在函数定义之前使用它,不会导致错误,且函数能够正确执行。
第一个结论易于理解:由于变量未定义,JavaScript在执行代码时无法找到该变量,因此会抛出错误。
然而,第二个和第三个结论往往令人费解。要解答这两个疑问,我们需要引入接下来要介绍的核心概念——变量提升。
变量提升
在正式探讨变量提升之前,我们需要先厘清JavaScript中的两个基本概念:声明和赋值。
var myName = "掘金"这段代码可以拆解为以下部分来看:
var myName //声明部分
myName = "掘金" //赋值部分
上述内容说明了变量的声明与赋值过程,接下来我们讨论函数的声明与赋值:
function foo(){
console.log('foo')
}
var bar = function(){
console.log('bar')
}第一个函数foo是一个完整的函数声明,不涉及赋值操作;第二个函数则是先声明变量bar,再将 function(){console.log('bar')} 赋值给bar。为了直观理解,你可以参考下图:

通过上述分析,我们了解了变量和函数的声明与赋值机制,接下来我们将深入探讨什么是变量提升。
所谓的变量提升,是指JavaScript代码在执行过程中,JavaScript引擎将变量和函数的声明部分提升到代码开头的“行为”。变量提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined 。
具体变化如下代码所示:
/*
* 变量提升部分
*/
// 把变量 myname提升到开头,
// 同时给myname赋值为undefined
var myname = undefined
// 把函数showName提升到开头
function showName() {
console.log('function showName run');
}
/*
* 可执行代码部分
*/
showName()
console.log(myname)
// 去掉var声明部分,保留赋值语句
myname = '掘金'可以通过下图更直观地感受这一过程:

从图中可以看出,对原始代码主要进行了两处调整:
- 第一处是将声明部分都提升到了代码开头,如变量
myName和函数showName,并给变量设置默认值undefined; - 第二处是移除原本声明的变量和函数,如
var myName = '掘金'的语句移除了 var 声明,整个移除showName的函数声明。
通过这两步操作,即可实现变量提升的效果。你可以执行这段模拟变量提升的代码,其输出结果与第一段代码应该完全一致。
通过这段模拟的变量提升代码,我们明白了为何可以在定义之前使用变量或函数——因为函数和变量在执行前都已被提升至代码开头。
let和const会变量提升吗?
我们执行以下代码进行尝试:
console.log(myName)
const myName = '掘金'
上图的报错为:ReferenceError: Cannot access 'variable' before initialization。这与未定义的报错不同,未定义报错如下
console.log(myName1)
const myName = '掘金'
由此可见,用 var 定义的变量可以在声明之前被访问而不报错,但是用 let 或 const 定义的变量却不行。
由 let 或 const 定义的变量在提升时不会默认初始化,因此在声明之前访问会报错:ReferenceError: Cannot access 'variable' before initialization。
然而由 var 定义的变量在提升时会被初始化为默认值 undefined,因此在声明之前访问会得到 undefined。这里面涉及到了一个概念 暂时性死区,let/const 定义的变量被提升却无法正常访问,是因为存在暂时性死区(Temporal Dead Zone)。
JavaScript 代码的执行顺序
通过上述对变量提升的介绍,你可能会误以为变量和函数在物理层面被移动到了代码最前面,正如图中所展示的那样。但实际上,变量和函数声明在代码中的位置并未改变,而是在编译阶段被JavaScript引擎放入到内存中。没错,一段JavaScript代码在被执行前需要经过JavaScript引擎的编译,编译完成后才会被执行,如下图所示:

1.编译阶段
编译和变量提升有什么关系呢?我们可以从下面两部分代码来看:
第一部分:变量提升部分的代码。
var myName = undefined
function showName() {
console.log('function showName run');
}第二部分:执行部分的代码。
showName()
console.log(myName)
myName = '掘金'我们可以将JavaScript的执行流程细化,如下图所示:

从上图可以看出,输入一段代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。
执行上下文是JavaScript执行一段代码时的运行环境,例如调用一个函数,就会进入该函数的执行上下文,确定该函数在执行期间用到的诸如this、变量、对象以及函数等。
可以用下面的伪代码简单理解环境变量对象:
VariableEnvironment:
myname -> undefined,
showName ->function : {console.log(myname)接下来我们看一下生成环境变量对象的过程:
showName()
console.log(myName)
var myName = '掘金'
function showName() {
console.log('function showName run');
}- 第1行和第2行,由于这两行代码不是声明操作,因此JavaScript引擎不会做任何处理;
- 第3行,由于这行是经过var声明的,因此JavaScript引擎将在环境对象中创建一个名为
myName的属性,并使用undefined对其进行初始化; - 第4行,JavaScript引擎发现了一个通过 function 定义的函数,因此它将函数定义存储到堆(HEAP)中,并在环境对象中创建一个showName的属性,然后将该属性值指向堆中函数的位置。
这样就生成了变量环境对象。接下来JavaScript引擎会把声明以外的代码编译为字节码,可以类比如下的伪代码:
showName()
console.log(myname)
myname = '掘金'好了,现在有了执行上下文和可执行代码,那么接下来就到了执行阶段了。
2. 执行阶段
JavaScript引擎开始执行“可执行代码”,按照顺序一行一行地执行。下面我们就来一行一行分析下这个执行过程:
- 当执行到
showName函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以 JavaScript 引擎便开始执行该函数,并输出“函数showName被执行”结果。 - 接下来打印
myName信息,JavaScript 引擎继续在变量环境对象中查找该对象,由于变量环境存在myName变量,并且其值为undefined,所以这时候就输出undefined。 - 接下来执行第3行,把“掘金”赋给
myName变量,赋值后变量环境中的myname属性值改变为“掘金”,变量环境如下所示:
VariableEnvironment:
myname -> "掘金",
showName ->function : {console.log(myName)代码中出现相同的变量或者函数怎么办?
现在你已经知道了,在执行一段JavaScript代码之前,会编译代码,并将代码中的函数和变量保存到执行上下文的变量环境中,那么如果代码中出现了重名的函数或者变量,JavaScript引擎会如何处理?
我们先看下面这样一段代码:
function showName() {
console.log('沸点');
}
showName();
function showName() {
console.log('掘金');
}
showName(); 分析这两次调用打印出来的值是什么吗?
完整执行流程:
- 首先是编译阶段。遇到了第一个
showName函数,会将该函数体存放到变量环境中。接下来是第二个showName函数,继续存放至变量环境中,但是变量环境中已经存在一个showName函数了,此时,第二个showName函数会将第一个showName函数覆盖掉。这样变量环境中就只存在第二个showName函数了。 - 接下来是执行阶段。先执行第一个
showName函数,但由于是从变量环境中查找showName函数,而变量环境中只保存了第二个showName函数,所以最终调用的是第二个函数,打印的内容是“掘金”。第二次执行showName函数也是走同样的流程,所以输出的结果也是“掘金”。
综上所述,一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。
总结
好了,今天就到这里,下面我来简单总结下今天的主要内容:
- JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
- 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为
undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。 - 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。
以上就是今天所讲的主要内容,当然,学习这些内容并不是让你掌握一些 JavaScript 小技巧,其主要目的是让你清楚JavaScript的执行机制:先编译,再执行。
以上就是JavaScript在浏览器中的执行机制的详细内容,更多关于JavaScript执行机制的资料请关注本站其它相关文章!
JS监听滚动事件:回到顶部按钮的显示与隐藏逻辑
2026-11-05 16:19:37
JavaScript怎么读写LocalStorage与SessionStorage
2026-11-05 15:40:11
anime.js 与 GSAP 对比:轻量级项目该怎么选择动画库
2026-11-05 11:24:55
flv.js 与 hls.js 在直播场景下的选型对比
2026-11-05 08:18:52
JavaScript能做什么?前端开发中的作用与应用场景解析
2026-11-04 23:17:54
