详解JS预解析原理
本文深入解析JavaScript引擎在执行代码前的预解析过程。详细对比var声明与function定义的预解析差异,阐述栈内存与堆内存的存储机制,并探讨条件语句、匿名函数及自执行函数对预解析的影响,帮助开发者理解变量提升与闭包原理。
预解析的不同机制
预解析,亦称预声明,是指在代码正式执行前,JavaScript引擎对变量和函数声明进行的提前处理。需要注意的是,变量(var)与函数(function)在预解析阶段遵循两套完全不同的处理机制。
- 当浏览器加载HTML页面时,首先会构建一个供JavaScript代码运行的环境,即全局作用域(global)。在浏览器环境中,这个全局作用域通常对应于window对象,也被称为顶级作用域。
- JavaScript中的内存空间主要划分为两种:栈内存(Stack)和堆内存(Heap)。
- 栈内存用于提供JavaScript代码的执行环境,并存储基本数据类型的值。无论是全局作用域还是函数内部的私有作用域,其执行环境均建立在栈内存之上。
- 堆内存主要用于存储引用数据类型的值。对于对象而言,其属性名和属性值会被存入堆内存;对于函数而言,函数体内的代码会被作为字符串存储在堆内存中。
- 在当前作用域内,JavaScript代码执行之前,浏览器会默认扫描所有带有var关键字的变量和function关键字的函数,并对其进行提前声明或定义,这一过程被称为“预解析”,也常被称为变量提升。
var关键字的预解析机制
以代码var a=1为例进行说明。
- 1. 在代码运行之前,引擎会扫描是否存在带var关键字的变量名。如果存在,则在内存中为该变量名开辟空间。此时,变量名a并不代表具体数值,其默认值为undefined。undefined是一个标识符,表示该变量名尚未关联到具体数据,即处于未定义状态。这一阶段称为变量的声明。
- 2. 当代码实际运行时,引擎会为数据1开辟新的内存空间。
- 3. 随后,将数据1与变量名a进行绑定。这里需要区分“变量类型”与“数据类型”。按照JavaScript语言原理,变量可能存在undefined类型;但作为数据类型,并不存在名为undefined的数据类型,只有字符串“undefined”;同理,不存在null数据类型,但存在字符串“null”。
var num;
//1、声明(declare):var num; ->告诉浏览器在当前作用域中有一个num的变量了,如果一个变量只是声明了但是没有赋值,默认的值是undefined
console.log(num);//->undefined
num = 12;
//2、定义(defined):num=12; ->给我们的变量进行赋值
console.log(num);//->12
//变量提前使用的话,就是undefined
console.log(testStr);//undefined
var testStr="22222222"
function关键字的预解析步骤
以函数定义function fn(){……}为例。
在代码执行之前,引擎会扫描所有带function关键字的脚本,进行变量定义的同时直接完成赋值操作。
- 1. 函数定义仅保存相关字符串信息。在预解析阶段,内存中会保存
fn大括号内的代码字符串。 - 2. 当代码运行到
fn()时,函数开始执行。此时会开辟一个新的堆内存空间,将之前保存的字符串作为代码再次执行。在函数产生的作用域内,还会继续进行预解析和代码运行。
如果函数被多次执行,会产生多个独立的作用域。这些作用域内的内容相互独立,彼此之间没有直接关联(关于原型和原型链的详细原理将在后续章节深入研究)。
fn(100,200);//->可以在上面执行,因为预解释的时候声明+定义就已经完成了
function fn(num1, num2) {
var total = num1 + num2;
console.log(total);
}
总结
1. var和function关键字在预解析时的操作存在显著差异。
- var -> 在预解析阶段仅提前声明变量,赋值操作需等到代码实际执行时才完成。
- function -> 在预解析阶段同时完成声明和定义(即赋值)。因此,在代码执行阶段,若遇到已定义的函数声明,会直接跳过该部分代码。
2. 预解析仅发生在当前作用域内。例如,初始阶段仅对window全局作用域进行预解析;只有当函数被执行时,才会对函数内部的作用域进行预解析。
[重要] 初始阶段仅对window全局作用域进行预解析。由于fn函数内部目前仅存储字符串,其中的var total并无实际执行意义,因此不进行预解析。这印证了“预解析是发生在当前作用域下的”这一结论。
综合题;
console.log(obj);//->undefined
var obj = {name: "xie", age: 25};
function fn(num1, num2) {//代码执行到这一行的时候直接的跳过这一块的代码,因为在预解释的时候我们已经完成了声明加定义
var total = num1 + num2;
console.log(total);
}
var num1 = 12;
fn(num1, 100);//执行fn,把全局变量num1的值赋值给形参num1,把100赋值给形参num2
下面是一个预解析思路
var a,
b = 0,
fn = function () {
var a = b = 2;
};
fn();
console.log(a, b);
把上面解析成下面就好理解了
var a;
window.b = 0;
window.fn = function () {
//var a = b = 2;
var a = 2;//a是私有的和全局没关系
b = 2;//b是全局的
};
fn();//window.fn()
console.log(a, b);//undefined 2
预解析机制
- 1. 无论条件是否成立,都会进行预解析。
console.log(a);//->undefined
if (!!("a" in window)) {//"a" in window -> true
var a = "xie";
}
console.log(a);//->xie
例子中的if是不成立的,预解析的时候,碰到非functon内的var,都会声明,无论你写在if else 还是别的判断里; 假设if语句起作用的话,那么第一次log(a)的时候,就会报错了(没有声明的变量,是不能直接用的,除非typeof ),而声明并且没有赋值的表现才是undefined;假设不成立; 最开始总结的预解析步骤:代码运行之前,先扫描有没有带var关键字的变量名,有的话,为这个变量名,在内存里开一个空间;预解释是发生在代码执行前的,所以if根本阻挡不了预解析;
- 2. 预解析只发生在“=”的左边,仅对左边的变量名进行预解析,右边的值不参与预解析。
匿名函数之函数表达式:将函数定义部分作为值赋值给一个变量或元素的事件处理程序。
fn1();//->undefined() Uncaught TypeError: fn is not a function JS中只有函数可以执行 && JS上面的代码如果报错了,在不进行任何的特殊处理情况下我们下面的代码都不在执行了
var fn1 = function () {
console.log("ok");
};
fn1();
//预解释的时候:fn=xxxfff000
fn2();//->"ok"
function fn2() {
console.log("ok");
}
fn2();//->"ok"
预解析的时候:var fn1 = function()... ->fn的默认值是undefined;这里即使有function,也是不能进行预解释的
- 3. 函数体中return语句下方的代码不再执行,但这些代码仍需参与预解析。而return后面的内容被视为返回值进行处理,因此不进行预解析。
function fn() {
console.log(total);
return function sum() {};//return是把函数中的值返回到函数的外面,这里是把function对应的内存地址返回的到函数的外面,例如:return xxxfff111;函数体中return下面的代码都不在执行了
var total = 10;
console.log(total);
}
- 4. 匿名函数的function在全局作用域下不进行预解析;
匿名函数之自执行函数:定义与执行同时完成。函数内的声明仅在函数内部有效;
(function(num){
var testStr="test"+num;
console.log(num);
})(100);
console.log(testStr);// testStr is not defined
- 5. 在预解析时,如果遇到同名变量或函数,只声明一次。虽然不重复声明,但赋值操作仍会重复进行。
预解析:
var fn; 声明
fn = xxxfff000; [声明]不用了+定义
fn = xxxfff111; [声明]不用了+定义
// ->fn=xxxfff111
var fn = 12;//window.fn=12
function fn() {//window.fn=function(){}
}
JS中作用域只有两种:
- window全局作用域;
- 函数执行形成的私有作用域;
- {name:“”} if(){} for(){} while(){} switch(){} 这些都不会产生作用域;
ES6可以用let形成块级作用域;https://www.jb51.net/article/67732.htm
面试题
// 涉及this的指向和闭包
var num = 20;
var obj = {
num: 37,
fn: (function (num) {
this.num *= 3; // window.num * 3 = 60
// num += 15;
var num = 45;
return function () {
this.num *= 4;
num += 20; // 调用父作用域的num (45+20)
console.log(num);
};
})(num), //->把全局变量num的值20赋值给了自执行函数的形参,而不是obj下的30,如果想是obj下的30,我们需要写obj.num
};
var fn = obj.fn;
fn(); //->65 , 执行了第1次=> window.num = 240
obj.fn(); //->85 闭包(65+20) // 执行了第2次=> obj.num = 37*4 = 148
console.log(window.num, obj.num); // 240,148
以上就是详解JS预解析原理的详细内容,更多关于JS预解析原理的资料请关注本站其它相关文章!
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
