JavaScript——执行环境、变量对象、作用域链

前言

这几天在看《javascript高级程序设计》,看到执行环境和作用域链的时候,就有些模糊了。书中还是讲的不够具体。通过上网查资料,特来总结,以备回顾和修正。

目录:

  • EC(执行环境或者执行上下文,Execution Context)
  • ECS(执行环境栈Execution Context Stack)
  • VO(变量对象,Variable Object)|AO(活动对象,Active Object)
  • Scope Chain(作用域链)和[[Scope]]属性

EC——执行环境或执行上下文

每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文。

JavaScript中,EC分为三种:

  • 全局级别的代码——这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境
  • 函数级别的代码——当执行一个函数式,运行函数体中的代码
  • Eval的代码——在Eval函数内运行的代码

EC建立分为俩个阶段:

  1. 进入上下文阶段:发生在函数调用时,但是在执行具体代码之前(比如,对函数参数进行具体化之前)
  2. 执行代码阶段:变量赋值,函数引用,执行其它代码

我们可以将EC看做是一个对象:

EC={VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */},this:{},Scope:{ /* VO以及所有父执行上下文中的VO */}
}

ECS——执行环境栈

一系列活动的执行上文从逻辑上形成一个栈。栈底总是全局上下文,栈顶是当前(活动的)执行上下文。当在不同的执行上文间切换(退出的而进入新的执行上下文)的时候,栈会被修改(通过压栈或退栈的形式)。

压栈:全局EC → 局部EC1 → 局部EC2 → 当前EC

出栈:全局EC ←全局EC1 ←全局EC2 ←当前EC

我们可以用数组的形式来表示环境栈:

ECS=[局部EC,全局EC];

每次控制器进入一个函数(哪怕该函数被递归调用或者作为构造器),都会发生压栈的操作。过程类似JavaScript数组的Push和Pop操作。

当JavaScript代码文件被浏览器载入后,默认最先进入的是一个全局的执行上下文。当在全局上下文中调用执行一个函数时,程序流就进入该被调用函数内,此时引擎就会为该函数创建一个新的执行上下文,并且将其压入到执行上下文堆栈的顶部。浏览器总是执行当前在堆栈顶部的上下文,一旦执行完毕,该上下文就会从堆栈顶部被弹出,然后,进入其下的上下文执行代码。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。

VO——变量对象|AO——活动对象

 

VO

每一个EC都对应一个变量对象VO,在该EC中定义的所有变量和函数都存在其对应的VO中。

VO分为全局上下文VO(全局对象,Global Object,我们通常说的Global对象)和函数上下文的AO

VO: {// 上下文中的数据 (变量声明(var), 函数声明(FD), 函数形参(function arguments))
}
  • 进入执行上下文时,VO的初始化过程具体如下:
  1. 函数的形参(当进入函数执行上下文时)—— 变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined
  2. 函数声明(FunctionDeclaration, FD) —— 变量对象的一个属性,其属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则替换它的值
  3. 变量声明(var,VariableDeclaration) —— 变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的属性。

注意:改过程是有先后顺序的。

  • 执行代码阶段时,VO中的一些属性undefined值将会确定。

AO

在函数的执行上下文中,VO是不能直接访问的。它主要扮演被称作活跃对象(activation object)(简称:AO)的角色。

这句话怎么理解呢,就是当EC环境为函数时,我们访问的是AO,而不是VO。

VO(functionContext) === AO;

AO是在进入函数的执行上下文时创建的,并为该对象初始化一个arguments属性,该属性的值为Arguments对象。

AO = {arguments: {callee:,length:,properties-indexes: //函数传参参数值
  }
};

FD的形式只能是如下这样:

function f(){}

示例

 

 

VO示例

alert(x); // functionvar x = 10;
alert(x); // 10
 
x = 20;function x() {};alert(x); // 20

进入执行上下文时:

ECObject={VO:{x:<reference to FunctionDeclaration "x">}
};

执行代码时:

ECObject={VO:{x:20 //与函数x同名,替换掉,先是10,后变成20
  }
};

对于以上的过程,我们详细解释下。

在进入上下文的时候,VO会被填充函数声明;同一阶段,还有变量声明 ” X ”,但是,正如此前提到的,变量声明是在函数声明和函数形参之后,并且,变量声明不会对已经存在的统一名字的函数声明和函数形参发生冲突。因此,在进入上下文的阶段,VO填充如下形式:

VO = {};VO['x'] = <引用了函数声明'x'>// 发现var x = 10;
// 如果函数“x”还未定义
// 则 "x" 为undefined, 但是,在我们的例子中
// 变量声明并不会影响同名的函数值
 
