资讯专栏INFORMATION COLUMN

JS 作用域链

darry / 3559人阅读

摘要:首先,在创建函数时,作用域链内就会先填入对象,图片只例举了全部变量中的一部分。然后,解释器进入函数的执行环境,同样的,首先填入父级的作用域链,就是的,包括了对象活动对象。之后再把的活动对象填入到作用域链最顶部,这就是的作用域链了。

之前学习JS函数部分时,提到了作用域这一节,但是因为使用材料书不同,今天在读博客的时候发现其实还有一个知识点即作用域链,所以来写一些个人理解和认识加深记忆。

引用:

JavaScript中的作用域链和闭包

JS的作用域和作用域链

结合代码图文讲解JavaScript中的作用域与作用域链(主要)

先上考试代码:

/==========例1==========
  
var scope="global";
function fn(){
  alert(scope);
  var scope="local";
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?
  
//===========例2==========
  
var scope="global";
function fn(){
  alert(scope);
  scope="local";
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?
  
//===========例3=========
  
var scope="global";
function fn(scope){
  alert(scope);
  scope="local";
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?

我当时做时候,卡在了第三题,后面明白了。

例1:只要记得变量声明提升,例1应该没什么问题,由于var变量声明提前,所以当调用fn()时,第一个alert应该弹出undefined,之后赋值,再alert"local"。而函数外的alert之前调用全局变量scope,弹出"global"

    var scope = "global";
    function fn() {
        var scope;    
        alert(scope);            // undefined
        scope = "local";
        alert(scope);            // local
    };
    fn();
    alert(scope);                // global

例2:由于函数内没有var声明变量,所以函数内的scope指向的是全局变量scope,那alert当然是全局变量的值啦——"global",之后赋值,再次alert,弹出"local"。此时全局变量scope已经被重新赋值,所以函数外的alert弹出"local"

例3:这里先不提,后面就OK了。当时我不知道如果传入实参在函数内是以什么方式存在,不传入值为多少等。

板书ing

作用域链:

作用域链(Scope Chain)是javascript内部中一种变量、函数查找机制,它决定了变量和函数的作用范围,即作用域,理解作用域链的作用原理,上一篇文章的三个例子也就能理解了,从而知其然也知其所以然。
作用域链是ECMAScript-262说明文档中的概念,javascript引擎是按ECMAScript-262说明文档去实现的,了解javascript引擎的工作原理有利于我们理解javascript的特性,但绝大多数js程序员不会去了解非常底层的技术,所以阅读ECMAScript-262说明文档,我们可以有一个直观的方式去模拟javascript引擎的工作原理。
本文将通过1999年的ECMAScript-262-3th第三版来说明作用域链的形成原理,将会介绍执行环境,变量对象和活动对象,arguments对象,作用域链等几个概念。2009年发布了ECMAScript-262-5th第五版,不同的是取消了变量对象和活动对象等概念,引入了词法环境(Lexical Environments)、环境记录(EnviromentRecord)等新的概念,所以两个版本的概念不要混淆了。

重点来了!

1. 执行环境(Execution Contexts)

执行环境(Execution Contexts)也被翻译为执行上下文,当解析器进入ECMAScript的可执行代码,解析器就进入一个执行环境,活动的执行环境组成一个逻辑上的栈,在这个逻辑栈顶部的执行环境是当前运行的执行环境。
注:ECMAScript中有三种可执行代码,GlobalFunctionEval,全局环境即是Global可执行代码,函数即是Function可执行代码。逻辑栈是一种特殊的数据存储格式,特点是‘先进后出,后进先出",添加数据会先压入逻辑栈顶部,删除数据必须先从顶部开始删除。

变量对象(Variable Object)、活动对象(Activation Object)和Arguments对象(Arguments Object)
(上面这句话很重要哦)

每个执行环境都有一个与之关联的变量对象,当解析器进入执行环境时,就会创建一个变量对象,变量对象保存着在当前执行环境中声明的变量和函数的引用。

变量对象是一个抽象的概念,在不同的执行环境中,变量对象有不同的身份,在解析器进入任何执行环境之前,就已经创建了一个Global对象,当解析器进入全局执行环境时,Global对象就充当变量对象,当解析器进入一个函数时,就会创建一个活动对象充当变量对象。

我的理解是:解析器在执行代码时,会遇到不同的执行环境,此时,会创建一个变量对象,里面存放了环境内的变量和对象(函数)引用。

当执行环境是变量,则会生成一个Global对象,此时变量对象就是Global对象

当执行环境是函数,则会生成一个活动对象(Activation Object)

2.解析器处理代码时的两个阶段

我们都知道javascript解析器是一段一段解析处理代码的,为毛?这就要涉及解析器处理代码时的两个阶段,解析代码和执行代码。
当解析器进入执行环境时,变量对象就会添加执行环境中声明的变量和函数作为它的属性,这就意味着变量和函数在声明之前已经可用,变量值为undefined,这就是变量和函数声明提升(Hoisting)的原因,与此同时作用域链和this确定,此过程为解析阶段,俗称预解析。接着解析器开始执行代码,为变量添加相应值的引用,得到执行结果,此过程为执行阶段。

我们还是用栗子谈吧

var a=123;
var b="abc";
function c(){
  alert("11");
}


记得之前那句话吗?在解析器进入任何执行环境之前,就已经创建了一个Global对象,当解析器进入全局执行环境时,Global对象就充当变量对象。一开始,JavaScript解析器就已经生成了一个Global Object来充当变量对象,里面存放了全局环境里的变量,对象(函数)等。就如上图所示了,所以这也是为什么我们在函数内部声明变量时,声名会提前,赋值undefined的原因了。到现在为止,执行到这就是预解析的过程也叫解析代码。

然后开始执行赋值等操作,此过程就叫执行过程。

再看这个:

function testFn(a){
  var b="123";
  function c(){
    alert("abc");
  }
}
  
testFn(10);

解析器进入函数执行环境时,则会创建一个活动对象作为变量对象,活动对象还会创建一个Arguments对象,arguments对象是一个参数集合,用来保存参数,这就是我们写函数时可以使用arguments[0]等来使用参数的原因。

var a="123";
function testFn(b){
  var c="abc";
  
  function testFn2(){
    var d="efg";
    alert(a);
  }
  
  testFn2();
}
  
testFn(10);


首先,在创建函数testFn时,作用域链内([[scope]])就会先填入Global Object对象,图片只例举了全部变量中的一部分。
解析器进入到testFn的执行环境(执行上下文)时,在将函数的活动对象添加到Global对象之前,注意是之前,形成一个作用域链。

然后,解释器进入testFn2函数的执行环境,同样的,首先填入父级的作用域链,就是testFn的[[scope]]],包括了Global对象、testFn活动对象。之后再把testFn2的活动对象填入到作用域链最顶部,这就是testFn2的作用域链了。

testFn2调用变量a时,首先在当前的testFn2活动对象中查找,如果没有找到就顺着作用域链向上,在testFn活动对象中查找变量a,如果没有找到再顺着作用域链向上查找,直到在最后Global对象中找到为止,否则报错。所以函数内部可以调用外部环境的变量,外部环境不能调用函数内部的变量,这就是作用域特性的原理。
大概总结一下:

执行环境:(Execution Contexts)也被翻译为执行上下文,当解析器进入ECMAScript的可执行代码,解析器就进入一个执行环境,活动的执行环境组成一个逻辑上的栈,在这个逻辑栈顶部的执行环境是当前运行的执行环境。

ECMAScript中有三种可执行代码,Global、Function和Eval,全局环境即是Global可执行代码,函数即是Function可执行代码。逻辑栈是一种特殊的数据存储格式,特点是‘先进后出,后进先出",添加数据会先压入逻辑栈顶部,删除数据必须先从顶部开始删除。

变量对象(Variable Object)、活动对象(Activation Object)和Arguments对象(Arguments Object)

每个执行环境都有一个与之关联的变量对象,当解析器进入执行环境时,就会创建一个变量对象,变量对象保存着在当前执行环境中声明的变量和函数的引用。

变量对象是一个抽象的概念,在不同的执行环境中,变量对象有不同的身份,在解析器进入任何执行环境之前,就已经创建了一个Global对象,当解析器进入全局执行环境时,Global对象就充当变量对象,当解析器进入一个函数时,就会创建一个活动对象(Activation Object)充当变量对象。

大致过程: 1. 自动创建Global对象

(当解析器进入全局执行环境时,调用变量和函数时只在Global对象中查找。)

2. 解释器进入执行环境(执行上下文)

(也可理解为执行函数时等等。)

3.生成变量对象

(每个执行环境都有一个与之关联的变量对象,当解析器进入执行环境时,就会创建一个变量对象,变量对象保存着在当前执行环境中声明的变量和函数的引用。)

(变量对象是一个抽象的概念,在不同的执行环境中,变量对象有不同的身身份。)

4. 创建作用域链(执行过程中的预解析、执行阶段)

(每个执行环境都有一个与之关联的作用域链,当解析器进入执行环境时被定义,作用域链是一个对象列表,用来检索各个变量对象中的变量和函数,这样可以保证执行环境有权访问哪些变量和函数)

解析阶段:当解析器进入执行环境时,变量对象就会添加执行环境中声明的变量和函数作为它的属性,这就意味着变量和函数在声明之前已经可用,变量值为undefined,这就是变量和函数声明提升(Hoisting)的原因,与此同时作用域链和this确定,此过程为解析阶段,俗称预解析。
执行阶段:接着解析器开始执行代码,为变量添加相应值的引用,得到执行结果,此过程为执行阶段。)

这里也就是说,变量对象先于作用域链创建前就以生成完毕?

5.按优先级填入Global对象、活动对象等 6. 整个作用域链创建完成

我们回到最初的题目,最后的例3中,调用fn时,并没有传参,所以fn函数的活动对象中没有相关的键值(注意只是没有值,但存在这个属性),第一个alert弹出undefined,之后为其赋值,这时fn函数的活动对象中的scope就有值了,之后alert调用,搜索时自然先从优先级最高的fn活动对象中寻找,然后弹出"local"。
而函数外的alert,依旧只有Global对象,其中的值未曾改变,所以弹出"global"

// undefined local  global

了解作用域和作用域链都更好的帮助了解闭包噢。

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

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

相关文章

  • 形象化模拟作用域链,深入理解js作用域、闭包

    摘要:至此作用域链创建完毕。好了,通过深入理解作用域链,我们能跟好的理解的运行机制和闭包的原理。 前言 理解javascript中的作用域和作用域链对我们理解js这们语言。这次想深入的聊下关于js执行的内部机制,主要讨论下,作用域,作用域链,闭包的概念。为了更好的理解这些东西,我模拟了当一个函数执行时,js引擎做了哪些事情--那些我们看不见的动作。 关键词: 执行环境 作用域 作用域链 变...

    txgcwm 评论0 收藏0
  • 深入学习js之——作用域链

    摘要:开篇作用域是每种计算机语言最重要的基础之一,因此要想深入的学习作用域和作用域链就是个绕不开的话题。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。这时候执行上下文的作用域链,我们命名为至此,作用域链创建完毕。 开篇 作用域是每种计算机语言最重要的基础之一,因此要想深入的学习JavaScript,作用域和作用域链就是个绕不开的话题。 在《深入学习js之—-执行上下文栈》中我们提到...

    lemanli 评论0 收藏0
  • [学习笔记] JavaScript 作用域链

    摘要:全局执行环境的变量对象始终是作用域链中的最后一个变量对象。综上,每个函数对应一个执行环境,每个执行环境对应一个变量对象,而多个变量对象构成了作用域链,如果当前执行环境是函数,那么其活动对象在作用域链的前端。 1.几个概念 先说几个概念:函数、执行环境、变量对象、作用域链、活动对象。这几个东东之间有什么关系呢,往下看~ 函数 函数大家都知道,我想说的是,js中,在函数内部有两个特殊...

    ?xiaoxiao, 评论0 收藏0
  • JS基础知识:变量对象、作用域链和闭包

    摘要:前言这段时间一直在消化作用域链和闭包的相关知识。而作用域链则是这套规则这套规则的具体运行。是变量对象的缩写那这样放有什么好处呢我们知道作用域链保证了当前执行环境对符合访问权限的变量和函数的有序访问。 前言:这段时间一直在消化作用域链和闭包的相关知识。之前看《JS高程》和一些技术博客,对于这些概念的论述多多少少不太清楚或者不太完整,包括一些大神的技术文章。这也给我的学习上造成了一些困惑,...

    Keven 评论0 收藏0
  • JS 执行上下文栈 / 作用域链

    摘要:每一个执行上下文可以访问的对象包括自身的作用域和父执行上下文的作用域和父父执行上下文作用域直到全局作用域,这就产生了作用域链。语句结束后,作用域链恢复正常。 0、自己理解 代码执行或函数调用生成执行上下文(只有当前执行上下文有执行权),该执行上下文内只能访问当前执行上下文的变量、函数和上一级执行上下文中的变量、函数,激活下一个执行上下文的时候执行权转移到新的执行上下文,形成执行上下文栈...

    yunhao 评论0 收藏0
  • JS中的new和作用域链

    摘要:同时构造函数内部的被指定为。这时的作用域链是由的活动对象和全局对象组成的。在被调用时,它自身的活动对象被创建,然后添加到了中存储着的作用域链的最前方。当函数执行完毕时活动对象会被从该作用域链上删除。参考自运算符作用域原理译函数的作用域链 new的运行机制 当代码new Animal(cat)执行时: var obj=Object.create(Animal.prototype); 传...

    calx 评论0 收藏0

发表评论

0条评论

darry

|高级讲师

TA的文章

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