目录

  • 0.写在开头
  • 1.执行上下文
  • 2.VO
  • 3.AO
  • 4.Scope
  • 5.闭包

0.写在开头


本文将秉承能写代码就不多BB的原则,争取将执行上下文VOAOScope[[scope]]作用域作用域链这些晦涩抽象的概念用伪代码来清晰表述出来,用以强化理解和记忆。

若有写的不对的地方,还请大佬们在评论区批评指正!

那么,让我们开始吧~

1.执行上下文


JS引擎在执行一段代码前,会先创建对应的执行上下文(EC,Execution Context),该执行上下文负责存储VOAOScopethis。同时也创建执行上下文栈(ECStack,Execution Context Stack)来管理执行上下文的推入和弹出

不多BB,来看代码

let a = 1
function fn1(){fn2()
}
function fn2(){console.log(a)
}
fn1()
// 在执行fn1()时,执行上下文栈操作如下
ECStack.push(globalEC) // globalEC即全局上下文
ECStack.push(fn1EC)
ECStack.push(fn2EC)// 执行完毕,出栈操作如下
ECStack.pop() // 弹出fn2EC并销毁
ECStack.pop() // 弹出fn1EC并销毁
// globalEC会一直保留,直到程序退出

2.VO


VO即Variable Object 变量对象,定义在全局执行上下文(globalEC)中,存储全局变量函数

来看代码

let a = 1
let arr = [1,2,3]
let obj = {id:107}
function fn(){ ... }// globalEC
globalEC = {VO:{a: 1,arr: [1,2,3]obj: {id:107}fn: function fn(){ ... }}
}

3.AO


AO即Activation Object 活跃对象,定义在函数执行上下文(fnEC)中(准确来说,在函数开始执行时才创建),存储局部变量子函数以及arguments

不多BB,来看代码

function fn(a,b){var c = 3,var fn2 = function(){let d = 4console.log(a+b+c+d)}fn2()
}
fn(1,2) // 10// fn函数开始执行前,创建fnEC
fnEC = {AO:{arguments:{'0':1,'1':2,length:2},a:1,b:2,c:undefined, fn2:undefined, }
}
// 将fnEC推入执行上下文栈
ECStack.push(fnEC) // [globalEC,fnEC]// fn函数执行的过程中慢慢填装AO
fnEC = {AO:{arguments:{ ... },a:1,b:2,c:3,fn2:function(){ ... }}
}
// 执行内部函数fn2,也是如此
fn2EC = {AO:{arguments:{length:0},d:4}
}
// 将fn2EC推入栈
ECStack.push(fn2EC) // [globalEC,fnEC,fn2EC]// 执行fn2结束
ECStack.pop() // fn2EC销毁
// 执行fn结束
ECStack.pop() // fnEC销毁

4.Scope


Scope定义在执行上下文中,就是所谓的作用域,存储在其中的一个个AOVO按队列顺序链接成了所谓的作用域链
在开始介绍Scope前,先介绍与其关联的[[scope]]

[[scope]]定义在函数中,在函数创建时会保存当前父级函数的[[scope]]以及父级函数执行上下文的AO;若为全局函数,那么保存的是当前全局执行上下文的VO

文字介绍比较晦涩,所以不多bb,写代码就完事了,先来看[[scope]]

let a = 1
function fn(){let b = 2function fn2(){let c = 3function fn3(){console.log(a,b,c)}}fn2()
}
fn()// 创建fn函数时,定义fn.[[scope]]
fn.[[scope]] = [globalEC.VO]
// 执行fn函数,在其内部创建fn2函数时,定义fn2.[[scope]]
fn2.[[scope]] = [fnEC.AO, ...fn.[[scope]] ]
// 执行fn2函数,在其内部创建fn3函数时,定义fn3.[[scope]]
fn3.[[scope]] = [fn2EC.AO,...fn2.[[scope]] ] // [fn2EC.AO, fn1EC.AO, globalEC.VO]

Scope定义在执行上下文,[[scope]]定义在函数中,二者关系如下:

fnEC.Scope = [ fnEC.AO, ...fn.[[scope]] ]

不多BB,继续coding,这次串联上前面介绍的ECECStackVOAO[[scope]],只要搞懂了这些,作用域、作用域链、作用域链查询这些概念就是小case

