EcmaScript中的this和ScopeChain

哈Ha!

上一篇文章中,我们研究了应用于EcmaScript的OOP的一般理论,以及有关JS中的OOP与古典语言之间的差异的新手开发人员的普遍谬误。

今天,我们将讨论另外两个同样重要的EcmaScript概念,即,实体与执行上下文的关系(此连接)和实体与生成上下文的关系( ScopeChain )。

因此,让我们开始吧!

这个


在采访中回答以下问题:“告诉我们更多有关此问题 。”。 通常,新手开发人员会给出非常模糊的答案:“ 是对象”,位于“用于调用方法的位置”之前,“这是调用函数的上下文”,等等。

实际上,这种概念(对于EcmaScript至关重要)的情况要复杂得多。 让我们按顺序弄清楚。

假设我们有一个JavaScript程序,该程序具有全局声明的变量; 全局功能; 局部函数(在其他函数内部声明),从函数返回的函数。

const a = 10; const b = 20; const x = { a: 15, b: 25, } function foo(){ return this.a + this.b; } function bar () { const a = 30; return a + b; } function fooBaz(){ function test () { return this.a + this.b; } return test(); } function fooBar() { const a = 40; const b = 50; return function () { return a + b; } } fooBar()(); 

将控制权转移到可执行代码时,将在执行上下文中创建一个条目。 可执行代码-这是我们在给定时间执行的任何代码,可以是全局代码或任何函数的代码。

执行上下文是代表和定界代码的抽象。 从这种抽象的角度来看,代码分为全局代码(任何连接的脚本,内联脚本)和函数代码(嵌套函数的代码不属于父函数的上下文)。

第三种类型-EvalCode。 在本文中,我们忽略了它。

从逻辑上讲,执行上下文集是一个按照后进先出(lifo)原理工作的堆栈 。 堆栈的底部始终是全局上下文,顶部是当前可执行文件。 每次调用函数时,都会在其上下文中创建一个条目。 函数完成后,其上下文结束。 用完的上下文按顺序和相反的顺序从堆栈中删除。

看一下上面的代码。 我们在全局代码中调用了fooBar函数。 在fooBar函数中我们返回一个立即调用的匿名函数 。 堆栈发生以下变化: 全局上下文进入该堆栈-调用fooBar其上下文进入堆栈-fooBar上下文结束,返回一个匿名函数,并从堆栈中删除-调用一个匿名函数 ,其上下文进入堆栈- 匿名函数实现,返回值并将其上下文从堆栈中删除-在脚本末尾,将从堆栈中删除全局上下文

执行上下文可以有条件地表示为对象。 该对象的属性之一将是词法环境(LO)。

词汇环境包含:

  • 所有上下文变量声明
  • 所有函数声明
  • 函数的所有形式参数(如果我们正在谈论函数的上下文)

输入执行上下文时,解释器将扫描上下文。 所有变量声明和函数声明都将出现在上下文的开头。 创建的变量等于未定义的变量,并且函数完全可以使用。

也是执行上下文的属性,而不是上下文本身,正如一些新手访调员回答的那样! 这是在进入上下文时定义的,并且在上下文生存期结束之前一直保持不变(直到从堆栈中删除上下文为止)。

在全局执行上下文中, 严格模式决定:当严格模式关闭时,它包含一个全局对象(在浏览器中,它代理到窗口对象的顶层),其中“使用严格”的定义是不确定的。

在函数的上下文中-这个问题要有趣得多!
此函数的功能由调用者确定,并取决于调用的语法。 例如,据我们所知,有一些方法可以让您在调用( callapply )时很难固定该方法,也有一些方法可以让您使用“已修复此问题”( bind )创建包装器。 在这些情况下,我们明确声明了这一点,毫无疑问它的定义。

使用正常的函数调用,情况就复杂得多!

EcmaScript内置类型之一ReferenceType将帮助我们理解函数中如何附加此类型。 这是在实施级别可用的内部类型之一。 从逻辑上讲,它是一个具有两个基本属性的对象(对某个基本对象的引用,为其返回ReferenceType), propertyName (该对象标识符的字符串表示形式,其返回了ReferenceType)。

对于所有变量声明,函数声明和属性引用, 都会返回ReferenceType (从理解这一点的角度来看,这是我们感兴趣的情况)。

为以通常方式调用的函数定义规则:
如果ReferenceType位于功能激活括号的左侧,则此ReferenceType底部将放在this函数中。 如果方括号左侧是其他类型,则this可能是全局对象,也可能是undefined对象(实际上为null ,但是由于从ecmascript的角度来看null没有特定的值,因此将其强制转换为全局对象,对该对象的引用可能是等于undefined取决于严格模式)。

