前言

在《javascript 之执行环境-08》文中说到,当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文(execution context)。对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

词法作用域

在《作用域》中说到JavaScript采用词法作用域也叫静态作用域,这个作用域是在函数编译(js执行前很短的时间内编译)时决定的,而不是函数调用时决定;

这是因为函数(一个function 是Function 的一个实例,这个后面会写到)有一个内部属性 [[scope]],当函数编译时,就会把所有父变量对象保存在内部属性[[scope]]中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

注意:虽然js是浏览器解释执行,但是js也是存在编译(计算机只认识二进制哪里认识你写的abcd),只是跟java .net等语言有点区别,具体可以查看《你不知道javadcript》,上卷,词法作用域这个本上也有详细解释;

作用域链

在《引出作用域链》中说到作用域链本质是一个指向变量对象的指针链表。

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

下面以一个例子看看作用域链是怎么构建的:

 var a=10;
function run(){
var name='Joel';
function say(){
var content='hello',name=' Word'; console.log(content+name+','+a);
}
say();
}
run();//hello Word,10

编译之后函数内部属性

//编译时各自的[[scope]]
run.[[scope]] = [
globalContext.VO //全局变量对象
]; say.[[scope]] = [
run.VO,
globalContext.VO
];

执行函数
函数执行分为两部分

  • 创建上下文对象
  • 代码执行

创建上下文对象,创建vo 变量对象、scope chain 作用域链、this 指针以及把函数对象内部属性[[scope]]的值复制给上下文对象scope chain 属性

run.ec={
VO:{
//变量对象初始化
},
//scope chain :run.[[scope]],
scope chain :globalContext.VO,
this:thisValue
}

执行阶段此时上下文被推入环境栈,VO激活为AO,此时VO 已经初始化完成,此时把当前环境的AO 被插入到scope chain 顶端
即 Scope = AO+[[Scope]]

AO会添加在作用域链的最前面

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

函数开始执行阶段

//执行
run.ec={
AO:{
//变量对象初始化
},
// scope chain:AO+run.[[scope]],
scope chain:AO+globalContext.VO,
this:thisValue
}

作用域链 = (动)活动对象(AO) + (静) scope属性 

动指的是执行的时候的变量对象,静指的是词法作用域,即父级变量对象;

创建过程

以下面的例子为例,结合着之前讲的执行上下文、变量对象、执行上下文栈,来总结下函数执行上下文中作用域链的创建过程:

 var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();

执行过程如下:

1.checkscope 函数被创建/编译,保存父级变量对象到内部属性[[scope]]

checkscope.[[scope]] = [
globalContext.VO
];

2.执行checkscope 函数,此时并不立刻执行,js内部开始做准备工作,创建上下文对象 ,推入执行环境栈

checkscopeContext ={}

第一步:创建上下文对象的作用域链:复制函数内部属性[[scope]]来创建作用域链

checkscopeContext = {
Scope: checkscope.[[scope]],
}

第二步:创建上下文变量对象:一开始只是用 arguments 来初始化变量对象,值为默认值 undefined,继续初始化function 函数、var 变量

checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}

第三步:绑定this 指针 变量对象初始化完成,开始执行函数,此时VO 激活为AO

3.准备工作完成,开始执行函数

执行 checkscope 函数,checkscope 函数执行上下文对象被压入执行上下文栈

ECStack = [
checkscopeContext,
globalContext
];

4.VO激活成AO,将活动对象压入 checkscope 作用域链顶端

checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}

5.随着函数的执行,修改 AO 的属性值

checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}

6.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [
globalContext
];

总结

  • 分析代码的时候,务必回看函数的定义,毕竟是词法作用域。

  • 函数作用域链 = (动)活动对象(AO) + (静)scope属性

