本篇文章带大家了解一下javascript中的执行上下文和执行栈、事件循环。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
下面的这些概念,无论是执行上下文、 还是执行栈,它在规范中的概念都很抽象,很多内容的理解实际靠的都是想象力,若有错误之处,还请指正。
执行上下文
简而言之,执行上下文(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 task1taskquenue=[task1]; push task2taskquenue=[task1,task2]; push task3taskquenue=[task1, task2,task3]100task2 okay! 110task1 okay! -22task3 okay!settimeout是一个定时器,它能够将任务放到任务队列中。如图:
添加作业task1:
添加作业task2:
添加作业task3:
执行到此处, task1、task2和task3都被
注册国际域名如何操作?国际域名注册规则是怎样的?腾讯云服务器特价3年出租号去哪个平台好 用什么平台出租自己的游戏号域名注册需要实名制吗购买做过站的域名好吗 怎么挑选做过站的域名建网站资料都需要准备哪些?网站打不开打开缓慢一下午出现次网站后台打不开了-虚拟主机/数据库问题