资讯专栏INFORMATION COLUMN

彻底理解Javascript中的作用域链

zhunjiee / 593人阅读

摘要:全局和上下文中的作用域链这里不一定很有趣,但必须要提示一下。全局上下文的作用域链仅包含全局对象。代码的上下文与当前的调用上下文拥有同样的作用域链。代码执行时对作用域链的影响在中,在代码执行阶段有两个声明能修改作用域链。

1 定义

我们已经知道一个执行上下文中的数据(参数,变量,函数)作为属性存储在变量对象中。

也知道变量对象是在每次进入上下文是创建并填入初始值,值的更新出现在代码执行阶段。

作用域链就是这些变量对象的链表。

让我们看一下和作用域相关的上下文结构
VO是当前上下文的变量对象,重点是Scope属性,Scope = VO+[[scope]]。其中[[scope]]为所有父上下文变量对象的链表。

activeExecutionContext = {
    VO: {...}, // or AO
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
      // for identifiers lookup
    ]
};

函数的生命周期分为创建和激活。

2 函数创建阶段
var x = 10;
  
function foo() {
  var y = 20;
  alert(x + y);
}
  
foo(); // 30

此前,我们仅仅谈到有关当前上下文的变量对象。这里,我们看到变量“y”在函数“foo”中定义(意味着它在foo上下文的AO中),但是变量“x”并未在“foo”上下文中定义,相应地,它也不会添加到“foo”的AO中。乍一看,变量“x”相对于函数“foo”根本就不存在;但正如我们在下面看到的——也仅仅是“一瞥”,我们发现,“foo”上下文的活动对象中仅包含一个属性--“y”。

fooContext.AO = {
y: undefined // undefined – 进入上下文的时候是20 – at activation
};

函数“foo”如何访问到变量“x”?理论上函数应该能访问一个更高一层上下文的变量对象。实际上它正是这样,这种机制是通过函数内部的[[scope]]属性来实现的。

[[scope]]是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中。