VO['x'] = <值不受影响,仍是函数>

执行代码阶段,VO被修改如下:

VO['x'] = 10;
VO['x'] = 20;

如下例子再次看到在进入上下文阶段,变量存储在VO中(因此,尽管else的代码块永远都不会执行到,而“b”却仍然在VO中)

if (true) {var a = 1;
} else {var b = 2;
}alert(a); // 1
alert(b); // undefined, but not "b is not define
 

AO示例

unction test(a, b) {var c = 10;function d() {}var e = function _e() {};(function x() {});
}test(10); // call

当进入test(10)的执行上下文时,它的AO为:

testEC={AO:{arguments:{callee:testlength:1,0:10},a:10,c:undefined,d:<reference to FunctionDeclaration "d">,e:undefined}
};

由此可见,在建立阶段,VO除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。函数表达式不会对VO造成影响,因此,(function x() {})并不会存在于VO中。

当执行test(10)时,它的AO为:

testEC={AO:{arguments:{callee:test,length:1,0:10},a:10,c:10,d:<reference to FunctionDeclaration "d">,e:<reference to FunctionDeclaration "e">}
};

可见,只有在这个阶段,变量属性才会被赋具体的值。

作用域链

在执行上下文的作用域中查找变量的过程被称为标识符解析(indentifier resolution),这个过程的实现依赖于函数内部另一个同执行上下文相关联的对象——作用域链。作用域链是一个有序链表,其包含着用以告诉JavaScript解析器一个标识符到底关联着那一个变量的对象。而每一个执行上下文都有其自己的作用域链Scope。

一句话:作用域链Scope其实就是对执行上下文EC中的变量对象VO|AO有序访问的链表。能按顺序访问到VO|AO,就能访问到其中存放的变量和函数的定义。

Scope定义如下:

Scope = AO|VO + [[Scope]]

其中,AO始终在Scope的最前端,不然为啥叫活跃对象呢。即:

Scope = [AO].concat([[Scope]]);

这说明了,作用域链是在函数创建时就已经有了。

那么[[Scope]]是什么呢?

[[Scope]]是一个包含了所有上层变量对象的分层链,它属于当前函数上下文,并在函数创建的时候,保存在函数中。

[[Scope]]是在函数创建的时候保存起来的——静态的(不变的),只有一次并且一直都存在——直到函数销毁。 比方说,哪怕函数永远都不能被调用到,[[Scope]]属性也已经保存在函数对象上了。

var x=10;
function f1(){var y=20;function f2(){return x+y;}
}

以上示例中,f2的[[scope]]属性可以表示如下:

f2.[[scope]]=[f2OuterContext.VO
]

f2的外部EC的所有上层变量对象包括了f1的活跃对象f1Context.AO,再往外层的EC,就是global对象了。
所以,具体我们可以表示如下:

f2.[[scope]]=[f1Context.AO,globalContext.VO
]

对于EC执行环境是函数来说,那么它的Scope表示为:

functionContext.Scope=functionContext.AO+function.[[scope]]

注意,以上代码的表示,也体现了[[scope]]和Scope的差异,Scope是EC的属性,而[[scope]]则是函数的静态属性。

(由于AO|VO在进入执行上下文和执行代码阶段不同,所以,这里及以后Scope的表示,我们都默认为是执行代码阶段的Scope,而对于静态属性[[scope]]而言,则是在函数声明时就创建了)

对于以上的代码EC,我们可以给出其Scope的表示:

exampelEC={Scope:[f2Context.AO+f2.[[scope]],f1.context.AO+f1.[[scope]],globalContext.VO]
}

接下来,我们给出以上其它值的表示:

  • globalContext.VO
globalContext.VO={x:10,f1:<reference to FunctionDeclaration "f1">
}
  • f2Context.AO
f2Context.AO={f1Context.AO={arguments:{callee:f1,length:0},y:20,f2:<reference to FunctionDeclaration "f2">}
}
  • f2.[[scope]]
f2Context.AO={f1Context.AO:{arguments:{callee:f1,length:0},y:20,f2:<reference to FunctionDeclaration "f2">},globalContext.VO:{x:10,f1:<reference to FunctionDeclaration "f1">}
}
  • f1.[[scope]](f1的所有上层EC的VO)
f1.[[scope]]={globalContext.VO:{x:undefined,f1:undefined}
}

好,我们知道,作用域链Scope呢,是用来有序访问VO|AO中的变量和函数,对于上面的示例,我们给出访问的过程:

  • x,f1