// 执行代码如下
let a = 0
function fn3(){ console.log(a) }
function fn1(b,c,d){let a = 1fn3()function fn2(b){let c = 8console.log(a,b,c,d)  }fn2(7)
}
fn1(2,3,4) // 输出: 0  1,7,8,4// 咳咳... 那么重头戏来了
// 详细分析如下(建议把上述执行的代码截图,对照着看以下的伪代码分析过程)
ECStack.push(globalEC) // 推入全局执行上下文globalEC.VO.a = 1
globalEC.VO.fn3 = function(){ ... } // VO存储fn3
fn3.[[scope]] = [globalEC.VO] // fn3创建时定义[[scope]]globalEC.VO.fn = function(){ ... }
fn.[[scope]] = [globalEC.VO]fnEC = { // 在执行fn前创建fnECAO:{arguments:{'0':2,'1':3,'2':4,length:3},b:2,c:3,d:4,a:undefined,fn2:function(){ ... }}
}
ECStack.push(fnEC) // 推入fnEC [globalEC, fnEC]fn1.AO.a = 1 // 执行let a = 1才会给AO中的a赋值fn3EC = { // 执行fn3前创建fn3ECAO:{arguments:{length:0} },Scope:[ fn3EC.AO, ...fn3.[[scope]] ] // [ fn3EC.AO, globalEC.VO ]
}
ECStack.push(fn3EC) // 推入fn3EC [globalEC, fnEC, fn3EC]// 执行console.log(a),会从当前执行上下文即fn3EC内部的Scope查询,沿着作用域链逐级往上查找a
fn3EC.Scope.forEach(scope=>{if(scope.hasOwnProperty('a')){console.log(scope['a']) // 找到的是: globalEC.VO.a即0break}
)ECSTack.pop() // fn3EC销毁,[globalEC, fnEC] fn2.[[scope]] = [fn1EC.AO, ...fn1.[[scope]] ] // 创建fn2时定义[[scope]]
fn2EC = { // 执行fn2前创建执行上下文AO:{arguments:{ '0':7,length:1},b:7,c:undefined},Scope:[ fn2EC.AO, ...fn2.[[scope]] ] // [fn2EC.AO,fn1EC.AO,globalEC.VO]
}
ECStack.push(fn2EC) // [globalEC, fnEC, fn2EC]
fn2EC.AO.c = 8
// 执行console.log(a,b,c,d),从当前执行上下文即fn2EC的Scope查询
fn2EC.Scope.forEach(scope=>{if(scope.hasOwnProperty('a')){console.log(scope['a']) // 找到的是: fn1EC.AO.a即1break}// 依此类推// 找到 fn2EC.AO.b即7// 找到 fn2EC.AO.c即8// 找到 fn1.AO.d即4
)ECStack.pop() // 销毁fn2EC, [globalEC, fnEC]
ECStack.pop() // 销毁fnEC, [globalEC]// 直到用户关闭当前页面,globalEC销毁,ECStack也销毁

5.闭包


在搞懂了前面的东西,那么理解闭包就是信手拈来,其本质上就是返回的内部函数的[[scope]]保存了下来

不多bb,来看代码

function fn1(){let a = 1function fn2(){let b = 2console.log(a,b)}return fn2
}
let fn = fn1()
fn() // 1,2// 执行fn1函数,在创建fn2时,会定义fn2.[[scope]]
fn2.[[scope]] = [ fn1EC.AO, ...fn1.[[scope]] ]
// fn1执行完毕,将fn2 return出去,这时候fn = fn2 --> fn.[[scope]] = fn2.[[scope]],故fn2的[[scope]]保留了下来,可以访问fn1EC.AO和fn1.[[scope]]
// 在执行fn时,创建的执行上下文是这样的
fnEC = {AO:{arguments:{ ... },b:2}Scope:[fnEC.AO, ...fn.[[scope]] ] // [fnEC.AO, fn1EC.AO, globalEC.VO]
}
// 通过fn1EC.AO可以访问fn1函数内部的变量a

再来看一个老掉牙的闭包面试题

var data = []
for (var i = 0; i < 3; i++) {data[i] = (function (i) {return function(){console.log(i)}})(i)
}data[0]() // 0
data[1]() // 1
data[2]() // 2// 我们来分析data[1]
父函数.[[scope]] = [ globalEC.VO ]
父函数EC = { // 注意每次循环父函数都不一样AO:{arguments:{'0':1,length:1},i:1},Scope:[ 匿名函数EC.AO, globalEC.VO ]
}
返回的函数.[[scope]] = [ 父函数EC.AO, globalEC.VO ] // 保存了父函数的Scope
返回的函数EC={AO:{arguments:{length:0}},Scope:[自身AO,父函数EC.AO, globalEC.VO] // 这时i变量从父函数EC.AO中获取
}

一文彻底搞懂执行上下文、VO、AO、Scope、[[scope]]、作用域链、闭包相关推荐

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

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

  2. 25、搞懂闭包、作用域、执行期上下文(VO、AO)、作用域链

    25.1 闭包 闭包是指有权访问另一个函数作用域中的变量的函数--js高程 简单来说闭包就是函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包. 25.2 作 ...

  3. 一文快速搞懂Kudu到底是什么

    文章目录 引言 文章传送门: Kudu 介绍 背景介绍 新的硬件设备 Kudu 是什么 Kudu 应用场景 Kudu 架构 数据模型 分区策略 列式存储 整体架构 Kudu Client 交互 Kud ...

  4. 奇舞周刊第 440 期:一文彻底搞懂前端沙箱

    记得点击文章末尾的" 阅读原文 "查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 一文彻底搞懂前端沙箱‍ 沙箱是一种安全机制,为运行中‍的程序提供隔离环境.通常 ...

  5. layer output 激活函数_一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)...

    在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...

  6. 一文彻底搞懂前端监控 等推荐

    大家好,我是若川.话不多说,这一次花了几个小时精心为大家挑选了20余篇好文,供大家阅读学习.本文阅读技巧,先粗看标题,感兴趣可以都关注一波,一起共同进步. 前端点线面 前端点线面 百度前端研发工程师, ...

  7. opc服务器是硬件吗,opc是什么(一文彻底搞懂什么是OPC)

    原标题:(opc是什么(一文彻底搞懂什么是OPC)) opc是什么(一文完全搞懂什么是OPC)从2000年终以来,我们就一直在运用OPC软件互操纵性范例,而那些正准备踏入和想要踏入工业自动化范畴的人们 ...

  8. 一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)

    在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...

  9. 一文彻底搞懂Mybatis系列(十六)之MyBatis集成EhCache

    MyBatis集成EhCache 一.MyBatis集成EhCache 1.引入mybatis整合ehcache的依赖 2.类根路径下新建ehcache.xml,并配置 3.POJO类 Clazz 4 ...

最新文章

  1. Chapter 7. 对话框控件
  2. 将h5用HBuilderX打包成安卓app后,document.documentElement.scrollTop的值始终为0或者document.body.scrollTop始终为0...
  3. DEDEv5.6跳转网址修改成直链地址而非动态跳转
  4. 巨头入局,华为云将给音视频行业带来什么?
  5. ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)
  6. LeetCode 1566. 重复至少 K 次且长度为 M 的模式
  7. 【MATLAB技巧】——求解符号方程
  8. Python中星号、下画线、斜线含义汇总
  9. makefile--嵌套执行(四)
  10. 小米集团王嵋因错误表达致歉并请辞;亚马逊云服务出现中断,许多网站受到影响;deepin 深度系统更新发布|极客头条...
  11. CCCC-GPLT L1-038. 新世界 团体程序设计天梯赛
  12. ADC学习(4)—— 电压比较器
  13. python去重txt文本_Python实现的txt文件去重功能示例
  14. arduino下载库出错_纯干货!关于Arduino 库在多种操作系统安装使用最详细、最全面的指南及常见问题解决办法!...
  15. ubuntu16.04中运行orb-slam2_with_semantic_labelling-master
  16. 局域网常见攻击方式原理
  17. css做三角形横线加小三角,CSS创建三角形(小三角)的几种方法
  18. kindeditor图片上传 jsp版
  19. 前端微信小程序资讯类仿今日头条微信小程序
  20. Google结构化数据

热门文章

  1. 交互式系统中采用的调度算法
  2. 前端培训出来的人,公司怎么看呢?
  3. 【贪心】Bin Packing
  4. Gift to XBACK(小小礼物)
  5. 远程桌面协议报错0xd06解决方案
  6. Lagrange插值法
  7. javascript求1~100的素数和
  8. 双循环是什么意思c语言,什么是双循环
  9. cisco 2900企业级系列路由器初始配置
  10. 阿里JVM大神创业之路-离开是为了更好的开始