关于JS预编译的一些探究(个人见解, 如果有纰漏和问题望指正)

预编译(因为没有涉及到作用域的知识, 所以在比喻方面不会那么的精准, 在写作用域的时候会完善一部分)

预编译前奏

  1. imply global 暗示全局变量: 即任何变量, 未经声明就赋值, 则此变量归window(全局对象所有)
var a = 100;
function foo() {
    b = 50;
}
foo();
console.log(a, b); // 输出100, 50 因为b未经声明就使用, 即使他在函数作用域里, 系统也会给他拎到全局去
  1. 一切声明的全局变量, 都是window的属性
var a = 100;
var b = 50;
function foo() {
    c = 20;
}
foo();
console.log(window.a, window.b, window.c); //100 50 20

预编译四部曲

  1. 创建AO对象
  2. 找形参和变量声明, 将形参和变量声明作为AO的属性名放进AO对象, 值为undefined
  3. 将实参值和形参值相统一
  4. 在函数体内找函数声明, 值赋予函数体

我们先来看个实例

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
// 按照预编译流程走, 这种难度的题目其实也不在话下了

关于预编译到此为止, Thanks for reading

发布了33 篇原创文章 · 获赞 11 · 访问量 2264

猜你喜欢

转载自blog.csdn.net/weixin_44238796/article/details/103358986