JavaScript 执行机制解析
JavaScript 作为一种广泛使用的脚本语言,其执行机制与传统编译型语言有显著差异。本文将结合具体代码示例,深入探讨 V8 引擎下 JavaScript 的执行原理,包括编译与执行阶段、变量提升、函数提升以及不同数据类型的存储方式等核心概念。
一、JS 执行的两个核心阶段
V8 引擎执行 JavaScript 代码分为两个主要阶段:编译阶段和执行阶段,这两个阶段交替进行,形成了 JS 独特的执行模式。
- 编译阶段:在代码执行前的一瞬间完成,主要负责语法错误检测、变量和函数提升等准备工作
- 执行阶段:按照编译后的结果执行代码,处理具体的逻辑运算
这种 "边编译边执行" 的特性,使得 JavaScript 既保持了脚本语言的灵活性,又能通过预编译优化执行效率。
二、执行上下文与调用栈
V8 引擎通过执行上下文和调用栈来管理代码执行:
- 执行上下文是一个包含代码执行所需所有信息的对象
- 调用栈则负责管理执行上下文的创建和销毁
全局代码执行时,首先会创建全局执行上下文并压入调用栈。当执行函数时,会创建新的函数执行上下文并压入栈顶,函数执行完毕后其执行上下文会被弹出栈并销毁。
三、变量提升与函数提升
变量提升和函数提升是编译阶段的重要工作,通过以下代码可以清晰理解:
1. 变量提升示例
// 1.js
showName();
console.log(showName);
console.log(hero);
var myName = '张三';
let hero = '钢铁侠';
function showName(){
console.log(myName);
console.log('函数showName被执行');
}
执行结果分析:
showName()可以正常执行,因为函数声明被提升console.log(showName)会输出函数体,证明函数被提升console.log(hero)会报错,因为let声明的变量存在暂时性死区showName函数中打印myName会输出undefined,因为变量提升后尚未赋值
V8 引擎对这段代码的编译处理类似:
// 编译阶段的处理(模拟)
function showName(){
console.log(myName);
console.log('函数showName被执行');
}
var myName; // 初始化为undefined
// 执行阶段
showName();
console.log(showName);
console.log(hero); // 报错,let声明的变量未到初始化阶段
myName = '张三';
let hero = '钢铁侠'; // let变量在执行到此处才初始化
2. 函数提升优先级
函数声明的提升优先级高于变量提升:
// 2.js
showName();
console.log(myName);
myName = '张三';
// 编译阶段处理(模拟)
function showName(){
console.log('函数showName被执行');
}
var myName; // undefined
执行结果:
- 函数正常执行并输出 "函数 showName 被执行"
- 打印
myName输出undefined(已提升但未赋值)
3. 函数表达式不提升
与函数声明不同,函数表达式不会被提升:
// 6.js
func(); // 报错:func is not a function
let func = () => {
console.log('函数表达式不会提升');
}
执行时会报错,因为函数表达式使用 let 声明,既不会像函数声明那样提升,也处于暂时性死区中无法访问。
四、var、let 和 const 的区别
1. 重复声明特性
// 4.js
console.log(a); // undefined
var a = 1;
var a = 2; // 重复声明,不报错,会被忽略
console.log(a); // 2
let b = 3;
// let b = 4; // 重复声明,会报错
console.log(b); // 3
var允许重复声明,后面的声明会覆盖前面的let/const不允许重复声明,会直接报错- 即使在严格模式下,
var仍然允许重复声明
2. 暂时性死区
let 和 const 声明的变量存在暂时性死区,在声明之前访问会报错,而 var 声明的变量会提升为 undefined。
五、函数执行上下文
函数执行时会创建独立的执行上下文,包含参数处理、变量声明等:
// 3.js
var a = 1;
function fn(a){
console.log(a); // 输出函数a
var a = 2;
// function a(){}
var b = a;
console.log(a); // 2
}
fn(3);
console.log(a); // 1(全局变量不受影响)
编译阶段对函数 fn 的处理:
- 创建函数执行上下文
- 处理参数
a - 发现函数声明
function a(){}并提升 - 处理变量声明
var a和var b
执行过程中,函数参数、函数声明和变量声明的优先级为:函数声明 > 参数 > 变量声明。
六、数据类型的存储方式
JavaScript 中不同数据类型的存储方式不同:
// 7.js
let str = 'hello'; // 简单数据类型
let str2 = str; // 值的拷贝
str2 = '你好';
console.log(str, str2); // hello 你好
let obj = { // 复杂数据类型
name: '张三',
age: 18
}
let obj2 = obj; // 引用式拷贝(复制地址)
obj2.age++;
console.log(obj2, obj); // 两者的age都是19
存储机制:
- 简单数据类型(字符串、数字等)存储在栈内存中,赋值时进行值拷贝
- 复杂数据类型(对象、数组等)存储在堆内存中,变量仅保存引用地址,赋值时复制地址
总结
JavaScript 执行机制的核心要点:
- 代码执行分为编译和执行两个阶段,交替进行
- 编译阶段会进行变量提升和函数提升,为执行做准备
- 调用栈管理执行上下文的创建和销毁
var与let/const在提升行为、重复声明等方面有显著区别- 函数声明提升优先级高于变量提升,函数表达式不提升
- 不同数据类型采用不同的存储方式,影响赋值行为
理解这些机制有助于我们编写更可预测的代码,避免因执行顺序与编写顺序不同而产生的 bugs。