前言
一定要看完,千万别放收藏夹里吃灰
话不多说
先来看如下一段代码
console.log(a);
var a = 10;
我们都知道,JavaScript是解释性语言,解释一行执行一行,可是 a 变量的申明在后面一句,为什么输出不报错呢?
我们应该听说过JavaScript运行三部曲:
- 分析代码
- 预编译
- 执行语句
而这里的这个原因,就是和预编译相关了
既然如此,到底什么是预编译
预编译又称为预处理,是做些代码文本的替换工作。是整个编译过程的最先做的工作。 -------------百度百科
可能这么看有点难懂,没事,我们慢慢看下去
预编译前奏(铺垫)
- imply global 暗示全局变量:即任何变量,如果变量未经申明就赋值,此变量就为全局对象所有
- 一切申明的全局变量全是window的属性
我直接用代码来展示吧
//console.log(a); 单独这么一条语句肯定报错
a = 10;
console.log(a);
下面两句并未报错
同时控制台上输入window.a,输出的是10
我们说了,任何变量,如果变量未经申明就赋值,此变量就为全局对象所有,至于这个window就是全局对象
//上述代码可以这么理解
window{
a : 10;
}
//未申明的变量a赋值,则相当于为window对象增加了一个叫a的属性,属性值为10
一切申明的全局变量全是window的属性
这条就简单了,在全局范围内申明的变量,都是window的属性
var a = 111;
-->window.a = 111;
console.log(a) ==> console.log(window.a)
再来看一段代码
function fn() {
var a = b = 10;
}
fn();
console.log(window.b);
赋值的顺序是从右往左,所以顺序是:
- b = 10
- var a = b
那么对于b来说,b是一个未声明的变量,对他进行了赋值,那么它就属于全局对象window里的属性了
那么这一点就全都搞懂了
预编译详解
关于预编译,我们一定听过很多人说的
- 函数声明整体提升
- 变量 声明提升
这两句话,很好理解,虽然能解决大多数问题,但是,并不是我所愿的,就像下面这个问题,他并不能解决。
先来看一段代码
试着说出其输出结果
function f(a) {
console.log(a);
var a = 10;
console.log(a);
function a() {}
console.log(a);
var b = function () {}
console.log(b);
}
f(1);
我们通过这段代码来讲解预编译的相关知识
要解决这个问题,我们肯定要清楚,谁先提升到谁前面,谁又要覆盖谁
下面就是预编译的重点:
- 创建AO对象
- 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
我们按照步骤来
创建AO对象(Activation Object 执行期上下文)
AO{
}
找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
AO{
a : undefined, //a先是传入了形参,之后因为有一个变量 var a 所以现在传入的是 var a
b : undefined; //函数表达式也是申明的
//将其均赋值 undefined
}
将实参值和形参统一
AO{
a : 1, //a先是传入了形参,之后因为有一个变量 var a 所以现在传入的是 var a
b : undefined; //函数表达式也是申明的
//将其均赋值 undefined
}
在函数体里面找函数声明,值赋予函数体
AO{
a : function a () {}, //将函数体赋值给属性
b : undefined; //b是函数表达式,不是函数申明
}
预编译发生在执行函数之前
function f(a) {
console.log(a);
var a = 10;
console.log(a);
function a() {}
console.log(a);
var b = function () {}
console.log(b);
}
f(1);
第一句就是console.log(a)这时候我们上哪找这个a呢?
这个时候是去AO对象里面找的,所以输出的a是 函数a
第二、三句 var a = 10这一步其实是不完全执行,因为预编译的第二步已经找找到变量声明(变量 声明提升),将变量和形参名作为AO属性名,所以这一句执行的其实是 a = 10
这个时候,将AO对象的a的值修改为10,所以输出的是10
第四、五句 function a() {} 这句话在预编译第四步的时候提升上去了(函数声明整体提升) 所以就不看了,此时AO对象里面的a任然是 10 所以输出也是10
第六、七句虽然第六句和第二句类似,AO对象里b的值修改成是 函数b
所以也将输出函数b
最终输出如下
再来看一段代码练练手吧
function f(a,b) {
console.log(a);
console.log(b);
var b = 234;
console.log(b);
a = 123;
console.log(a);
function a(){}
var a ;
b = 345;
var b = function () {}
console.log(a);
console.log(b);
}
f(1);
结果
注意当函数退出的时候AO对象会被销毁
全局发生的预编译
预编译不止发生在函数体,还发生在全局
console.log(a);
var a = 1;
//输出undefined
console.log(a);
var a = 1;
function a() {}
//输出函数 a
全局的预编译和函数的预编译有点差别,它没有第三步,也就是说,他没有形参
同时它的第一步不是生成AO对象,是生成 GO对象(Global Object),其他的步骤都一样
看完应该就懂了吧,别再放收藏夹吃灰去了