JS进阶系列-JS执行期上下文(一)
❝点赞再看,年薪百万 本文已收录至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(){}无法提升
致谢读者
看到这里的读者都是最帅的,最美的,本篇是我尝试写文章的新做法,第一次搞这种类型的文章,因为希望自己的文章阅读起来可以没有那么枯燥(可以从中获取更多的快乐),学习是一件特别痛苦的事情,看文章也是,我也希望自己的文章可以更加的生动、幽默。让看文章的你既可以学到东西也不会那么枯燥。
如果您有更好的建议,请在下方留下您宝贵的评论。
结尾
如果觉得本篇文章对您有用的话,可以麻烦您帮忙点亮那个点赞按钮吗?
对于二郎神杨戬这个暖男来说:「真的真的非常有用」,您的支持将是我继续写文章前进的动力,我们下篇文章见。
作者:一只流浪的kk 出处:https://www.cnblogs.com/jjgw/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留原文链接,否则将追究法律责任 |
微信扫一扫 |