【JS】函数预编译(预解析 / 变量提升)

本文深入讲解JavaScript中的预解析概念,包括执行上下文的创建与执行阶段、变量与函数声明的预解析过程及其潜在隐患。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

执行上下文

在理解预解析之前,我们先了解下 执行上下文(Execution Context) 的概念

  • 每次当控制器转到可执行代码的时候,就会进入一个执行上下文。
  • 执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
  • 每一个函数执行时,都会给对应的函数创建这样一个执行环境
  • JavaScript中的运行环境大概包括三种情况。
1. 全局环境:JavaScript代码运行起来会首先进入该环境
2. 函数环境:当函数被调用执行时,会进入当前函数中执行代码
3. eval(不建议使用,可忽略)
  • 当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,因此在一个JavaScript程序中,必定会产生多个执行上下文
  • JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。

栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文

console.log("全局作用域")

function A() {
  console.log("函数A的作用域")
  function B() {
    console.log("函数B的作用域")
  }
  B();
}

A();
例如:一个全局作用域下有个函数A,函数A的作用域下又包含了个函数B

1. 一个程序的运行,首先进入的是全局环境(全局作用域),然后全局上下文就被放到栈的最下面

2. 当运行到函数A时,就会创建一个函数A正在执行的上下文,放到栈里面,因为全局已经放进栈里了,所有函数A会放到全局的上面,也就是栈顶

3. 当运行到函数A里面的函数B时,就会把函数B放进去,叠在函数A上,此时因为执行的是函数B,所有此时函数B是正在执行的上下文

4. 当函数B执行完后,就会从栈里面取出去,此时的执行上下文就是函数A,当函数A执行完后,被取出去了,全局就是此时的执行上下文(牵涉到 js 垃圾回收机制)

5. 全局上下文在浏览器窗口关闭后出栈。

总结

1. 执行上下文是单线程,类似于乒乓球筒,先放进去的,要把上面的都取出来才能拿到下面的

2. 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待(要用乒乓球,肯定是先取上面的)

3. 全局上下文只有唯一的一个,它在浏览器关闭时出栈

4. 函数的执行上下文的个数没有限制

5.  每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

预解析/预编译

当调用一个函数时(激活),一个新的执行上下文就会被创建。一个执行上下文的生命周期可以分为两个阶段。创建阶段和执行阶段,而预解析就发生在 创建阶段的声明变量对象 的时候,js的代码的执行机制就是先预解析,再执行代码

  • 创建阶段:执行上下文会分别创建变量对象建立作用域链,以及确定this指向
  • 执行阶段:完成变量赋值函数引用,以及执行其他代码

在这里插入图片描述

局部函数预解析(变量对象创建过程),分为4个阶段

function fn(a,c){
	console.log(a) 
	var a = 123
	console.log(a)
	console.log(c)
	function a(){}
	if(false){
		var d = 678
	}
	console.log(d)
	console.log(b)
	var b = function (){}
	console.log(b)
	function c(){}
	console.log(c)
}
fn(1,2)
/* 预编译分析 */
1. 创建变量对象(又称AO对象)
AO{

}
2. 找形参和变量的声明,作为AO对象的属性名,值为undefined
AO{
	a: undefined
	c: undefined
	d: undefined
	b: undefined
}
3. 实参与形参相统一
AO{
	a: 1
	c: 2
	d: undefined
	b: undefined
}
4. 找函数声明,会覆盖掉变量的声明
AO{
	a: a(){}
	c: c(){}
	d: undefined
	b: undefined
}

全局函数预解析(变量对象创建过程),分为3个阶段

fn();	// 能打印
var fn = 100;
fn();	// 报错:不是一个函数
function fn(){
	console.log("我是 fn 函数");
}
/* 预编译分析 */
5. 创建 GO( Grobal Object ) 对象
GO{

}
6. 找变量声明(包括函数表达式形式var fun = function(){}GO{
	fn: undefined
}
7. 找函数声明,函数会覆盖变量声明
GO{
	fn: fn(){}
}

函数参数(函数的形参)

function fn(b){	
	function b(){
		
	}
	b();
}
fn(200);
  • 真实的执行过程
function fn(b){	
/*
预解析先声明参数var b,并赋值,在声明函数b
*/	
	var b = 200;
	b();
	function b(){}
	b();
}
fn(200);

变量声明(变量和赋值式函数)

let/const声明的变量,仍然会提前被收集到变量对象中,但和var不同的是,let/const定义的变量,不会在这个时候给他赋值undefined

console.log(num);	// undefind
console.log(a); // 报错
let a = 10;
var num = 100;
  • 预解析会把 var 关键字定义的变量在代码执行之前先声明,但此时并赋值为undefind
console.log(num);	// undefind
var num = 100;
console.log(num);	// 100
  • 真实的执行过程
/*
预解析先声明var num,但此时并未赋值
*/
var num;
console.log(num);	// undefind
num = 100;
console.log(num);	// 100

注意

  • 赋值式函数:var fn = function(){}
  • 按照 var 的规则进行解析
fn();	// 报错
var fn = function fn(){
	console.log("我是fn函数");
}
  • 真实的执行过程
/*
	在预解析,只是告诉浏览器,fn 这个名字可以使用,但是没有赋值
*/
var fn;
fn()	// 报错
fn = function fn(){
	console.log("我是fn函数");
}
fn();

函数声明(声明式函数)

  • 在内存中先声明有一个变量名是函数名,并且这个名字代表的内容是一个函数
fn();	// 第一句
/*
预解析在所有代码执行之前,告诉浏览器,fn 这个名字可以使用,并且 fn 代表的是一个函数
*/

function fn(){
	console.log("我是fn函数");
}

  • 真实的执行过程
/*
	会把函数定义阶段提到调用阶段的上面
*/
function fn(){
	console.log("我是fn函数");
}
fn();	// 第一句

预解析隐患

if 里面的代码也会进行预解析

  • 这是你以为的:
console.log(num);	// undefined
if(false){
	var num = 100;
}
console.log(num);	// 100
  • 实际上:if 条件不管成不成立,里面的代码都会进行预解析
console.log(num);	// undefined
if(false){
	var num = 100;
}
console.log(num);	// undefined
  • 相当于:
console.log(num);	// undefined
if(false){
	var num;
	num = 100;
}
console.log(num);	// undefined

return 后面的代码也会进行预解析

  • return 后面的代码虽然不执行,但是也会进行预解析
function fn(){
	var num = 100;
	console.log(num);	// 100
	console.log(n);	// undefined
	return;
	var n = 200;
}
fn();
  • 相当于:
function fn(){
	var num;
	var n;
	num = 100;
	console.log(num);	// 100
	console.log(n);	// undefined
	return;
	n = 200;
}
fn();
  • 书写代码建议:
1. 函数名 不要与 变量同名
2. 变量名以名词为主,2个或3个单词组合使用
如:username,userInfoAge
3. 函数名以功能区分,尽量语义化
如:getColor(),setColor()
4. 尽量使用 赋值式函数 来定义

下一篇:函数作用域https://blog.youkuaiyun.com/qq_45677671/article/details/114988263

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值