注意这重要的一点--[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。

3 函数激活阶段

正如在定义中说到的,进入上下文创建AO/VO之后,上下文的Scope属性(变量查找的一个作用域链)作如下定义:

Scope = AO|VO + [[Scope]]

上面代码的意思是:活动对象是作用域数组的第一个对象,即添加到作用域的前端。
Scope = [AO].concat([[Scope]]);

这个特点对于标示符解析的处理来说很重要。

标示符解析是一个处理过程,用来确定一个变量(或函数声明)属于哪个变量对象。
标识符解析过程包含与变量名对应属性的查找,即作用域中变量对象的连续查找,从最深的上下文开始,绕过作用域链直到最上层。

这样一来,在向上查找中,一个上下文中的局部变量较之于父作用域的变量拥有较高的优先级。万一两个变量有相同的名称但来自不同的作用域,那么第一个被发现的是在最深作用域中。

4 闭包

在ECMAScript中,闭包与函数的[[scope]]直接相关,正如我们提到的那样,[[scope]]在函数创建时被存储,与函数共存亡。实际上,闭包是函数代码和其[[scope]]的结合。

因为闭包函数在创建的时候就创建了父级的变量对象链表,也就是父级作用域链, 然后闭包函数再访问父级作用域链中的变量,导致父级函数执行完毕后仍然不能释放执行上下文的情况。

5 通过构造函数创建的函数的[[scope]]

在上面的例子中,我们看到,在函数创建时获得函数的[[scope]]属性,通过该属性访问到所有父上下文的变量。但是,这个规则有一个重要的例外,它涉及到通过函数构造函数创建的函数。

var x = 10;
  
function foo() {
  
 var y = 20;
  
 function barFD() { // 函数声明
alert(x);
alert(y);
}
  
 var barFE = function () { // 函数表达式
alert(x);
alert(y);
};
  
 var barFn = Function("alert(x); alert(y);");
  
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
  
}
  
foo();

我们看到,通过函数构造函数(Function constructor)创建的函数“bar”,是不能访问变量“y”的。但这并不意味着函数“barFn”没有[[scope]]属性(否则它不能访问到变量“x”)。问题在于通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象。考虑到这一点,如通过这种函数创建除全局之外的最上层的上下文闭包是不可能的。

6 全局和eval上下文中的作用域链

这里不一定很有趣,但必须要提示一下。全局上下文的作用域链仅包含全局对象。代码eval的上下文与当前的调用上下文(calling context)拥有同样的作用域链。

globalContext.Scope = [
Global
];
  
evalContext.Scope === callingContext.Scope;
7 代码执行时对作用域链的影响

在ECMAScript 中,在代码执行阶段有两个声明能修改作用域链。这就是with声明和catch语句。它们添加到作用域链的最前端,对象须在这些声明中出现的标识符中查找。如果发生其中的一个,作用域链简要的作如下修改:

Scope = withObject|catchObject + AO|VO + [[Scope]]

eval代码运行的字符串利用当前调用的上下文,并且能够修改当前上下文中的变量对象,也就是说eval内和eval外的代码一样,只是不能变量提升,执行起来是一样的。
with代码块和catch代码块都改变了作用域链,但是在他们代码块中声明的变量,也存在了函数作用域中,只是with的对象和catch对象外部不能访问而已。

eval("var x=3");
console.log(x); //3      
with (obj1) {
    var innner ="inner";
    console.log(a); //1
    console.log(b); //2
}
console.log(innner); // inner 仍能访问
console.log(a) //访问不到
try {
    throw new Error("error")
}
catch (e) {
    var cat = 2;
}
console.log(cat); //2 仍能访问

英文原文:http://dmitrysoshnikov.com/ec...
本文绝大部分内容来自上述地址,仅做少许修改

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86609.html

相关文章

  • 彻底明白作用域、执行上下文

    摘要:代码执行阶段在这个阶段会生成三个重要的东西变量对象,作用域链变量对象在函数上下文中,我们用活动对象来表示变量对象。这时候执行上下文的作用域链,我们命名为至此,作用域链创建完毕。 好久没更新文章了,这一憋就是一个大的。说起js中的概念,执行上下文和作用域应该是大家最容易混淆的,你说混淆就混淆吧,其实大多数人在开发的时候不是很关注这两个名词,但是这里面偏偏还夹杂好多其他的概念--变量提升啊...

    whataa 评论0 收藏0
  • 前端基础进阶(四):详细图解作用域链与闭包

    摘要:之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。而的作用域链,则同时包含了这三个变量对象,所以的执行上下文可如下表示。下图展示了闭包的作用域链。其中为当前的函数调用栈,为当前正在被执行的函数的作用域链,为当前的局部变量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初学JavaScrip...

    aikin 评论0 收藏0
  • JavaScript之例题中彻底理解this

    摘要:最后重点理解结论箭头函数的,总是指向定义时所在的对象,而不是运行时所在的对象。输出,箭头函数不会绑定所以传入指向无效。原因是,要彻底理解应该是建立在已经大致理解了中的执行上下文,作用域作用域链,闭包,变量对象,函数执行过程的基础上。 本文共 2025 字,看完只需 8 分钟 概述 前面的文章讲解了 JavaScript 中的执行上下文,作用域,变量对象,this 的相关原理,但是我...

    Hwg 评论0 收藏0
  • 初学者彻底理解javascript闭包以及this关键字

    摘要:理解了这句话,我们就可以来看闭包了闭包前面说过,函数可以访问函数作用域链中的变量,但如果我们想在函数外访问函数内却不行了。 不管是闭包还是this关键字,都是困扰JS初学者的比较难懂的东西,如果你对它们的认识还不足够清晰,那么现在就一起把它们掌握掉。还是那句话,我们从最基本的开始,建立起一个非常清晰的知识结构,好了,开始吧 ? 闭包 当然我们今天说的是javascript里的闭包。要学...

    魏明 评论0 收藏0
  • Javascript】深入理解this作用域问题以及new/let/var/const对this作

    摘要:理解作用域高级程序设计中有说到对象是在运行时基于函数的执行环境绑定的在全局函数中,等于,而当函数被作为某个对象调用时,等于那个对象。指向与匿名函数没有关系如果函数独立调用,那么该函数内部的,则指向。 理解this作用域 《javascript高级程序设计》中有说到: this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,t...

    snowLu 评论0 收藏0

发表评论

0条评论

zhunjiee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<