创新互联www.cdcxhl.cn八线动态BGP香港云服务器提供商,新人活动买多久送多久,划算不套路!
创新互联专注于铜山企业网站建设,成都响应式网站建设,商城网站制作。铜山网站建设公司,为铜山等地区提供建站服务。全流程按需定制,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务本篇文章给大家分享的是有关什么是JS中的执行上下文、 执行栈、事件循环,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
下面的这些概念,无论是执行上下文
、 还是执行栈
,它在规范中的概念都很抽象,很多内容的理解实际靠的都是想象力,若有错误之处,还请指正。
执行上下文
简而言之,执行上下文(Execution Context)是正在运行的可执行代码所处环境的抽象,用于追踪一段代码中变量的求值。这是我总结过来的概念,可能有些不准确,也可以参考真正的规范定义。
不过总的来说,有三个关键点:
只有可执行代码才会有执行上下文
执行上下文是有状态的:运行状态(Perform)、挂起状态(Suspend)以及恢复(Resume)。处于Perfrom状态的执行上下文称之为运行时执行上下文(Running Execution Context)
执行上下文完全不等价于词法环境,硬说关系,也只是前者引用了后者而已。
执行一个JS脚本时,可以有多个执行上下文存在,但是 运行时上下文只有唯一一个(异步也是如此,至于为什么提了四个……三大天王有四个不是常识么……)。
并且ES规范中规定,可执行代码有下面几个:
全局代码
函数代码
Eval语句
模块代码
换言之,看以下代码:
var g=111 function f(){ console.log(g); for(let i =0; i < 10; i++){ console.log(i); } } f(); /// (*)
如果上面的代码运行,只会产生两个执行上下文:
全局
函数
f
但是如果将标注(*)
的行注释掉,那么最终只有一个执行上下文,因为函数f
根本就不会执行,自然就不会有相应的执行上下文了。里面唯一一个迷惑的是,就是for-loop
了,但它根本不是可执行代码,所以它是函数执行上下文的一部分。
执行上下文的重要组成部分
一个执行上下文,可以抽象为:
ExecutionContext = { State: <> LexEnv = { This:< ... > , OuterEnv:< ... > , DecRec:{ //... identifiername-variable } } VaEnv = { This:< ... > , OuterEnv:< ... > , VarRec:{ //... identifiername-variable } } }
事实上,在一个执行上下文中有两个相当重要的组件:LexicalEnvironmentComponent
(词法环境组件)和VariableEnvironmentComponent
(变量环境组件)。词法环境组件指向当前代码中的词法环境(LexEnv
), 变量环境组件指向当前代码的变量环境(VarEnv
)。
关于执行上下文不得不说的二三事中,有一个很重要的部分就是作用域链,但是在执行上下文中并没有看到相关内容。不过作用域链的确存在,它就在[[Scope]]
内部属性中,通过浏览器可以直接看到。
不过也可以这样理解,在一个执行上下文被创建时,不仅会创建当前词法环境的LexEnv
,也会创建LexEnv.OutEnv
、LexEnv.OutEnv.OutEnv
…,直至延伸到全局为止。
执行上下文的创建与销毁
1、创建一个新执行上下文(ExecutionContext , EC)
2、创建当前词法环境(LexEnv 和 VarEnv )
3、将该执行上下文的 LexicalEnvironmentComponent
和 VariableEnvironmentComponent
指向当前环境下的 LexEnv
和 VarEnv
中。
4、将新执行上下文推入 执行栈
中,并成为运行时执行上下文。
5、对可执行代码块内的标识符进行实例化和初始化:
收集当前词法环境内所有声明的标识符归入DecRec中,所有
var
声明的标识符归入VarNames
集合中,在此阶段会进行标识符名检测,若与let/const/...
声明的标识符与VarNames
中的标识符重复, 报错。将
DecRec
中的标识符进行实例化,并设为uninitialized
。VarNames
中的标识符绑定在ObjRec
中,实例化后直接初始化为undefined
。对于
function
声明的函数,将直接指向函数对象,并也会绑定到ObjRec中,这是浏览器默认行为。
6、运行代码。
非var声明的标识符会在声明处进行初始化(默认为
undefined
)。完成所有变量的赋值,并可能会一直在变化。
7、运行完毕从 执行栈
中弹出。
备注:
- 关于
This
绑定,大部分情况可以用过去的说法解释,然而某些情况下却不尽然。 - 闭包我会在下一篇介绍。
- 执行上下文,我个人认为并不如何重要,但是却能在许多情形下起到极为关键的作用,所以还是有必要去深入认识一下。
- 关于执行上下文和词法环境的关系,最多是前者引用了后者,仅此而已。诚然,有许多情况没必要用执行上下文来说明,但是永远避免不了违和感。
执行栈与事件循环
执行栈(Execution Stack)就是由执行上下文构成的堆栈,类似于Call Stack
。
1、当Javascript引擎
遇到一段可执行代码时,新建一个执行上下文。
2、将它推入执行栈中。并设置为运行时执行上下文。
- 如果存在其他执行上下文。
- 那么将当前执行上下文挂起
- 然后再将新执行上下文推入执行栈中。
3、执行上下文运行完毕,弹出销毁恢复并将原执行上下文设为运行时。
总觉得这些没什么好说的,但是水一下吧
执行栈最重要的部分并非是执行栈概念本身,而是与任务队列的关系,它是事件循环的入门关键概念之一。
众所周知,Javascript语言是单线程的,此处的执行栈就相当于主线程的调用栈,也是唯一一个调用栈,至于什么是主线程可以查阅相关资料,这里有些超纲了……
那么javascript是如何实现异步的?
确切来说,这不是Javascript核心的部分,它是结合浏览器API(如Web Worker, Browser-context了解一下)实现的。
在事件循环中(事件处理过程),有两个极其重要的概念:
- 任务序列: Task Quenue
- 事件: Event
这两个概念,是抽象滴。
在Javascript中,一个任务也可以称之为事件,通常是一个函数回调,由许多任务组成的队列,就是所谓的任务序列了。任务序列有很多分类,例如:作业序列(Job Quenue)、消息序列(Message Quenue),本质没区别。
不必再深入了解,现在需要记住的是:一个任务序列中的任务如果想要被执行,就必须将它取出放入执行栈中。
举一个抽象点的例子:例如下面的代码:
var temp = 10; console.log('push task1'); setTimeout(function task1(){ temp+=10; console.log(temp+'task1 okay! '); },1000) console.log('taskquenue=[task1]; push task2'); setTimeout(function task2(){ temp*=10; console.log(temp+'task2 okay! '); },500) console.log('taskquenue=[task1,task2]; push task3'); setTimeout(function task3(){ temp*= -0.2; console.log(temp+'task3 okay! '); },1500) console.log('taskquenue=[task1, task2,task3]');
输出如下:
push task1 taskquenue=[task1]; push task2 taskquenue=[task1,task2]; push task3 taskquenue=[task1, task2,task3] 100task2 okay! 110task1 okay! -22task3 okay!
setTimeout
是一个定时器,它能够将任务放到任务队列中。如图:
- 添加作业
task1
: - 添加作业
task2
: - 添加作业
task3
:
执行到此处, task1
、task2
和task3
都被放入了任务队列; 然后执行栈全部执行完毕后,开始处理任务队列中的任务。
为什么任务队列中的任务必须在执行栈空时后执行呢?
- 这里我也不清楚,这毕竟真正涉及了底层内容;只是有些明白为何这样,不过担心误导他人,就算了。
- 一般而言, 关于任务序列的相关概念没有如此简单,还涉及了很多东西,例如阻塞、调度(Schedule)等,这些方面可以参考其他诸如
C++
、Java
这类多线程的语言或是看看操作系统这方面的内容。 - 如果只是简单的业务实现,不需要了解这么多底层的东西。
现在开始处理任务吧:
- 处理
task2
: - 处理
task1
: - 处理
task3
:
好了,一个事件循环就这么结束了。
然后Javascript引擎进入休眠阶段(Javascript引擎永不结束!),等待有新的任务执行,然后开始下一个事件循环。
- 这里只是一个简单例子
- 事件循环可以有多个任务队列
- 任务序列共分为两种: 微任务序列和宏任务序列
- 我们的script代码,就是宏任务序列之一。
这是我精读Javascript系列第三篇,猝不及防的就到了事件循环,看起来一下子就深入好多好多…… 但是我觉得这才是最不合理程安排,大多的文档都把任务序列和调用栈给分开了,但是在~~_____~~,它们本应该就是一体,不应该以任何方便的理由将它们分开。
深入Javascript, 除了看规范,最好也看下JS引擎的实现文档,有些进阶内容,不在规范中,而是在这些文档里(请自己Google查找,百度也能搜索到)。
如果对Javascript引擎比较感兴趣的,可以参考:
- MDN About_Javascript
- Chromium V8参考
- Github V8
虽然,极其不建议新手一下子看V8源代码,那种头痛欲裂又浑身颤抖不止的感觉实在是过瘾之极啊……
以上就是什么是JS中的执行上下文、 执行栈、事件循环,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联-成都网站建设公司行业资讯频道。
分享标题:什么是JS中的执行上下文、执行栈、事件循环-创新互联
转载源于:http://lswzjz.com/article/jejss.html