javascript 之作用域链-10的更多相关文章

  1. 初探JavaScript(四)——作用域链和声明提前

    前言:最近恰逢毕业季,千千万万的学生党开始步入社会,告别象牙塔似的学校生活.往往在人生的各个拐点的时候,情感丰富,感触颇深,各种对过去的美好的总结,对未来的展望.与此同时,也让诸多的老“园”工看完这些 ...

  2. 从零开始讲解JavaScript中作用域链的概念及用途

    从零开始讲解JavaScript中作用域链的概念及用途 引言 正文 一.执行环境 二.作用域链 三.块级作用域 四.其他情况 五.总结 结束语 引言 先点赞,再看博客,顺手可以点个关注. 微信公众号搜 ...

  3. 理解JavaScript的作用域链

    上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现. 本文就看看Exec ...

  4. javascript 之作用域链-07

    复习作用域 上一节我们说到作用域:是指变量可以访问的范围,他规定了如何查找变量,以及确定当前执行代码对变量的访问权限:也说到静态作用域即词法作用域,是在编译阶段决定变量的引用(由程序定义的位置决定,和 ...

  5. JavaScript系列----作用域链和闭包

    1.作用域链 1.1.什么是作用域 谈起作用域链,我们就不得不从作用域开始谈起.因为所谓的作用域链就是由多个作用域组成的.那么, 什么是作用域呢? 1.1.1作用域是一个函数在执行时期的执行环境. 每 ...

  6. 从函数作用域和块级作用域看javascript的作用域链

    在ES6之前,javascript只有全局作用域和函数作用域.所谓作用域就是一个变量定义并能够被访问到的范围.也就是说如果一个变量定义在全局(window)上,那么在任何地方都能访问到这个变量,如果这 ...

  7. 理解JavaScript中作用域链的关系

    javascript里的关系又多又乱.作用域链是一种单向的链式关系,还算简单清晰:this机制的调用关系,稍微有些复杂:而关于原型,则是prototype.proto和constructor的三角关系 ...

  8. JavaScript的作用域链

    /* js当中 每个函数都是一个执行环境 函数调用函数会进入新的执行环境结束之后再回来当前 作用域链: 在内部的作用域中可以访问和修改外部的变量 在外部作用域不能修改或者访问内部的变量 */ var ...

  9. javascript从作用域链的角度看闭包

    闭包 闭包是一个能访问外部函数定义的变量的函数. 为什么? 当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不再存在父作用域了,这就是作用 ...

随机推荐

  1. python 3.6 +pyMysql 操作mysql数据库

    版本信息:python:3.6 mysql:5.7 pyMysql:0.7.11 ########################################################### ...

  2. STM32W108无线射频模块通用IO接口应用实例

    STM32W108无线射频模块通用IO接口应用实例 本实例编写STM32W108的GPIO測试程序,通过控制GPIO引脚,实现对LED灯的控制. 开发环境与硬件说明 硬件:STM32W108无线开发板 ...

  3. VMWare 虚拟化 Ubuntu 64 (16.04)-- docker 无法链接 pull 镜像 ?(solved)

    背景 根据项目的需要,虚拟化一个Ubuntu OS 来玩 docker,虚拟机选择的是WMWare (VMware-player-14.0.0-6661328); Ubuntu的镜像来自于官网(ubu ...

  4. java多线程编程核心技术——第六章总结

    目录 1.0立即加载/"饿汉式" 2.0延迟加载/"懒汉式" 3.0使用静态内置类实现单例模式 4.0序列化与反序列化的单例模式实现 5.0使用static代码 ...

  5. JAVA入门[21]-Jedis操作redis示例

    本节目标 通过JedisPool获取Jedis示例,并完成对redis 简单的Key-value读写操作. 完整代码结构如下: redis服务端 在本地运行redis-server.exe,然后在re ...

  6. (转)解决jdk1.8中发送邮件失败(handshake_failure)问题

    解决jdk1.8中发送邮件失败(handshake_failure)问题 作者 zhisheng_tian 2016.08.12 22:44* 字数 1573 阅读 2818评论 6喜欢 9 暑假在家 ...

  7. mongodb 3.4 集群搭建:分片+副本集

    mongodb是最常用的nodql数据库,在数据库排名中已经上升到了前六.这篇文章介绍如何搭建高可用的mongodb(分片+副本)集群. 在搭建集群之前,需要首先了解几个概念:路由,分片.副本集.配置 ...

  8. JavaSe:代码块执行顺序

    //执行顺序:(优先级从高到低.)静态代码块>mian方法>构造代码块>构造方法. 其中静态代码块只执行一次.构造代码块在每次创建对象是都会执行. //普通代码块:在方法或语句中出现 ...

  9. 事务处理操作(COMMIT,ROLLBACK)。复制表。更新操作UPDATE实际工作中一般都会有WHERE子句,否则更新全表会影响系统性能引发死机。

    更新操作时两个会话(session)同时操作同一条记录时如果没有事务处理操作(COMMIT,ROLLBACK)则会导致死锁. 复制表:此方法Oracle特有

  10. IOS学习8——常用框架学习汇总

    我们在学习和code过程中经常会用到一些框架,本文将会持续更新最新学习和用到的框架 布局框架: Masonry介绍与使用实践:快速上手Autolayout iOS MJRefresh下拉.上拉刷新自定 ...