- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
被JavaScript的闭包、上下文、嵌套函数、this搞得很头痛,这语言设计的,感觉比较混乱,先勉强理解总结一下😂😂😂.
名称 | 描述 |
---|---|
是什么? | 执行上下文 (execution context) 是 JavaScript 代码被解析和执行时所在环境的抽象概念。每一个函数 执行 的时候都会创建自己的执行上下文,保存了函数运行时需要的信息,并通过自己的上下文来运行函数。 |
干什么用的? | 当然就是运行函数自身的,实现自我价值。 |
有那些种类? | ① 全局上下文 :全局环境最基础的执行上下文,所有不在任何函数内的代码都在这里面。 🔸 浏览器中的全局对象就是 window ,全局作用域下 var 申明、隐式申明的变量都会成为全局属性变量,全局的 this 指向 window 。 🔸 其中会初始化一些全局对象或全局函数,如代码中的 console 、 undefined 、 isNaN ② 函数上下文 :每个函数都有自己的上下文,调用函数的时候创建。可以把全局上下文看成是一个顶级根函数上下文。 ③ eval() 调用内部上下文 : eval 的代码会被编译创建自己的执行上下文,不常用也不建议使用。基于这个特点,有些框架会用 eval() 来实现沙箱Sandbox。 |
保存了什么信息? | 初始化上下文的变量、函数等信息 🔸 thisValue : this 环境对象引用。 🔸 内部(Local)环境 :函数本地的所有变量、函数、参数(arguments)。 🔸 作用域链 :具有访问作用域的其他上下文信息。 |
谁来用? | 执行上下文 由 函数调用栈 来统一保存和调度管理。 |
生命周期 | 创建(入栈) => 执行 => 销毁(出栈) ,函数调用的时候创建,执行完成后销毁。 |
函数调用栈 (Function Call Stack),管理函数调用的一种栈式结构(后进先出 )队列,或称 执行栈 ,存储了当前程序所有执行上下文(正在执行的函数)。最早入栈的理所当然就是程序初始化时创建的 全局上下文 了,他是VIP会员,会一直在栈底,直到程序退出.
🟢函数执行上下文调用流程 (也是函数的生命周期):
function first() {
second(); //调用second()
}
function second() {
}
first();
上面的代码执行过程如下图所示 :
Global
,进入执行栈。 first()
函数,创建其下文并入栈。 first()
函数内部调用了 second()
函数,创建 second()
下文入栈并执行。 second()
函数执行完成并出栈,控制权回到 first()
函数上下文。 first()
函数执行完成并出栈,控制权回到全局上下文。
🌰 再来一个函数调用栈的示例:
var a = 1;
let b = 1;
function FA(x) {
function FB(y) {
function FC(z) {
console.log(a + b + x + y + z);
}
FC(3);
}
FB(2);
}
FA(1); //8
上面函数在执行 FC() 时的函数调用堆栈如下图(Edge浏览器断点调试):
✅ 执行 FC 函数代码时,其 作用域 保留了所有要用到的作用域变量,从自己往上,直到全局对象,闭包就是这么来的! 。
var a = 1;
:var申明的变量会作为全局对象 window
的变量。 let b = 1;
:全局环境申明的变量,任何函数都可以访问,放在全局脚本环境中,可以看做全局的一部分。 ✅ 调用堆栈中有FC、FB、FA,因为是嵌套函数,FB、FA并未结束,所以还在堆栈中,函数执行完毕就会被立即释放抛弃.
📢 函数调用栈容量是有限的 !—— 递归函数 。
递归函数就是一个多层+自我嵌套调用的过程,所以执行递归函数时,会不停的入栈,而没有出栈,循环次数太多会超出堆栈容量限制,从而引发报错。比如下面示例中一个简单的加法递归,在Firefox浏览器中递归1500次,就报错了(InternalError: too much recursion),Edge浏览器是11000次超出调用栈容量(Maximum call stack size exceeded).
❓怎么解决呢?
setTimeout(func,0)
发送到任务队列单独执行。
function add(x) {
if (x <= 0)
return 0;
return x + add(x - 1); //递归求和
}
add(1000); //Firefox:1000可以,1500就报错 InternalError: too much recursion
add(10000);//Edge:10000可以执行,11000就报错 Maximum call stack size exceeded
» Firefox 的调用堆栈:
作用域(scope)就是一套规定变量作用范围(权限),并按此去查找变量的规则。包括 静态作用域 、 动态作用域 ,JavaScript中主要是 静态作用域(词法作用域) .
this
,一般就是基于调用来确定上下文环境的。因此 this
值可以在调用栈上来找,注意的是 this
指向一个 引用对象 ,不是函数本身,也不是其词法作用域。
因此,词法作用域主要作用是规定了变量的访问权限,确定了如何去查找变量,基本规则 :
- 代码位置决定 :变量(包括函数)申明的的地方决定了作用域,跟在哪调用无关。
- 拥有父级权限 :函数(或块)可以访问其外部的数据,如果嵌套多层,则递归拥有父级的作用域权限,直到全局环境。
- 函数作用域 :只有函数可以限定作用域,不能被上级、外部其他函数访问。
- 同名就近使用 :如果有和上级同名的变量,则就近使用,先找到谁就用谁。
- 逐层向上查找 :变量的查找规则就是先内部,然逐级往上,直到全局环境,如果都没找到,则变量
undefined
。
这里的词法作用域,就是前文所说JS变量作用域。而闭包保留了上下文作用域的变量,就是为了实现词法作用域.
❓那词法作用域是怎么实现的呢?——作用域链、闭包 。
父级函数 FA() 执行完成后就出栈销毁了(典型场景就是返回函数) FB() 可以到任何地方执行,那内部函数 FB() 执行的时候到哪里去找父级函数的变量 x 呢?
Closure
_(Closure /ˈkləʊʒə(r)/ 闭包)_对象保存,多个(外部引用)逐级保存到函数上下文的 [[Scope]]
(Scope /skoʊp/ 作用域)集合上,形成 作用域链 。 undefined
function FA(x) {
function FB(y) {
x+=y;
console.log(x);
}
console.dir(FB);
return FB; //返回FB()函数
}
let fb = FA(1); //FA函数执行完成,出栈销毁了
fb(2); //3 //返回的fb()函数保留了他的父级FA()作用域变量x
fb(2); //5 //闭包中的x:我又变大了
fb(2); //7 //同一个闭包函数重复调用,内部变量被改变
📢闭包 简单理解就是,当前环境中存放在指向父级作用域的引用。如果嵌套子函数完全没有引用父级任何变量,就不会产生闭包。不过全局对象是始终存在其作用域链 [[Scope]] 上的.
🌰举个例子 :
var a = 1;
let b = 2;
function FunA(x) {
let x1 = 1;
var x2 = 2;
function FunB(y) {
console.log(a + b + x + x1 + x2 + y);
}
FunB(2);
console.dir(FunB)
}
FunA(1); //9
console.dir(FunA)
上面的代码示例中, FunA() 函数嵌套了 FunB() 函数,如下图 FunB() 函数的 [[Scope]] 集合上有三个对象:
Closure (FunA)
FunA()
函数的闭包,包含他的参数 x
、私有变量 x1
、 x2
。 Script
:Script Scope 脚本作用域(可以当做全局作用域的一部分),存放全局Script脚本环境内可访问的 let
、 const
变量,就是全局作用域内的变量。 var
变量 a
被提升为了全局对象 window
的“属性”了。 Global
:全局作用域对象,就是window,包含了 var
申明的变量,以及未申明的变量。 如果把 FunB() 函数放到外面申明,只在 FunA() 调用,其作用域链就不一样了.
执行上下文的创建过程中会创建对应的词法作用域,包括 词法环境 和 变量环境 .
EnvironmentRecord
:记录变量、函数的申明等信息,只存储函数声明和 let/const
声明的变量。 outer
:对(上级)其他作用域词法环境的引用,至少会包含全局上下文。 var
申明的变量,其他都和词法环境差不多。
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
❗变量查找 :变量查找的时候,是先从词法环境中找,然后再到变量环境。就是优先查找const、let变量,其次才var变量.
换几个角度来总结下,创建执行上下文主要搞定下面三个方面:
① 确定 this 的值(This Binding) :
- 在全局上下文中 ,
this
指向window
。- 函数执行上下文中 ,如果它被一个对象引用调用,那么
this
的值被设置为该对象,否则this
的值被设置为全局对象或undefined
(严格模式下)- call (thisArg)、 apply (thisArg)、 bind (thisArg)会直接指定
thisValue
值。
② 内部环境: 包括 词法环境 和 变量环境 ,就是函数内部的变量、函数等信息,还有参数 arguments 信息.
③ 作用域链 (外部引用):外部的词法作用域存放到函数的 [[Scope]] 集合里,用来查找上级作用域变量.
const
,其次 let
,尽量(坚决)不用 var
。 fun = null;
,尽早被垃圾回收。 let
、 const
、 var
str=''
,不管在哪里都会成为全局变量。 远离JavaScript、远离前端......我以为已经学会了,其实可能还没入门.
值类型变量的生命周期随函数,函数执行完就释放了。 垃圾回收GC (Garbage Collection)内存管理主要针对 引用对象 ,当检测到对象不再会被使用,就释放其内存。 GC是自动运行的,不需干预也无法干预 .
GC 回收一个对象的关键就是——确定他确是一个废物,么有任何地方使用他了,主要采用的方法就是标记清理.
❓什么是可达性?
上图中 FuncA 函数中的局部变量 obj1 ,其值对象 {P} 存放在内存堆中,此时的值对象 {P} 被根变量 obj1 引用了,是可达的.
obj1
也一起随她而去。值对象 {P}
就没有被引用了,就不可达了。 obj1=null;
同样的值对象 {P}
没有被引用了,就不可达了。
GC定期执行垃圾回收的两个步骤:
① 标记阶段 :找到可达对象并标记,实际的算法会更加精细.
- 垃圾收集器找到所有的根,并“标记”(记住)它们。
- 继续遍历并“标记”被根引用的对象。
- ...继续遍历,直到找到所有可达对象并标记。
② 清除阶段 :没有被标记的对象都会被清理删除.
⚠️ 全局变量不会被清理 :属于window的全局变量就是根,始终不会被清理,有背景靠山就是不一样! 。
©️版权申明 :版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处! 原文编辑地址-语雀 。
最后此篇关于JavaScript入门③-函数(2)原理{深入}执行上下文的文章就讲到这里了,如果你想了解更多关于JavaScript入门③-函数(2)原理{深入}执行上下文的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
本文全面深入地探讨了Docker容器通信技术,从基础概念、网络模型、核心组件到实战应用。详细介绍了不同网络模式及其实现,提供了容器通信的技术细节和实用案例,旨在为专业从业者提供深入的技术洞见和实
📒博客首页:崇尚学技术的科班人 🍣今天给大家带来的文章是《Dubbo快速上手 -- 带你了解Dubbo使用、原理》🍣 🍣希望各位小伙伴们能够耐心的读完这篇文章🍣 🙏博主也在学习阶段,如若发
一、写在前面 我们经常使用npm install ,但是你是否思考过它内部的原理是什么? 1、执行npm install 它背后帮助我们完成了什么操作? 2、我们会发现还有一个成为package-lo
Base64 Base64 是什么?是将字节流转换成可打印字符、将可打印字符转换为字节流的一种算法。Base64 使用 64 个可打印字符来表示转换后的数据。 准确的来说,Base64 不算
目录 协程定义 生成器和yield语义 Future类 IOLoop类 coroutine函数装饰器 总结 tornado中的
切片,这是一个在go语言中引入的新的理念。它有一些特征如下: 对数组抽象 数组长度不固定 可追加元素 切片容量可增大 容量大小成片增加 我们先把上面的理念整理在这
文章来源:https://sourl.cn/HpZHvy 引 言 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call
源码地址(包含所有与springmvc相关的,静态文件路径设置,request请求入参接受,返回值处理converter设置等等): spring-framework/WebMvcConfigurat
请通过简单的java类向我展示一个依赖注入(inject)原理的小例子虽然我已经了解了spring,但是如果我需要用简单的java类术语来解释它,那么你能通过一个简单的例子向我展示一下吗?提前致谢。
1、背景 我们平常使用手机和电脑上网,需要访问公网上的网络资源,如逛淘宝和刷视频,那么手机和电脑是怎么知道去哪里去拿到这个网络资源来下载到本地的呢? 就比如我去食堂拿吃的,我需要
大家好,我是飞哥! 现在 iptables 这个工具的应用似乎是越来越广了。不仅仅是在传统的防火墙、NAT 等功能出现,在今天流行的的 Docker、Kubernets、Istio 项目中也经
本篇涉及到的所有接口在公开文档中均无,需要下载 GitHub 上的源码,自己创建私有类的文档。 npm run generateDocumentation -- --private yarn gene
我最近在很多代码中注意到人们将硬编码的配置(如端口号等)值放在类/方法的深处,使其难以找到,也无法配置。 这是否违反了 SOLID 原则?如果不是,我是否可以向我的团队成员引用另一个“原则”来说明为什
我是 C#、WPF 和 MVVM 模式的新手。很抱歉这篇很长的帖子,我试图设定我所有的理解点(或不理解点)。 在研究了很多关于 WPF 提供的命令机制和 MVVM 模式的文本之后,我在弄清楚如何使用这
可比较的 jQuery 函数 $.post("/example/handler", {foo: 1, bar: 2}); 将创建一个带有 post 参数 foo=1&bar=2 的请求。鉴于 $htt
如果Django不使用“延迟查询执行”原则,主要问题是什么? q = Entry.objects.filter(headline__startswith="What") q = q.filter(
我今天发现.NET框架在做计算时遵循BODMAS操作顺序。即计算按以下顺序进行: 括号 订单 部门 乘法 添加 减法 但是我四处搜索并找不到任何文档确认 .NET 绝对 遵循此原则,是否有此类文档?如
已结束。此问题不符合 Stack Overflow guidelines .它目前不接受答案。 我们不允许提出有关书籍、工具、软件库等方面的建议的问题。您可以编辑问题,以便用事实和引用来回答它。 关闭
API 回顾 在创建 Viewer 时可以直接指定 影像供给器(ImageryProvider),官方提供了一个非常简单的例子,即离屏例子(搜 offline): new Cesium.Viewer(
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be
我是一名优秀的程序员,十分优秀!