• 语法分析:

    对于语法分析的话,大概过一下就可以了,比如:是否少写个括号,是否有中文等等一系列的问题。JavaScript解释器会全篇扫描一下,检查是否有错误,但是不会执行。

    预编译:

    这个就比较有意思了,也是本篇博客的重点,预编译的话主要分两种吧!一种是函数体里面的预编译(AO),另一种是全局环境的预编译(GO),不懂?没有关系,我会慢慢讲到。

    解释执行:

    对于解释执行执行的话,我想我也可以不用多说吧!就是在执行的时候解释一行执行一行呗!

    对于预编译这个概念其实我们有遇到过,只是我们不知道它的专业名词叫做预编译

    纳尼?不信我,好吧!我们看下是否遇到过,先简单举个例子来证明一下我的观点

    test();
    function test(){
        console.log('a');
    }
    console.log(a);
    var a=123;

    提问:输出什么?

    相信大家都可以做出来,其实这里面就已经包含预编译的阶段,最开始讲的时候就说了,预编译发生在函数执行的前一刻,所以在函数调用的时候就已经有预编译这一个阶段了。

    好的,我们再来看下另外一个示例

    test();
    function test(){
     console.log('a');
    }
    test();
    console.log(a);
    var a=123;

    对于如此简单的两道题目,相信在座的各位没有做不出来的吧!

    路人甲:“杨戬哥,这两道题目好简单(Low)呀!”

    杨戬:”是,是很简单,但是你们知道是什么原理吗?为什么第一题中输出a和undefined,对于a的话相信大家都知道,但是undefined输出是为什么?大家是否有考虑过。“

    路人乙:”杨戬哥,我们老师讲过两句比较有用的话“

    路人丙:”这两句话可以解决很多问题,我在碰到一些问题的时候,就是套用这两句话。“

    杨戬:”路人丙弟弟,你的这两句话确实可以解决很多问题,但是有些问题靠这两句话是解决不了的“

    路人丙:”杨戬哥,我不信“

    杨戬:”好吧!,既然你不信,那我就出道题考考你。“

    路人丙:”come on“

    function foo(a){
     console.log(a);
     var a=123;
     console.log(a);
     function a(){}
     console.log(a);
     var b=function(){}
     console.log(b);
     function d(){}
    }
    foo(1);

    杨戬:”提问console.log()都输出是什么?“

    路人丙:”这、这、这,还有这操作“

    路人丙:”算了,我还是老老实实听杨戬讲吧!,不装逼了“

    我先公布以下答案吧!

    但是到这里我还是没有那么快讲解预编译,考虑了一下,讲解之前还是需要铺垫一点东西,否则很难讲清楚。

    预编译前期

    预编译前期主要讲解两个东西

    例一

    var a=123;
    console.log(a);
    function test(){
        var a=b=123;
    }
    test();
    console.log(window.a);
    console.log(window.b);

    依次输出undefined、123

    看第一条,即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有

    a变量我们是已经声明了,但是b变量我们并没有声明,此时就进行赋值,所以归全局对象所有

    例二

      var b=123;
     console.log(b);
        console.log(window.b);

    此时b===window.b

    第二条,一切声明的全局变量都是window属性

    讲解的不是那么透彻,等讲完全局预编译的时候再回过头来看,你就会有一种醍醐灌顶的感觉

    预编译(执行期上下文)

    预编译在这里我主要分两种,一种是函数预编译(函数执行期上下文)和全局预编译(全局执行期上下文)

    函数预编译(函数执行期上下文)

    函数预编译我给大家总结了四条规律,无论什么样的,都能够正确的避坑

    根据这四条法则我们回到最开始的题目进行讲解

    例一

        function foo(a){
            console.log(a);
            var a=123;
            console.log(a);
            function a(){}
            console.log(a);
            var b=function(){}
            console.log(b);
            function d(){}
        }
        foo(1);

        /***
         * 1.创建AO对象
         * AO{
         *
         * }
         * 2.找形参和变量声明,将变量的形参名作为AO对象的属性名,值为undefined
         * AO{
         *     a:undefined
         *     b:undefined
         * }
         * 注:由于形参a和变量声明a相同,取其中一个即可
         *
         * 3.将实参值和形参进行统一
         * AO{
         *     a:undefined=>a:1,
         *     b:undefined
         * }
         *
         * 4.在函数体里面找函数声明,值赋予函数体
         *
         * AO{
         *     a:1=>function a(){},
         *     b:undefined,
         *     d:function d(){}
         * }
         * 此时预编译结束,开始函数执行
         */


    根据步骤分析

    1、创建AO对象

    2、找形参和变量声明,将变量的形参名作为AO的属性名,值为undefined

    形参:a

    变量声明:a,b

    由于形参和变量声明都是同一个,所以肯定出现覆盖的情况,取其中一个就可以了,此时AO对象中含有两个属性

    AO{
     a:undefined,
     b:undefined,
    }

    3、将实参值和形参进行统一

    实参值:1

    AO{
     a:1,
     b:undefined
    }

    此时的a从undefined变成1

    4、在函数体里面找函数声明,值赋予函数体

    函数声明:function a(){},function d(){}

    AO{
     a:function a(){},
        b:undefined,
        d:function d(){}    
        
    }

    此时预编译结束,函数开始执行,解释一行执行一行

    第一个输出function a(){},到了第二个的时候,var a已经提升了,但是a=123没有调用,所以第二个输出123

    第三个的时候,function a(){}已经进行提升了,所以这一行代码不要看,直接输出123,同理b=function (){}赋值也一样。

    例二

        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);
        /***
         * 1.创建AO对象
         * AO{
         *
         * }
         * 2.找形参和变量声明,将变量的形参名作为AO的属性名,值为undefined
         *  AO{
         *     a:undefined,
         *     b:undefined,
         *     c:undefined,
         *  }
         * 3.将实参值和形参进行统一
         *  AO{
         *      a:undefined=>a:1,
         *      b:undefined,
         *      c:undefined
         *  }
         *  4.在函数体里面找函数声明,值赋予函数体
         *  AO{
         *      a:1,
         *      b:undefined=>function b(){},
         *      c:undefined,
         *      d:function d(){}
         *  }
         *  预编译阶段结束,函数开始执行
         *
         */


    1、创建AO对象

    2、找形参和变量声明,将变量的形参名作为AO对象的属性名,值为undefined

    形参:a,b

    变量声明:c

    AO{
     a:undefined,
        b:undefined,
        c:undefined,
    }

    3、将实参值和形参进行统一

    实参值:1

    AO{
        a:1,
        b:undefined,
        c:undefined,
    }

    4、在函数体里面找函数声明,值赋予函数体

    函数声明:function b(){},function d(){}

    AO{
        a:1,
        b:function b(){},
        c:undefined,
        d:functiond(){}
    }

    预编译阶段结束,函数开始执行

    注意:函数执行上下文发生在函数执行的前一刻

    全局预编译(全局执行期上下文)

    to be honest,全局预编译和函数预编译都差不多,少了中间的第三步,将实参值和形参进行统一

    函数预编译发生在函数执行的前一刻,而全局预编译发生在script标签创建的时候,可以看作script是一个大的function。

    全局执行上下文和函数执行上下文同时使用才是真香,一起来看如下两个示例

    例一

        console.log(test);
        function test(test{
            console.log(test);
            var test=234;
            console.log(test)
            function test({}
        }
        test(1);
        var test=123;

        /***
         * 1.创建GO对象
         * GO{
         *
         * }
         * 2.找变量声明,值为undefined
         * GO{
         *     test:undefined
         * }
         * 3.找函数声明,值赋予函数体
         * GO{
         *     test:undefined=>function test(){....此处省略},
         * }
         * 全局预编译结束,开始执行代码
         *
         */


        /***
         * 1.创建AO对象
         * AO{
         *
         * }
         * 2.找形参和变量声明,将形参的变量名作为AO对象的属性名,值为undefined,
         *  AO{
         *     test:undefined,
         *  }
         *  3.将实参值和形参值统一
         *  AO{
         *      test:undefined=>test:1,
         *  }
         *  4.在函数体里面找函数声明,值赋予函数体
         *  AO{
         *      test:1=>test:function test(){}
         *  }
         *  函数预编译结束,开始执行代码
         *
         *
         *
         */


    一、全局预编译

    1、创建GO对象

    GO{

    }

    2、找变量声明,值为undefined

    变量声明:test

    GO{
     test:undefined
    }

    3、找函数声明,值赋予函数体

    函数声明:function test(test){....}

    GO{
        test:function test(test){....}
    }

    全局预编译结束,开始执行代码,进入函数预编译

    二、函数预编译

    1、创建AO对象

    AO{
        
    }

    2、找形参和变量声明,将形参的变量名作为AO的属性名,值为undefined

    形参:test

    变量声明:test

    AO{
        test:undefined
    }

    3、将实参值和形参统一

    实参值:1

    AO{
        test:1
    }

    4、在函数体里找函数声明,值赋予函数体

    函数声明:function test(){}

    AO{
        test:function test(){}
    }

    函数预编译结束,开始执行代码

    这里需要多分析全局预编译,还是万变不离其中

    例二

        global=100;
        function fn({
            console.log(global);
            global=200;
            console.log(global);
            var global=300;
        }
        fn();
        var global;

        /***
         * 1.创建GO对象
         * GO{
         *
         * }
         * 2.找变量声明,值为undefined
         * GO{
         *     global:undefined
         * }
         * 3.找函数声明,值赋予函数体
         * GO{
         *     global:undefined,
         *     fn:function fn(){...}
         * }
         *
         * 全局预编译结束,执行代码,进入函数预编译
         */


        /***
         *1.创建AO对象
         * AO{
         *
         * }
         * 2.找形参和变量声明,将形参的变量名作为AO的属性名,值为undefined
         *  AO{
         *      global:undefined
         *  }
         * 3.将实参值和形参统一
         *  AO{
         *      global:undefined
         *  }
         * 4.在函数体里面找函数声明,值赋予函数体
         * AO{
         *     global:undefined
         * }
         *函数预编译结束,开始执行代码
         *
         */

    一、全局预编译

    1、创建GO对象

    GO{
        
    }

    2、找变量声明,值为undefined

    变量声明:global

    GO{
        global:undefined
    }

    3、找函数声明,值赋予函数体

    GO{
        global:undefined,
        fn:function fn(){.....}
    }

    全局预编译完成,执行代码,进入函数预编译

    二、函数预编译

    1、创建AO对象

    AO{
        
    }

    2、找形参和变量声明,将形参的变量名作为AO对象的属性名,值为undefined

    形参:没有

    变量声明:global

    AO{
        global:undefined
    }

    3、将实参值和形参统一

    实参值:没有

    AO{
        global:undefined
    }

    4、在函数体里找函数声明,值赋予函数体

    函数声明:没有

    AO{
        global:undefined
    }

    函数预编译完成,执行代码

    在这里涉及一点点作用域和作用域链的知识,就近原则,自己有就用自己的,自己没有就看下GO里面是否有,如果都没有就是undefined,

    函数执行的时候,自己里面有global,所以就用自己的。

    现在回过头来看现在这两句话

    可以确认的是GO就是window,从window里面取值就是从GO里面取值。

    经典题目

    笔者找了一道非常有意思的题目,自己按照步骤也做错了,所以在这里分享一下给大家

        a=100;
        function demo(e{
            function e({}
            arguments[0]=2;
            console.log(e);
            if(a){
                var b=123;
                function c({}
            }
            var c;
            a=10;
            var a;
            console.log(b);
            f=123;
            console.log(c);
            console.log(a);
        }
        var a;
        demo(1);
        console.log(a);
        console.log(f);
        /***
         * 1.创建GO对象
         * GO{
         *
         * }
         * 2.着变量声明,值为undefined
         * GO{
         *     a:undefined
         * }
         * 3.找函数声明,值赋予函数体
         * GO{
         *     a:undefined,
         *     demo:function demo(e){...}
         * }
         * 全局预编译完成,执行代码进入函数预编译
         *
         */

        /***
         *1.创建AO对象
         * AO{
         *
         * }
         * 2.找形参和变量声明,将形参的变量名作为AO对象的属性名,值为undefined
         *  AO{
         *      e:undefined,
         *      a:undefined,
         *      b:undefined,
         *      c:undefined,
         *
         *  }
         *  3.将实参值和新参统一
         *  AO{
         *      e:1,
         *      a:undefined,
         *      b:undefined,
         *      c:undefined,
         *  }
         *  4.在函数体里面找函数声明,值赋予函数体
         *  AO{
         *      e:function e(){},
         *      a:undefined,
         *      b:undefined,
         *      c:function c(){}
         *  }
         *  函数预编译完成,执行代码
         */

    一、全局预编译

    1、创建GO对象

    GO{

    }

    2、找变量声明,值为undefined

    变量声明:a

    GO{
        a:undefined
    }

    3、找函数声明,值赋予函数体

    函数声明:function demo(e){...}

    GO{
        a:undefined,
        demo:function demo(e){...}
    }

    全局预编译完成,执行代码,进入函数预编译

    二、函数预编译

    1、创建AO对象

    AO{
        
    }

    2、找形参和变量声明,将形参的变量名作为AO对象的属性名,值为undefined

    形参:e

    变量声明:a,b,c

    AO{
        e:undefined,
        a:undefined,
        b:undefined,
        c:undefined
    }

    3、将实参值和形参统一

    实参值:1

    形参:e

    AO{
        e:1,
        a:undefined,
        b:undefined,
        c:undefined
    }

    4、在函数体里面找函数声明,值赋予函数体

    函数声明:function e(){},function c(){}

    AO{
        e:function e(){},
        a:undefined
        b:undefined
        c:function c(){}
    }

    函数预编译完成,执行代码

    理想答案

    实际答案

    由于谷歌浏览器的新规定,不能在if语句里面定义函数,所以,function c(){}无法提升

    致谢读者

    看到这里的读者都是最帅的,最美的,本篇是我尝试写文章的新做法,第一次搞这种类型的文章,因为希望自己的文章阅读起来可以没有那么枯燥(可以从中获取更多的快乐),学习是一件特别痛苦的事情,看文章也是,我也希望自己的文章可以更加的生动、幽默。让看文章的你既可以学到东西也不会那么枯燥。

    如果您有更好的建议,请在下方留下您宝贵的评论。

    结尾

    如果觉得本篇文章对您有用的话,可以麻烦您帮忙点亮那个点赞按钮吗?JS进阶系列-JS执行期上下文(一)-LMLPHP

    对于二郎神杨戬这个暖男来说:真的真的非常有用,您的支持将是我继续写文章前进的动力,我们下篇文章见。

    10-02 18:56