Js 作用域与作用域链与执行上下文不得不说的故事 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
最近在研究Js,发现自己对作用域,作用域链,活动对象这几个概念,理解得不是很清楚,所以拜读了@田小计划大神的博客与其他文章,受益匪浅,写这篇随笔算是自己的读书笔记吧~。
作用域
首先明确一个概念,js只有函数作用域(function-based),没有块级作用域,也就是只有函数会有自己的作用域,其他都没有。
接着,作用域分为全局作用域与局部作用域。
全局作用域中的对象可以在代码的任何地方访问,一般来说,下面情况的对象会在全局作用域中:
- 最外层函数和在最外层函数外面定义的变量
- 没有通过关键字"var"声明的变量
- 浏览器中,window对象的属性
局部作用域又被称为函数作用域(Function scope),所有的变量和函数只能在作用域内部使用。

var foo = 1;
window.bar = 2; function baz(){
a = 3;
var b = 4;
}
// Global scope: foo, bar, baz, a
// Local scope: b
在创建这个函数的时候,这个函数的作用域与作用域链(函数的作用域链将会在运行时用到)就已经决定了,而是不是在调用的时候,这句话至管重要。
作用域链
每一个Javascript函数都被表示为对象,它是一个函数实例。它包含我们编程定义的可访问属性,和一系列不能被程序访问,仅供Javascript引擎使用的内部属性,其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义。
内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可以由函数访问。此函数中作用域链中每个对象被称为一个可变对象,以“键值对”表示。当一个函数创建以后,它的作用域链被填充以这些对象,它们代表创建此函数的环境中可访问的数据:
1 function add(num1, num2){
2 var sum = num1 + num2;
3 return sum;
4 }
当add()函数创建以后,它的作用域链中填入了一个单独可变对象,此全局对象代表了所有全局范围定义的变量。此全局对象包含诸如窗口、浏览器和文档之类的访问接口。如下图所示:(add()函数的作用域链,注意这里只画出全局变量中很少的一部分)

add函数的作用域链将会在运行时用到,假设运行了如下代码:
1 var total = add(5,10);
运行此add函数时会建立一个内部对象,称作“运行期上下文”(execution context),一个运行期上下文定义了一个函数运行时的环境。且对于单独的每次运行而言,每个运行期上下文都是独立的,多次调用就会产生多此创建。而当函数执行完毕,运行期上下文被销毁。
一个运行期上下文有自己的作用域链,用于解析标识符。当运行期上下文被创建的时,它的作用域被初始化,连同运行函数的作用域链[[Scope]]属性所包含的对象。这些值按照它们出现在函数中的顺序,被复制到运行期上下文的作用域链中。这项工作一旦执行完毕,一个被称作“激活对象”的新对象就创建好了。此激活对象作为函数执行期一个可变对象,包含了访问所有局部变量,命名参数,参数集合和this的接口。然后,此对象被推入到作用域链的最前端。当作用域链被销毁时,激活对象也一同被销毁。如下所示:(运行add()时的作用域链)

在函数运行的过程中,每遇到一个变量,就要进行标识符识别。标识符识别这个过程要决定从哪里获得数据或者存取数据。此过程搜索运行期上下文的作用域链,查找同名的标识符。搜索工作从运行函数的激活目标的作用域前端开始。如果找到了,就使用这个具有指定标识符的变量;如果没找到,搜索工作将进入作用域链的下一个对象,此过程持续运行,直到标识符被找到或者没有更多可用对象可用于搜索,这种情况视为标识符未定义。正是这种搜索过程影响了性能。如果想了解如何编写高性能的js,建议看看这篇blog,这个小结也是摘自于它——http://www.cnblogs.com/coco1s/p/4017544.html
执行上下文
在JavaScript中有三种代码运行环境:
- Global Code
- JavaScript代码开始运行的默认环境
- Function Code
- 代码进入一个JavaScript函数
- Eval Code
- 使用eval()执行代码
为了表示不同的运行环境,JavaScript中有一个执行上下文(Execution context,EC)的概念。也就是说,当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)。
执行上下文包含三个重要的概念,彼此联系且不好理解,导致了新手很难做到一次理解清楚,如下图所示:

每个执行上下文都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this,当然还有一些附加的属性。

当一段JavaScript代码执行的时候,JavaScript解释器会创建Execution Context,其实这里会有两个阶段:
- 创建阶段(当函数被调用,但是开始执行函数内部代码之前)
- 创建Scope chain
- 创建VO/AO(variables, functions and arguments)
- 设置this的值
- 激活/代码执行阶段
- 设置变量的值、函数的引用,然后解释/执行代码
这里想要详细介绍一下"创建VO/AO"中的一些细节,因为这些内容将直接影响代码运行的行为。
对于"创建VO/AO"这一步,JavaScript解释器主要做了下面的事情:
- 根据函数的参数,创建并初始化arguments object
- 扫描函数内部代码,查找函数声明(Function declaration)
- 对于所有找到的函数声明,将函数名和函数引用存入VO/AO中
- 如果VO/AO中已经有同名的函数,那么就进行覆盖
- 扫描函数内部代码,查找变量声明(Variable declaration)
- 对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为"undefined"
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
看下面的例子:

function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);

对于上面的代码,在"创建阶段",可以得到下面的Execution Context object:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}