- "x"
-- f2Context.AO // not found
-- f1Context.AO // not found
-- globalContext.VO // found - 10

f1的访问过程类似。

  • y
- "y"
-- f2Context.AO // not found
-- f1Context.AO // found -20

我们发现,在变量和函数的访问过程,并没有涉及到[[scope]],那么[[scope]]存在的意义是什么呢?

这个还是看下一篇文章吧。

总结

  1. EC分为俩个阶段,进入执行上下文和执行代码。
  2. ECStack管理EC的压栈和出栈。
  3. 每个EC对应一个作用域链,VO|AO(AO,VO只能有一个),this。
  4. 函数EC中的Scope在进入函数EC是创建,用来有序方位该EC对象AO中的变量和函数。
  5. 函数EC中的AO在进入函数EC时,确定了Arguments对象的属性;在执行函数EC时,其它变量属性具体化。
  6. 函数的[[scope]]属性在函数创建时就已经确定,并保持不变。

 

 

转自:https://segmentfault.com/a/1190000000533094#articleHeader1

转载于:https://www.cnblogs.com/oneweek/p/8044045.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/428456.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java线程interrupt用法_Java 如何中断线程

本篇文章帮大家学习java 如何中断线程&#xff0c;包含了Java 如何中断线程使用方法、操作技巧、实例演示和注意事项&#xff0c;有一定的学习价值&#xff0c;大家可以用来参考。以下实例演示了如何使用interrupt()方法来中断线程并使用 isInterrupted() 方法来判断线程是否已…

laravel5.4之artisan使用总结一

Artisan是laravel自带的命令行接口&#xff1a; php artisan list 编写命令 生成命令&#xff1a; 可以使用Artisan命令&#xff0c;php artisan make:command ConsoleTest 执行完这个命令后&#xff0c;会在app/Console/Commands 目录下创建ConsoleTest命令类。会包含默认的属…

java如何保证类不被回收_垃圾回收机制保证了Java程序不会出现内存溢出。( )

【简答题】1.激素(名词解释)【单选题】6.下列哪种情况下可引起ADH分泌增加【判断题】在Java中使用String类型的实例对象表示一个字符串。( )【判断题】static关键字可以修饰成员变量,也可以修饰局部变量。( )【单选题】建设项目投资控制应贯穿于建设工程全过程,在建设项目实施阶…

intellij idea 分屏设置 与快捷键

1、找到分屏功能 File -> setting -> keymap&#xff0c;搜索&#xff08;注意大小写&#xff09;&#xff1a;   Split Vertically 水平分屏   Split Horizontally 垂直分屏 2、设置快捷键&#xff0c; 编辑快捷键的地方在搜索框同一行&#xff1a;    在标签上直…

java parseexception_Java ParseException类代码示例

import com.sun.org.apache.xerces.internal.impl.xpath.regex.ParseException; //导入依赖的package包/类Overridepublic void read(File file) throws IOException {try {Document doc db.parse(file);NodeList nlTimeSlots (NodeList) xp.evaluate("/ANNOTATION_DOCU…

bzoj 4016: [FJOI2014]最短路径树问题

Description 给一个包含n个点&#xff0c;m条边的无向连通图。从顶点1出发&#xff0c;往其余所有点分别走一次并返回。 往某一个点走时&#xff0c;选择总长度最短的路径走。若有多条长度最短的路径&#xff0c;则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11&am…

java.nio.file 找不到_java - 断言该错误:无法访问路径(找不到java.nio.file.Path) - 堆栈内存溢出...

我想使用Robolectric进行单元测试&#xff0c;但是我正在尝试使用robolectric进行简单测试&#xff0c;因此一开始我很困惑。 我遵循了手册&#xff0c;对示例进行了同样的操作&#xff0c;甚至其他帖子也无济于事。 每次收到错误消息&#xff1a;无法访问路径。 找不到java.ni…

SSH整合方案二(不带hibernate.cfg.xml)

