点赞再看,年薪百万
本文已收录至https://github.com/likekk/-Blog欢迎大家star,共同进步。如果文章有出现错误的地方,欢迎大家指出。后期将在将GitHub上规划前端学习的路线和资源分享。

前言

距离写上一篇文章已经过去两个月了(上一篇文章是2020年07月17日写的),托更有点严重,一方面是这几个月项目在赶,另一方面是自己近两个月以来变懒了(为自己不想更新文章找借口),国庆假期,公司放八天假。

自己没有去回家,主要是回家有点远,从来这边读书到现在的国庆期间一直都没有回过家,这七八天本来也打算出去玩的(肯定需要安排时间出去玩),但是想想还是学点东西比较好。于是有了这篇博客。

写预编译(执行期上下文)这篇博客的初衷是因为一方面方便自己复习,另一方面是为了后续出闭包、作用域和作用域链、this指向等等的博客做一些前期的准备,以上几个知识点可以说是在基础中比较难以掌握的,也有可能会在一些面试中经常会问到的。

正文

首先在讲解预编译之前,我们先说下JavaScript的语言特点吧!说两点比较重要的(比较优秀的两点)

我们知道JavaScript语言首先是单线程的,其次是解释性语言。对于这两点我相信大家都不陌生,接着我们拓展开来,对于解释执行只是发生在执行的最后一步,解释执行之前还有两步,简单介绍一下JavaScript运行三部曲

  • 语法分析
  • 预编译(全局预编译阶段【创建GO对象】和函数预编译阶段【AO】)
  • 解释执行

「语法分析:」

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

「预编译:」

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

「解释执行:」

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

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

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

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

提问:输出什么?

答案: a 和 undefined

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

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

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

答案: a 、 a 和 undefined

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

路人甲:“杨戬哥,这两道题目好简单(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()都输出是什么?“

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

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

我先公布以下答案吧!

答案:function a(){}、123、123、function(){}

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

预编译前期

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

1、imply global 暗示全局变量,即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有

2、一切声明的全局变量都是window属性

例一

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属性

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

预编译(执行期上下文)

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

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

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

  • 创建AO对象
  • 找形参和变量声明,将变量的形参名作为AO的属性名,值为undefined
  • 将实参值和形参进行统一
  • 在函数体里面找函数声明,值赋予函数体

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

例一

    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(){}

注意:b是函数表达式,不要弄错了

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

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

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

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

答案依次是:function a(){},123,123,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(){}
}

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

答案:1,2,2

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

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

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

  • 创建GO对象
  • 找变量声明,值为undefined
  • 找函数声明,值赋予函数体

函数预编译发生在函数执行的前一刻,而全局预编译发生在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(){}
}

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

答案:function test(test){....},function test(){},234

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

例二

    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
}

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

答案:undefined,200

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

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

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

  • 1、imply global 暗示全局变量,即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有

  • 2、一切声明的全局变量都是window属性

可以确认的是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(){}
}

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

理想答案

2,undefined,function c(){},10,100,123

实际答案

2,undefined,undefined, 10,100 ,123

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

致谢读者

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

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

结尾

如果觉得本篇文章对您有用的话,可以麻烦您帮忙点亮那个点赞按钮吗?

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

