预编译及变量提升
变量提升的概念:
// 变量提升:将【当前作用域】下所有声明的变量;提升到作用域最顶端
// 变量提升:声明提升,赋值不提升。
函数提升的概念:
// 将函数本体提升到作用域最顶端
// 将所有变量提升当前作用域最顶端
// 当函数名与变量名重复时候,变量赋值,会将函数本体覆盖
众所周知javascript是解释性语言,主要特点为解释一行执行一行。
而在js运行时会进行三件事:1语法分析 2.预编译 3.解释执行
语法分析会在代码执行前对代码进行通篇检查,以排除一些低级错误
预编译发生在代码执行的前一刻
解释执行顾名思义就是执行代码
我先给大家举几个预编译的小例子:
var a = 123;
console.log(a);
此时他返回的值会是123;
但如果我们调换位置:
console.log(a);
var a = 123;
我们得到的结果便会是undefined。(由于js解释性语言的原因,先执行console.log,而由于预编译的原因浏览器并不会报错)
如果我们在次尝试不定义变量直接获取:
console.log(a); 此时我们会发现浏览器会进行报错。//Uncaught ReferenceError: a is not defined
这条语句提示我们a没有定义
到这里有些人会有疑问为什么在console.log前,后和不定义a会有如此大的差别,这就是预编译起到的作用
预编译发生在函数执行的前一刻,过程如下:
创建AO对象,执行期上下文(后面更新关于执行期上下文详解)。
寻找函数的形参和变量声明,将变量和形参名作为AO对象的属性名,值设定为undefined.
将形参和实参相统一,即更改形参后的undefined为具体的形参值。
寻找函数中的函数声明,将函数名作为AO属性名,值为函数体。
实例
我们先来看下面这段代码:
function fn(a){
console.log(a);
var a = 123;
console.log(a);
function a(){};
console.log(a);
var b = function(){};
console.log(b);
function d(){};
}
//调用函数
fn(1);
创建AO对象
AO{
//空对象
}
//复制代码
//找形参和变量声明
AO{
a : undefined,
b : undefined
}
//复制代码
//形参和实参相统一
AO{
a : 1,
b : undefined
}
//复制代码
//找函数声明
AO{
a : function a(){},
b : undefined,
d : function d(){}
}
//复制代码预编译环节就此结束,此时的AO对象已经更新为:
AO{
a : function a(){},
b : undefined,
d : function d(){}
}
//复制代码函数开始逐行顺序执行:
function fn(a){
console.log(a);// 输出functiona(){}
var a = 123;//执行到这里重新对a赋,AO对象再一次更新
console.log(a);// 输出123
function a(){};//预编译环节已经进行了变量提升,故执行时不在看这行代码
console.log(a);// 输出123
var b = function(){};//这个是函数表达式不是函数声明,故不能提升,会对AO中的b重新赋值
console.log(b);//输出function(){}
function d(){};
}
//复制代码至此,函数执行完毕,销毁AO对象。
//我们再来看几个例子,熟悉函数的预编译过程。
//示例一:
function test (a,b){
console.log(a);
c = 0;
var c;
a = 3;
b = 2;
console.log(b);
function b(){};
function d(){};
console.log(b);
}
//调用函数
test(1);
//复制代码它的AO创建过程如下(此处省略创建空AO对象的部分,下文同):
AO1{
a : undefined,
b : undefined,
c : undefined
}
AO2{
a : 1,
b : undefined,
c : undefined
}
AO3{
a : 1,
b : function b(){},
c : undefined,
d : function d(){}
}
//复制代码至此预编译环节完成,开始执行:
function test (a,b){
console.log(a); //输出1
c = 0; //给AO对象中的c重新赋值0
var c;//预编译环节变量提升,不再读此行代码
a = 3;//给AO对象中的a重新赋值3
b = 2;//给AO对象中的b重新赋值2
console.log(b);//输出2
function b(){};//预编译环节变量提升,执行时不再读这行代码
function d(){};//预编译环节变量提升,执行时不再读这行代码
console.log(b);//输出2
}
//调用函数
test(1);
示例
GO2{
b : 123;
}
AO2{
a : 123;
}
//复制代码示例三 :
console.log(test);
function test(test){
console.log(test);
var test = 234;
console.log(test);
function test(){};
}
test(1);
var test = 123;
//复制代码我们来看它的预编译过程:
//执行前(页面加载完成时)生成GO对象
GO1{
test : undefined
}
GO2{
test : function(){}
}
//输出 function test(){…}
//执行test()前生成它的AO对象
AO1{
test : undefined
}
AO2{
test : 1
}
AO3{
test : function test(){}
}
//预编译结束开始执行test(1);
AO4{
test : 234
}
//输出234
//复制代码示例四:
function demo(){
console.log(b);
if(a){
var b = 100;
}
console.log(b);
c = 234;
console.log©;
}
var a;
demo();
a = 10;
console.log©;
//复制代码我们来看它的预编译过程:
//首先是全局对象GO
GO1{
a : undefined
}
G02{
a : undefined,
demo : function demo(){}
}
//执行demo()前预编译,由于demo中的c未声明就使用故为全局对象
//输出undefined
GO3{
a : undefined,
demo : function demo(){}
c : undefined
}
//此时a还是undefined,故不执行if()代码块
//输出还是undefined
GO4{
a : undefined,
demo : function demo(){}
c : 234;
}
//输出234
GO5{
a : 10,
demo : function demo(){}
c : 234;
}
//输出234