在"激活/代码执行阶段",Execution Context object就被更新为:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
总结
函数在定义时就会确定他的作用域与作用域链(静态),只有调用的时候才会创建一个执行上下文,其中包含了调用时的形参,其中的函数声明与变量(VO), 同时创建活动对象(AO),并将AO压入执行上下文的作用域链的最前端并且包含了this的属性,执行上下文的作用域链是通过正在被调用函数的作用域链得到的(动态)。
以上的理解如有错误,敬请指正,敬礼~
参考
http://www.cnblogs.com/coco1s/p/4017544.html
http://www.cnblogs.com/wilber2013/p/4909459.html
http://www.cnblogs.com/wilber2013/p/4909430.html#_nav_3
Js 作用域与作用域链与执行上下文不得不说的故事 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄的更多相关文章
- js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?
日常在群里讨论一些概念性的问题,比如变量提升,作用域和闭包相关问题的时候,经常会听一些大佬们给别人解释的时候说执行上下文,调用上下文巴拉巴拉,总有点似懂非懂,不明觉厉的感觉.今天,就对这两个概念梳理一 ...
- js的基础(平民理解的执行上下文/调用堆栈/内存栈/值类型/引用类型)
与以前的切图比较,现在的前端开发对js的要求似乎越来越高,在开发中,我们不仅仅是要知道如何运用现有的框架(react/vue/ng), 而且我们对一些基础的知识的依赖越来越大. 现在我们就用平民的方法 ...
- 深入学习JS执行--创建执行上下文(变量对象,作用域链,this)
一.介绍 本篇继上一篇深入理解js执行--单线程的JS,这次我们来深入了解js执行过程中的执行上下文. 本篇涉及到的名词:预执行,执行上下文,变量对象,活动对象,作用域链,this等 二.预执行 在上 ...
- 【机制】js的闭包、执行上下文、作用域链
1.从闭包说起 什么是闭包 一个函数和对其周围状态(词法环境)的引用捆绑在一起,这样的组合就是闭包. 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域. 在 JavaScript 中,每 ...
- JavaScript高级内容:原型链、继承、执行上下文、作用域链、闭包
了解这些问题,我先一步步来看,先从基础说起,然后引出这些概念. 本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解. 1.值类型 & 引用类型 funct ...
- JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包
最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...
- js基础梳理-如何理解作用域和作用域链?
本文重点是要梳理执行上下文的生命周期中的建立作用域链,在此之前,先回顾下关于作用域的一些知识. 1.什么是作用域(scope)? 在<JavaScritp高级程序设计>中并没有找到确切的关 ...
- js通过沿着作用域链还是原型链查找变量
这是一道非常典型的JS闭包问题,结果和具体的解析请看这里. 对于其中的`函数作用域链的问题`博主似乎没有解释清楚,有一些疑问:js中的变量到底是沿着作用域链还是原型链查找呢? 首先,要分清作用域链与原 ...
- 一篇文章看懂JS执行上下文
壹 ❀ 引 我们都知道,JS代码的执行顺序总是与代码先后顺序有所差异,当先抛开异步问题你会发现就算是同步代码,它的执行也与你的预期不一致,比如: function f1() { console.lo ...
随机推荐
- 0_Simple__simpleCallback
学习回调函数的基本概念,并在CUDA的任务流中插入基于CPU的主机函数,作为回调函数使用. ▶ 源代码:没有用到的部分被注释起来了 /*multithreading.h*/ #ifndef MULTI ...
- kafka单机模式部署安装,zookeeper启动
在root的用户下 1):前提 安装JDK环境,设置JAVA环境变量 2):下载kafka,命令:wget http://mirrors.shuosc.org/apache/kafka/0.10.2 ...
- css元素选择器 first-child nth-child
E:first-child 只要E元素是它的父级的第一个子元素,就选中.它需要同时满足两个条件, (1)是"第一个子元素", (2)是"这个子元素刚好是E ...
- ⑩bootstrap组件 导航 使用基础案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- 一个非常有用的函数—COALESCE
很多人知道ISNULL函数,但是很少人知道Coalesce函数,人们会无意中使用到Coalesce函数,并且发现它比ISNULL更加强大,不用再像以前 IsNull 又 IsNull(SqlServe ...
- 一:详解 HTTP 协议
本篇文章篇幅比较长,先来个思维导图预览一下. 一张图带你看完本篇文章 一.概述 1.计算机网络体系结构分层 计算机网络体系结构分层 2.TCP/IP 通信传输流 利用 TCP/IP 协议族进行网络通信 ...
- 能自学成为WEB前端工程师吗?
自学是大家学习一门it技术的时候,都会首先考虑的一种学习方式,web前端开发学习也是一样,但是自学web前端也是让大家充满疑问的一种学习方 自学是大家学习一门it技术的时候,都会首先考虑的一种学习方式 ...
- 图片转换base64数据上传,并且实现预览的简便方法
对于很多新手来说,实现上传图片并且预览功能,都会感到不知所可,然后开始在网站搜索各种各样的图片上传预览插件,但是有的时候我们只是想简单的实现判断格式,以及预览的功能,使用插件的话,会使得项目的资源空间 ...
- shell的逻辑运算符
一.逻辑运算符 逻辑卷标 表示意思 1. 关于档案与目录的侦测逻辑卷标! -f 常用!侦测『档案』是否存在 eg: if [ -f filename ] -d 常用!侦测『目录』是否存在 -b 侦测是 ...
- 9. 使用ZooKeeper Java API编程
ZooKeeper是用Java开发的,3.4.6版本的Java API文档可以在http://zookeeper.apache.org/doc/r3.4.6/api/index.html上找到. Ti ...