整体结构: 1.引入相关jar包 2.编写实体类和映射文件 package cn.zqr.domain;public class Customer {private Long cust_id;private String cust_name;private Long cust_user_id;private Long cust_create_id;private String cust_source;private String cust_industry;privat…

java铃声类_MediaPlayer.setDataSource中的java.lang.IllegalStateException,使用铃声类

我有一个用户(Samsung Galaxy S5&#xff0c;Android 4.4)报告的崩溃&#xff0c;我不明白发生了什么。这似乎是可以吸收的&#xff0c;但也许有些人遇到了同样的问题&#xff0c;或类似的问题。MediaPlayer.setDataSource中的java.lang.IllegalStateException&#xff0c;使用…

POJ 1742 Coins ( 经典多重部分和问题 DP || 多重背包 )

题意 : 有 n 种面额的硬币&#xff0c;给出各种面额硬币的数量和和面额数&#xff0c;求最多能搭配出几种不超过 m 的金额&#xff1f; 分析 : 这题可用多重背包来解&#xff0c;但这里不讨论这种做法。 如果之前有接触过背包DP的可以自然想到DP数组的定义 > dp[i][j] 表示…

java nextintln_java – 从lambda表达式引用的局部变量必须是final或者有效的final

我有一个JavaFX 8程序(适用于JavaFXPorts交叉平台),它实际上是为了做我想做的事情,但却是一步之遥.程序读取文本文件,对行进行计数以建立随机范围,从该范围中选取一个随机数并读取该行以进行显示.The error is: local variables referenced from a lambda expression must be f…

css用hover制作下拉菜单

首先我们的需求就是 制作一个鼠标移动到某个区域就会有下拉菜单的弹出,这样会有更多的子类内容,示例代码如下: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>*{mar…

java链式调用空指针_java 链式调用

前言现在很多开源库或者代码都会使用链式调用。因为链式调用在很多时候&#xff0c;都可以使我们的代码更加简洁易懂。以下Student类有多数个属性&#xff0c;让我们看看非链式调用和链式调用有何区别。非链式调用Main类&#xff1a;/*** Created by chenxuxu on 17-1-10.*/pub…

mysql 字典索引_【大白话mysql】你真的了解 mysql 索引吗?

本文来源于公众号&#xff1a; 跬步匠心什么是索引&#xff1f;当我们使用汉语字典查找某个字时&#xff0c;我们会先通过拼音目录查到那个字所在的页码&#xff0c;然后直接翻到字典的那一页&#xff0c;找到我们要查的字&#xff0c;通过拼音目录查找比我们拿起字典从头一页一…

java菜单面板设置完能关闭_用Java创建一个屏幕外框架(或者:当所有应用程序窗口关闭时,如何避免Mac上的空白菜单)?...

你一定要考虑WizardOfOdds非常有用的答案.正确使用“The Application Menu”将有所帮助,并且很容易设置最小的Info.plist开始.持久的文件菜单将允许您的应用程序在其他人关闭时打开一个新窗口.这个answer链接到一个简单的example.虽然Apple的Human Interface Guidelines是您用户…

mysql使用jtable_jtable 的简单使用

做后台管理管理系统时&#xff0c;基于ajax的数据操作和富有表现力的数据绑定插件jtable绝对是一个不错的选择&#xff0c;他接收来自服务器端的json格式的数据。而且他是一款开源的基于jquery和jquery ui的插件&#xff0c;您可以根据自己的需要修改其表现&#xff0c;如css&a…

java正则表达 w_正则表达式[\s\S] 与[\w\W]这样的用法,比较.

更新时间&#xff1a;2020年03月04日 17:29:41 投稿&#xff1a;mdxy-dxy很多人咨询[\s\S]*与[\w\W]* 什么意思&#xff0c;不是有.了吗&#xff0c;那么它们的组合&#xff0c;表示所有的都匹配&#xff0c;与它相对应的&#xff0c;有[\w\W]等&#xff0c;意义完全相同,需要…

dalvik虚拟机与Java区别_05 Android---java虚拟机跟dalvik虚拟机的区别(从01开始点点入门,视频+笔记)...

大家好,我是森森这节课我简单给大家介绍一下java虚拟机跟安卓系统虚拟机的区别.可能时间太晚了,有点不在状态,也有点紧张,大家见谅.05java虚拟机跟dalvik虚拟机的区别这篇文章重点写一写安卓运行时和Java运行时的区别.什么是运行时呢?其实我们所说的虚拟机就是运行时.两种虚拟…

java自定义菜单跳转页面_微信公众号开发 自定义菜单跳转页面并获取用户信息实例详解...

微信公众号开发 自定义菜单请先读完本文再进行配置开发请先前往微信平台开发者文档阅读“网页授权获取用户基本信息”的接口说明在微信公众账号开发中&#xff0c;往往有定义一个菜单&#xff0c;然后用户点击该菜单就进入用户个人中心的功能&#xff0c;通常应用于各个公众账号…

团队项目冲刺第一阶段03

回顾昨天:用了各种FQ软件下载SDK&#xff0c;最后终于在11点半后成功加载出helloworld 今天的任务:学习安卓数据库&#xff0c;实现对数据库信息的添加转载于:https://www.cnblogs.com/cairsha/p/8084694.html