预编译(Hoisting)是指在代码执行之前,JavaScript 引擎会将变量和函数的声明提升到当前作用域的顶部。
这意味着在代码中,变量和函数的声明可能在实际代码执行之前 已经被处理了。(提升优先级:函数>变量)
变量提升规则
这里要提前讲清楚变量提升的规则
x=5
var x
console.log(x)//输出5console.log(global)
global=100
var global
//输出undefined
使用 var 声明的变量会存在声明提升 ,但是在声明具体的值之前访问变量,但其值为 undefined。
console.log(v1);var v1 = 100;function foo() {console.log(v1);var v1 = 200;console.log(v1);}foo();console.log(v1);
概况
- 在进入执行环境(函数)时,就会创建一个新的变量环境,用来存储该执行环境中的所有变量和函数。
- 变量环境中的变量和函数会进行预处理(变量提升)并赋予初始值(undefined)。
- 在代码执行阶段,变量会被赋予实际值。
预编译全局三部曲
1. 创建GO对象(Global Object):
当编译器开始编译整个js代码时,首先会创建一个执行上下文对象GO对象(Global Object)
2. 找变量声明
将变量声明作为GO对象的一个属性名,其值初始化为undefined
3. 在全局找函数声明
将函数名作为Go对象的属性名,其值初始化为该函数体
举个例子
global=100
function fn(){console.log(global)global=200console.log(global)var global=300
}
fn()
var global1:创建go对象-->go{ }
2.寻找变量声明不看fn()方法内部,目前之前有 var global 这一个声明go{global:undefined}
3.寻找函数声明
存在 function fn(){ ....}函数声明go{global:undefinedfn: fn函数体}
最后
go{global:100fn: fn函数体}
全局预编译完成,引擎开始执行代码:将100赋值给global,执行函数fn,引擎便开始让编译器编译该函数内部代码
执行函数内部的编译
1.创建AO对象(action object)
编译器编译一个函数时都会为函数创建一个AO(action object)上下文执行对象
2.找形参和变量声明
将变量声明和形参的作为AO的属性名,值为undefined
3.将形参和和实参的值统一
将传入实参的值赋给形参
4.找函数声明
在函数体内寻找函数声明,将函数名作为AO对象的属性名,值赋予函数体
继续这个例子
global=100
function fn(){console.log(global)global=200console.log(global)var global=300
}
fn()
var global1:通过fn的代码创建AO对象AO{ }
2:寻找形参和变量声明作为AO的属性值,先初始为undefined
此时存在一个 var globalAO{global:undefined}
3:将传入实参的值赋给形参,没有形参也没有传入的实参
4:寻找内部函数声明
此时完成完整的代码执行过程:
global=100
function fn(){console.log(global)//函数内有变量声明,//但此时没有赋值,打印undefinedglobal=200//global被赋值为200console.log(global)//输出200var global=300//global被赋值为300
}
fn()
var global最终输出:undefined,200
调用栈
这个概念在闭包也会提到
1. 执行代码先入栈GO执行对象 ,再入栈AO执行对象
2. 调用栈指针 控制了执行顺序,function中找不到变量声明会指向GO对象中再去找。
- GO对象先入栈,执行后遇到函数,则创造AO对象入栈
- 调用栈指针指向AO对象,引擎开始执行AO对象,
- 对函数内执行赋值语句,引擎便会开始在AO对象的环境内寻找该变量声明,并按照先词法环境后变量环境的顺序寻找。
- 如果没有,调用栈指针便会指向GO对象在该对象的环境内寻找。
所以说是引擎在为函数内部变量赋值时寻找不到变量声明为会往外找(来到全局作用域)
global=100
function fn(){//内部没有变量声明console.log(global)global=200console.log(global)
}
fn()
var global //100,200
例题:c=1直接创造全局变量
g=4
function foo(a){console.log(a)//输出1c=0//创建全局变量var ca=3//如果直接对一个变量赋值而没有使用关键字 var、let 或 const 来声明该变量,这个变量就会变成全局变量。b=2//被覆盖为2console.log(b)//输出2function b(){}function d(){}//虽然在后面有一个同名的函数声明 function b(){},但函数声明并不会覆盖变量的赋值操作console.log(b)//输出2console.log(g,"g")}foo(1)
例题:function 函数提升
function fn(a){console.log(b)//这段代码在函数体中的位置比变量 b 的声明更靠前,因此输出 undefined。//如果下面的b改成:function b(){},则会输出 function b(){}console.log(a);//输出function a(){}//使用 function 关键字声明的函数会进行函数提升(function hoisting),这意味着函数声明会被提升到当前作用域的顶部var a=123console.log(a);//输出123function a(){}//函数声明console.log(a);//输出123var b=function(){}console.log(b);//输出function() b{}var d=aconsole.log(d);//输出123}
fn(1)