预编译(因为没有涉及到作用域的知识, 所以在比喻方面不会那么的精准, 在写作用域的时候会完善一部分)
预编译前奏
- imply global 暗示全局变量: 即任何变量, 未经声明就赋值, 则此变量归window(全局对象所有)
var a = 100;
function foo() {
b = 50;
}
foo();
console.log(a, b); // 输出100, 50 因为b未经声明就使用, 即使他在函数作用域里, 系统也会给他拎到全局去
- 一切声明的全局变量, 都是window的属性
var a = 100;
var b = 50;
function foo() {
c = 20;
}
foo();
console.log(window.a, window.b, window.c); //100 50 20
预编译四部曲
- 创建AO对象
- 找形参和变量声明, 将形参和变量声明作为AO的属性名放进AO对象, 值为undefined
- 将实参值和形参值相统一
- 在函数体内找函数声明, 值赋予函数体
我们先来看个实例
function fn(a) {
console.log(a); //输出function a() {}
var a = 123;
console.log(a); //输出123
function a() {}
console.log(a); //输出123
var b = function() {}
console.log(b); //输出function b() {}
function d() {}
}
fn(1);
在上面这个实例中, 变量名重复这么多, 那这不是你弄死我就是我弄死你, 这个到底是怎么操作的呢, 我们需要明确一个道理, 预编译发生在函数执行的前一刻, 也就是说, 当函数正式执行的时候, 预编译已经帮函数把这些重名都调和好了
按照我们所说的预编译四部曲, 那么当遇到上面的函数时候, 咱们来按照流程走一次
tip: 下面的例子都只是帮助大家好理解, 而在真实的底层中,AO是存在于作用域链中的并且同时存在的还会有GO,包括下方的GO实例也只是帮助大家好理解, 而真正的存储结构并不是那样
- 第一步创建AO对象(全称Activity Object, 活跃对象, 简单来说就是我们理解的作用域, 其实人家叫执行期上下文)
// 也就是说, 预编译一开始这个AO就搁这杵着了
AO: {
}
// 这个时候这个AO对象里啥都没有
- 第二步找函数中的形参和变量声明, 将形参和变量声明作为AO的属性, 值为undefined
// 这个函数中形参是a, 变量声明有两个吧 var a 和 var b, 所以这个时候AO中的值为
AO: {
a: undefined,
b: undefined
}
- 第三步将形参和实参值相统一
// 这个函数里形参是a, 实参是1, 所以这个时候AO对象为
AO: {
a: 1,
b: undefined
}
- 第四部在函数体内找函数声明挂进AO对象, 值被赋予函数体
//所以这个时候AO对象为
AO: {
a: function() {},
b: undefined,
d: function() {}
}
走完上面四步流程以后, 函数正式开始执行,而之后的console也就是在访问这个AO对象的值
-
所以当我们执行函数体内的第一句console.log(a)的时候这个时候系统会去AO中找a, 而这时候的a是function, 所以铁定输出function
-
走到第二句var a = 123的时候这个时候var已经可以忽略了, 预编译已经帮我们var过了, 所以我们可以理解为a = 123
-
然后走第三句的打印a这个时候a已经被赋值为123, 所以铁定输出123
-
然后这时候b被赋值了一个函数, 所以打印b为一个函数体
这就是上面那题为啥会输出这样结果的原因,希望我给你说明白了, 没说明白的话也没事, 后面会有很多的例子来让小伙伴加深印象
一些关于预编译的实例
实例1
function test(a, b) {
console.log(a); //1
c = 0;
var c;
a = 3;
b = 2;
console.log(b); // 2
function b() {}
function d() {}
console.log(b); // 2
}
test(1);
同理, 我们先来看看都发生了什么
- 首先, 当函数运行前一刻, 预编译四部曲开始, 先创建一个AO对象, 内容为空
AO: {
}
- 然后找形参和变量声明, 将他们作为AO的属性, 值为undefined
// 在这个test函数中形参为a, b 变量声明有var c; 所以这个时候AO对象中内容为
AO: {
a: undefined,
b: undefined,
c: undefined
}
- 形参实参相统一
// 由于实参只有1, 所以会对应到形参的a
AO: {
a: 1,
b: undefined,
c: undefined
}
- 最后在函数体内找函数声明, 函数声明的值会被马上确定为函数体
// 这个函数中的函数声明有b, d同时他们的值会被马上确定为函数体放进AO
AO: {
a: 1,
b: function() {},
c: undefined,
d: function() {}
}
至此预编译结束, 函数开始正式按流程执行,只是再遇到变量声明和函数声明的时候不会理会, 所以结局输出1 2 2也是理所当然了-
实例2
function test(a, b) {
console.log(a); // function() {}
console.log(b); // undefined
var b = 234;
console.log(b); // 234
a = 123;
console.log(a); // 123
function a() {};
var a;
b = 234;
var b = function() {}
console.log(a); // 123
console.log(b); // function() {}
}
test(1);
// 流程一样按照上面预编译四部曲走, 错不了
我们需要明确一点, 在全局中也会进行预编译四部曲, 而创建的对象叫做GO, 同时GO === window
console.log(a);
var a = 10;
function a() {}
function b() {
console.log(c);
var c;
c = 100;
}
b();
在上面的例子中, 全局中会先预编译四部曲, 结果是
GO: {
a: function a() {},
// 同时我们知道b在执行前一刻也会产生AO
b: {
value: function b() {},
AO: {
c: undefined
}
}
}
实例3
a = 100;
function demo() {
function e() {}
arguments[0] = 2;
console.log(e); // function() {}
if(a) {
var b = 123;
function c() {}
}
var c;
a = 10;
var a;
console.log(b); // undefined
f = 123;
console.log(c); // undefined
console.log(a); // 10
}
var a;
demo(1);
console.log(a); // 100
console.log(f); // 123
// 按照预编译流程走, 这种难度的题目其实也不在话下了