一文彻底搞懂执行上下文、VO、AO、Scope、[[scope]]、作用域链、闭包
目录
- 0.写在开头
- 1.执行上下文
- 2.VO
- 3.AO
- 4.Scope
- 5.闭包
0.写在开头
本文将秉承能写代码就不多BB
的原则,争取将执行上下文
、VO
、AO
、Scope
、[[scope]]
、作用域
、作用域链
这些晦涩抽象的概念用伪代码来清晰表述出来,用以强化理解和记忆。
若有写的不对的地方,还请大佬们在评论区批评指正!
那么,让我们开始吧~
1.执行上下文
JS引擎在执行一段代码前,会先创建对应的执行上下文(EC,Execution Context)
,该执行上下文负责存储VO
、AO
、Scope
、this
。同时也创建执行上下文栈(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
定义在执行上下文中,就是所谓的作用域
,存储在其中的一个个AO
和VO
按队列顺序链接成了所谓的作用域链
在开始介绍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,这次串联上前面介绍的EC
、ECStack
、VO
、AO
、[[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]]、作用域链、闭包相关推荐
- JavaScript——执行环境、变量对象、作用域链
前言 这几天在看<javascript高级程序设计>,看到执行环境和作用域链的时候,就有些模糊了.书中还是讲的不够具体.通过上网查资料,特来总结,以备回顾和修正. 目录: EC(执行环境或 ...
- 25、搞懂闭包、作用域、执行期上下文(VO、AO)、作用域链
25.1 闭包 闭包是指有权访问另一个函数作用域中的变量的函数--js高程 简单来说闭包就是函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包. 25.2 作 ...
- 一文快速搞懂Kudu到底是什么
文章目录 引言 文章传送门: Kudu 介绍 背景介绍 新的硬件设备 Kudu 是什么 Kudu 应用场景 Kudu 架构 数据模型 分区策略 列式存储 整体架构 Kudu Client 交互 Kud ...
- 奇舞周刊第 440 期:一文彻底搞懂前端沙箱
记得点击文章末尾的" 阅读原文 "查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 一文彻底搞懂前端沙箱 沙箱是一种安全机制,为运行中的程序提供隔离环境.通常 ...
- layer output 激活函数_一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)...
在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...
- 一文彻底搞懂前端监控 等推荐
大家好,我是若川.话不多说,这一次花了几个小时精心为大家挑选了20余篇好文,供大家阅读学习.本文阅读技巧,先粗看标题,感兴趣可以都关注一波,一起共同进步. 前端点线面 前端点线面 百度前端研发工程师, ...
- opc服务器是硬件吗,opc是什么(一文彻底搞懂什么是OPC)
原标题:(opc是什么(一文彻底搞懂什么是OPC)) opc是什么(一文完全搞懂什么是OPC)从2000年终以来,我们就一直在运用OPC软件互操纵性范例,而那些正准备踏入和想要踏入工业自动化范畴的人们 ...
- 一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)
在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...
- 一文彻底搞懂Mybatis系列(十六)之MyBatis集成EhCache
MyBatis集成EhCache 一.MyBatis集成EhCache 1.引入mybatis整合ehcache的依赖 2.类根路径下新建ehcache.xml,并配置 3.POJO类 Clazz 4 ...
最新文章
- Chapter 7. 对话框控件
- 将h5用HBuilderX打包成安卓app后,document.documentElement.scrollTop的值始终为0或者document.body.scrollTop始终为0...
- DEDEv5.6跳转网址修改成直链地址而非动态跳转
- 巨头入局,华为云将给音视频行业带来什么?
- ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)
- LeetCode 1566. 重复至少 K 次且长度为 M 的模式
- 【MATLAB技巧】——求解符号方程
- Python中星号、下画线、斜线含义汇总
- makefile--嵌套执行(四)
- 小米集团王嵋因错误表达致歉并请辞;亚马逊云服务出现中断,许多网站受到影响;deepin 深度系统更新发布|极客头条...
- CCCC-GPLT L1-038. 新世界 团体程序设计天梯赛
- ADC学习(4)—— 电压比较器
- python去重txt文本_Python实现的txt文件去重功能示例
- arduino下载库出错_纯干货!关于Arduino 库在多种操作系统安装使用最详细、最全面的指南及常见问题解决办法!...
- ubuntu16.04中运行orb-slam2_with_semantic_labelling-master
- 局域网常见攻击方式原理
- css做三角形横线加小三角,CSS创建三角形(小三角)的几种方法
- kindeditor图片上传 jsp版
- 前端微信小程序资讯类仿今日头条微信小程序
- Google结构化数据