让我们看一个例子:

 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();//  10 ..    ReferenceType  base     obj const test = obj.foo;//       test();//  0 ..  test()   .test(),..  base    ,       0. 

我认为定义方法很清楚。 现在考虑一些不太明显的情况。

功能表达


让我们回到参考类型。 此类型具有内置的GetValue方法,该方法返回通过ReferenceType接收到的对象的真实类型。 在表达式区域中,GetValue始终会触发。

一个例子:

 (function (){ return this;// this     undefined    strict mode })() 

这是因为GetValue始终在表达式区域中触发。 GetValue返回一个函数类型,并且在激活括号的左侧不是ReferenceType。 回想一下我们确定规则的规则: 如果方括号的左边有任何其他类型,则将全局对象放入this对象或undefined (实际上为null ,但是由于从ecmascript的角度来看null没有特定值,因此它将转换为全局对象,取决于严格模式,该链接可以等于undefined)

表达式区域是:赋值(=),运算符|| 或其他逻辑运算符,三元运算符,数组初始化程序,逗号分隔列表。

 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo(); //        //  ? (obj.foo)(); // ,    , GetValue   // ? (obj.foo = obj.foo)(); //        GetValue,     Fuction,   ReferenceType,   0   (   this) //  ||    ,    ..? (obj.foo || obj.foo)();// 0    ,     //  [obj.foo][0]();// 0    ,     // .. 

命名函数表达式中的相同情况。 即使递归调用此全局对象或undefined

此嵌套函数在父级中调用


也是重要的情况!

 const x = 0; function foo() { function bar(){ return this.x; } return bar(); } const obj = {x:10}; obj.test = foo; obj.test();// undefined 

这是因为对bar()的调用等效于对LE_foo.bar的调用,并且词法环境的对象为此未定义。

构造函数


正如我上面所写:
此函数的功能由调用者确定,并取决于调用的语法。

我们使用new关键字调用构造函数。 此函数激活方法的特殊之处在于,将调用内部函数方法[[construct]] ,该方法执行某些操作(有关设计人员创建实体的机制,将在OOP的第二篇或第三篇文章中进行讨论!)并调用内部[[call]]方法,该方法放下在创建的构造函数实例中。

范围链


范围链也是这样的执行上下文的属性。 它是当前上下文和所有生成上下文的词法环境的对象列表。 在此链中,解析标识符名称时将进行变量搜索。

注意:这将函数与执行上下文关联,并将ScopeChain与子上下文关联。

规范指出ScopeChain是一个数组:

  SC = [LO, LO1, LO2,..., LOglobal]; 

但是,在某些实现中,例如JS,范围链是通过链接列表实现的。

为了更好地理解ScopeChain,我们将讨论功能的生命周期。 它分为创建阶段和执行阶段。

创建函数时,将为其分配内部[[SCOPE]]属性。
[[SCOPE]]中 ,记录了较高(生成)上下文的词汇环境的对象的层次链。 该属性将保持不变,直到该函数被垃圾收集器销毁为止。

注意! [[SCOPE]]与ScopeChain不同,它是函数本身的属性,而不是其上下文。

调用函数时,将初始化并填充其执行上下文。 上下文附加有ScopeChain = LO(函数本身)+ [[SCOPE]](影响上下文的LO层次链)。

标识符名称解析-ScopeChain从左到右顺序轮询LO对象。 输出是ReferenceType,其基本属性指向在其中找到标识符的LO对象,PropertyName将是标识符名称的字符串表示形式。

这就是盖子在引擎盖下的排列方式! 闭包本质上是在ScopeChain中搜索其标识符在函数中存在的所有变量的结果。

 const x = 10; function foo () { return x; } (function (){ const x = 20; foo();// 10 ..     <b><i>[[SCOPE]]</i></b> foo          })() 

以下示例说明了生命周期[[SCOPE]]

 function foo () { const x = 10; const y = 20; return function () { return [x,y]; } } const x = 30; const bar = foo();//   ,   foo    bar();// [10,20] .. [[SCOPE]]    foo          

一个重要的例外是构造函数 。 对于此类功能,[[SCOPE]]始终指向全局对象。

另外,不要忘记,如果ScopeChain链中的链接之一具有原型,那么搜索也将在原型中进行。

结论


我们将通过论文提出关键思想:

  • 这是实体与执行上下文的关系
  • ScopeChain是实体与所有生成上下文的关系
  • this和ScopeChain是执行上下文属性
  • 此功能由调用方确定,并取决于调用的语法
  • ScopeChain是当前上下文的词法环境+ [[Scope]]
  • [[Scope]]-这是函数本身的属性,包含生成上下文的词汇环境的层次链

希望本文对您有所帮助。 直到以后的文章,朋友们!

Source: https://habr.com/ru/post/zh-CN468943/


All Articles