JS进阶系列-JS执行期上下文(一)的更多相关文章

  1. JS进阶系列之执行上下文

    function test(){ console.log(a);//undefined; var a = 1; } test(); 也许你会遇到过上面这样的面试题,你只知道它考的是变量提升,但是具体的 ...

  2. 【 D3.js 进阶系列 】 进阶总结

    进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟 ...

  3. 【 D3.js 进阶系列 — 4.0 】 绘制箭头

    转自:http://www.ourd3js.com/wordpress/?p=660 [ D3.js 进阶系列 — 4.0 ] 绘制箭头 发表于2014/12/08 在 SVG 绘制区域中作图,在绘制 ...

  4. js 进阶 10 js选择器大全

    js 进阶 10 js选择器大全 一.总结 一句话总结:和css选择器很像 二.JQuery选择器 原生javaScript中,只能使用getELementById().getElementByNam ...

  5. js进阶 9 js操作表单知识点总结

    js进阶 9 js操作表单知识点总结 一.总结 一句话总结:熟记较常用的知识点,对于一些不太常用的知识点可以在使用的时候查阅相关资料,在使用和练习中去记忆. 1.表单中学到的元素的两个对象集合石什么? ...

  6. js进阶 9-14 js如何实现下拉列表多选移除

    js进阶 9-14 js如何实现下拉列表多选移除 一.总结 一句话总结: 1.js如何实现下拉列表多选移除? 把这个下拉列表中的option移除,然后加到另外一个下拉列表(文字)中去.remove方法 ...

  7. js进阶 9-12 js如何实现级联菜单 (章节测试)

    js进阶 9-12  js如何实现级联菜单 (章节测试) 一.总结 一句话总结: 1.js如何实现级联菜单 ? 二维数组,以第一级菜单的文本值做键,以对应的二级菜单选项的文本做值 2.用哪个属性可以获 ...

  8. js进阶 9-6 js如何通过name访问指定指定表单控件

    js进阶 9-6 js如何通过name访问指定指定表单控件 一.总结 一句话总结:form中控件的三种访问方式:2formElement 1document 1.form中控件的三种访问方式? 1.f ...

  9. js进阶 9-5 js如何确认form的提交和重置按钮

    js进阶 9-5 js如何确认form的提交和重置按钮 一.总结 一句话总结: 1.这个并不好做:onsubmit 里面的代码必须返回false才能取消onsubmit方法的执行,所以,有return ...

随机推荐

  1. [NOIP2018]保卫王国 题解

    NOIP2018提高组D2T3 ddp虽然好想,但是码量有点大(其实是我不会),因此本文用倍增优化树形DP来解决本题. 题意分析 给一棵树染色,每个节点染色需要一定的花费,要求相邻两个节点至少要有一个 ...

  2. 我是怎样刚拿饿了么P7 offer,然后途虎一轮游的

    今年初拿了个饿了么P7的offer,于此同时大家顺便看看我怎么途虎一轮游的.废话不多说,直接上题吧. 一面 首先上来就是自我介绍,简单的说下自己的项目经验,涉及的技术栈之类的. 然后每一轮必问的问题来 ...

  3. 接口测试中postman环境和用例集

    postman的环境使用 postman里有环境的设置,就是我们常说的用变量代替一个固定的值,这样做的好处是可以切换不同的域名.不同的环境变量,不同的线上线下账户等等场景.下面就看下怎么用吧. 创建一 ...

  4. centos 7 对用过yum更新的软件服务进行降级

    centos 7 执行 yum update 会对现有服务软件进行更新,但是如果把不该升级的软件升级,彼此软件不兼容,如何进行降级,比如:kibana 必须与 elasticsearch 大版本相同, ...

  5. 上海做假证t

    上海做假证[电/薇:187ヘ1184ヘ0909同号]办各类证件-办毕业证-办离婚证,办学位证书,办硕士毕业证,办理文凭学历,办资格证,办房产证不. 这是一个简单的取最大值程序,可以用于处理 i32 数 ...

  6. python好用的测试库-Nose

    前序: python除了unittest,还有一款更快捷的nose,nose可以说是对unittest的一种简化吧,但是他不需要unittest那种必须有固有的格式,他只需要文件,类名,方法名等含有t ...

  7. 笔记-Linux 内存优化

    1.清理前内存使用情况 free -m2.开始清理 echo 1 > /proc/sys/vm/drop_caches 3.清理后内存使用情况 free -m 4.完成! 查看内存条数命令: d ...

  8. idea中右击的快捷键都找不到 Diagrams

    今天突然发现了一件很恐怖的事情,那就是我的IDEA的右击中找不到Diagrams了,因为我是用这个东西打开 .bpmn文件生成png的,突然没了.. 说一下解决吧 在FIle -> settin ...

  9. 读网文《将20M文件从30秒压缩到1秒,我是如何做到的?》做实验

    先在微信公众号上看到网文<将20M文件从30秒压缩到1秒,我是如何做到的?>,然后在网上搜索了一下,看到了原文:https://www.jianshu.com/p/2e46ccb125ef ...

  10. 浅谈DOM事件的优化

    在 JavaScript 程序的开发中,经常会用到一些频繁触发的 DOM 事件,如 mousemove.resize,还有不是那么常用的鼠标滚轮事件:mousewheel (在 Firefox 中,滚 ...