| JavaScript对话面试官系列 | - 执行上下文

109 阅读4分钟

起因

当我输入了大量来自优质个人博客文章,经典书籍,名牌讲师的 JavaScript 系统知识之后,我反而变得有些困惑。我明明可以流利的书写八大继承,流利的书写 Underscore 库中的防抖节流,深拷贝……,我也知晓什么是闭包,什么是迭代器,生成器……。但是问题来了,当我试图将一个 JavaScript 核心概念,比如闭包……,介绍给同学,讲解给自己,对话面试官的时候。原本自认为可以脱口而出的语言,到嘴边却显得吞吞吐吐。只能用只言片语,或者东拼西凑的知识点来表达给对方。这无疑让我有了一种,虽然花了大量时间但是却从未拥有过 JavaScript 的感觉。

目的

所以这个系列要尝试解决的问题就是当别人询问或者考察我JavaScript 核心概念的时候,我可以尽可能流畅的,清晰的表达给他人。

期望

希望掘金优秀的前端技术人员和前辈们可以在百忙之中多多补充这篇文章,多多审查这篇文章,多多提问我。我期望可以通过这个系列来解决我当前的问题

执行上下文

我们知道JavaScript代码是一段一段执行的,再执行到一段可执行代码的时候,就会创建执行上下文/执行上下文环境/执行上下文对象JavaScript可执行代码有三种:全局代码,函数代码,eval代码,所以对应的执行上下文对象也就有三种,分别是全局执行上下文,函数执行上下文,eval执行上下文。这三种执行上下文对象都有三个属性,第一个是变量环境对象VO,第二个是作用域链Scope,第三个是this

可以用一段代码的执行来更好的解释执行上下文:

var a = 1;
function out () {
    var name = "ryan";
    function in () {
        console.log(name);
    }
    return in()
}
out();

有这样的一段代码,var a = 1,有一个外层函数outout函数当中定义 var name = 2out外层函数当中有一个内层函数in,内层函数,打印外层函数作用域的变量name,返回内层函数的调用。外层先调用out,然后再调用in

首先一上来JavaScript引擎会先创建GO就是global object全局对象,这个全局对象上,我们可以使用一些预定义的对象或者函数,比如:日期对象Date,数学对象 Math …… 开始执行后JavaScript引擎会拿到他的第一段可执行代码,全局代码,所以会先创建全局执行上下文对象,并且将执行上下文push到调用栈当中,这时开始初始化全局上下文对象。初始化这个对象就是初始化三个属性:作用域链属性,VO变量对象,绑定this

初始化作用域链:将JavaScript刚刚创建的GO保存为作用域链属性。这样全局上下文的代码可以根据作用域链访问到GO上的属性和方法。

初始化VO:这个VO就是来收集定义的变量和函数的定义。 这里就是全局定义的a属性 和定义的out函数。

绑定this:这个就是this的绑定,这一块可以和面试官说完执行上下文之后来聊聊。也是东西很多。

这时out函数被创建,保存全局上下文对象的作用链作为out函数的[[scope]]属性。调用out函数,然后创建out函数上下文对象,入栈,开始初始化out函数初始化上下文对象。

初始化AO活动对象:函数上下文当中叫AO(既可以收集定义的变量和函数,还能够收集函数的形参),全局当中我们是叫做VO。这里就是 name变量in内部函数。

初始化作用域链:将out函数的[[scope]]属性赋值过来作为作用域链属性。再将out函数的AO对象压入out函数作用域链的最顶端。这样做的结果和目的就是,out函数开始执行查找属性和函数的时候,先在out函数的作用域的AO对象上找属性和方法,然后去GO上找属性和方法。

绑定this:还是函数的this绑定。

这时in函数被创建,保存out函数的作用链到in函数的[[scope]]属性,调用in函数,然后创建in函数上下文,入栈,开始初始化in函数初始化上下文对象。

初始化AO活动对象:这里没有初始化代码。

初始化作用域链:将in函数的[[scope]]属性赋值过来作为作用域链属性。再将in函数的AO对象压入in函数作用域链的最顶端。这样做的结果就很明确,in函数开始执行查找属性和函数的时候,先是自己的AO,然后是out函数的AO,最后是全局上下文的GO

绑定this:还是函数的this绑定。

执行完in之后出栈,out出栈,全局上下文出栈,执行完毕。