我对“词法环境”“执行上下文”的理解

execution context,lexical environment左边的这两个单字出自ECMA标准用于规范语言实现,也就是回答“代码是怎样被执行的?”。我没有耐心仔细的研读规范,如果日后我有了耐心研读了规范,并且写了更多的代码,看了更多的书,我的认识可能会改变。但是现在我还是想阐述一下我对这两个单词的认识。以下内容在写作时没有外部参考文献,但是内容受到很多文章的启发,更确切的来说有很多是其他人的观点。

回答“代码是怎样被执行”这个问题,肯定不可以很深入,现在是研究前端就停留在前端的深度上就好了,如果走到了汇编语言,扯到了“取指执行”这种层次就太没有必要了,我也不会。回到这个问题上来。以下我用一段话,基于我的个人认知,回答这个问题。

var a = "global a"; 
function haoming() {
    console.log("haoming");
}
with({
    a : "with a",
    b : 1
}) {
    console.log(a);
    console.log(this);
    (function() {
        console.log(this.a);
        console.log(a);
    })();
    (function a () {
        var a = "with anonymous a ";
        console.log(this.a);
        console.log(a);
    })();
    (function() {
        console.log(this.a);
        console.log(a);
    })();
    function func() {
        var b = "func b";
        console.log(a);
        function func1() {
            console.log(b);
        }
        return func1;
    }
}
var func1 = func();
func1();
haoming();

首先,解释器“预处理”全局作用域的代码,将变量声明,函数定义,
添加到一个lexical environme对象上,设置this值为全局对象(在浏览器环境为window),还有设置变量对象(我暂且认为变量对象和词法环境对象相同,但是它们肯定不同,要不然标准为什么要设计它呢,所以后文我忽略它,它的功能都假装由词法环境对象实现,在初始化时,它们肯定是一样的)。注意哦,在这个阶段,

变量的值是没有初始化的,函数声明会初始化一个函数对象,在这个函数对象中有一个“作用域”属性,这个属性的值很有用。但是函数表达式在这个阶段是不会被处理的,它只会像变量声明一样,是一个没有初始化的变量。这一点其实已经可以解释很多问题了,比如变量声明提前,函数声明提前(可以在函数声明代码前使用函数)。然后这三个对象组成一个执行上下文对象添加到执行栈的顶部,直到整个程序执行完毕,这个全局执行上下对象才会离开执行栈。然后,解释器开始按照代码书写顺序执行代码,解释一些语句。直到,它遇到了with语句(一般情况下没有人会用with语句,会影响性能,而且代码变得很奇怪),这个时候,我猜测,会创建一个新的执行上下文对象,添加到栈顶,它的词法环境就是括号中的对象。然后继续执行代码,遇到一个使用了‘a’变量的语句,从栈顶的词法环境开始查找,确定值是“with a”,遇到一个使用this的语句,this的确定规则我们后文再讲,此时直接读取栈顶的this值是window。继续执行遇到一个‘立刻执行的函数表达式’,首先创造一个新的执行上下文对象,它的词法环境中有一个匿名函数对象,然后再创造一个函数上下文对象,根据函数对象的“作用域”属性,处理堆栈,如果作用域属性形成的链和从栈顶到栈底的执行上下文对象(中的词法环境)链一样,那么栈不改变,否则栈改变为”作用域链“的顺序。然后预处理函数执行上下文对象中的变量声明,函数声明,之后按照函数内部代码的顺序执行函数代码,输出this.a,直接读取this值,this为window,从栈顶开始到底部查找window的值,读取它的a属性。输出a的值,从栈顶到栈底查找它的值,在with执行上下文中确定它的值为”with a ”,执行完了当前函数的所有代码,弹出该函数执行上下对象,弹出只有一个匿名函数对象的执行上下文对象。继续执行遇到一个”命名的立刻执行函数表达式“,和上文一样创造插入一个(词法环境)只有一个命名函数对象的执行上下文对象(这个唯一值不可以修改),利用该函数对象创造和插入一个函数执行上下文对象,访问this.a,确定this值为window,向下查找window,输出它的a属性值,访问a变量,向下查找a变量。在with执行上下问中找到,输出”with a“弹出当前的函数执行上下文对象,和只有一个值的执行上下文对象。结束with内部的代码执行,弹出with执行上下文对象,此时,执行栈中又只有全局执行上下文对象了,执行func函数,根据函数对象的作用域链属性更改执行栈(好像只有闭包要更改),根据上文讲诉直接创建一个函数执行上下文压入栈中(注意,没有那个只有一个值的执行上下文)。开始执行函数中的代码,查找a变量,在全局执行上下文中获取到。结束该函数的执行,弹出栈顶项。执行func1,根据函数对象的作用域链属性更新栈(这是唯一要更新的地方,因为func1的作用域链属性栈顶多了一个闭包),向栈中压入一个闭包的执行上下文,创建函数的执行上下文压入栈中,查找b变量,从栈顶往下查找,在闭包中查找到值。结束函数执行。弹出函数执行上下文对象。弹出和它关联的闭包执行上下文。流程基本结束。弹出全局执行上下文,执行栈清空。

上文中扯了一大堆,超级希望你理解了我的意思。我在上一段中有很多次把执行上下文,和词法环境混淆,其实执行上下文中最重要的就是词法环境了,this值不怎么重要,变量对象和词法环境基本没差别。理解了上一段,关于JS的作用域问题再也不是问题了。我还看过有人用树的方法来理解作用域(把每个词法环境当作一个树节点),其实也是不错的方法。没准某个实现还真的混了树和栈来做。

现在已经不需要我回答词法环境和执行上下文是什么了。词法环境在标准中还是有点复杂的,由环境记录和outter属性组成,outter属性就是我前面说到的函数对象中的作用域链属性,它们的值是一样的。当创造函数对象时会把栈顶的outter赋值给它创造的那个函数对象的作用域链属性。所以说,在大部分情况,是不会发生作用域链和栈不同而更新栈的情况。环境记录可以是对象或者声明集合,是对象的情况比如with语句,全局的执行上下文中的词法环境,直接就是括号中的对象和window对象,这种的特定是可以更改词法环境中的项。(预处理以后还可以更改)另一种就是函数的词法环境对象并不是一个我们可以控制的对象,虽然如此说,它的内部应该也实现为一个对象。执行上下文包裹了词法环境。

this值如何确定?
如果一个函数是作为对象的方法被调用的那么,this值就是这个对象。否则this值就是window。如何识别一个函数是否是作为对象的方法被调用的呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值