人人都看得懂的JS运行机制(下)

简介: 本文是以作者自己理解的思路拆分的JS运行机制,见解有限难免疏漏,欢迎留言勘误、交流。

单线程意味着什么?


意味着,正常情况下,JS引擎会按照代码书写的逻辑,依次调用函数,在任意时间点,有且只能有一个函数被执行。外层函数必须等到内层函数处理完毕有返回值之后才能继续执行。


为什么要单线程?


JS最初被设计使用在浏览器上,作为浏览器上的脚本语言,需要与用户的操作互动以及操作DOM, 如果时多线程的话,需要关注各个线程之间状态的同步问题。


想象一下,js引擎中可以同时执行2个函数,如果两个函数操作同一个对象,那么到底以哪个为准?


然后,操作DOM结构,在线程A上已经删了某节点,线程B同时还在对该节点一顿操作,这就尴尬了。


而做多线程的状态同步又是得不偿失的,因此就直接用单线程了。


有什么问题?


假如,某个函数耗时比较久,那么调用它的外层函数必须安安静静的等待这个函数执行完成才能继续执行, 如果,这个函数出错了,那么外层的函数也没办法继续执行了。


function foo(){
  bar()
  console.log('after bar')
}
function bar(){
  while(true) {}
}
foo()


比如这个栗子,foo函数中调用了bar函数,那么foo函数必须等待bar函数执行完毕才能继续执行后续代码,然而bar函数是个无限执行的函数哟,回不来的,那么foo函数等到花儿都谢了也没办法执行后面的代码的。


当然这是比较极端的情况,但是在前端的业务场景中有几类常见的case,在此是有问题的:


  • 定时器延迟操作


  • 网络请求


  • 网页事件等


这些就是我们常见的异步操作, 事件触发之后并不能立即得到结果,按照之前的运行模式,浏览器就会阻塞其他操作,等待相应结果,表现在页面中就是页面卡死,这是一个优秀的应用所不能允许发生的。


同步&异步


关于同步和异步操作,借用朴灵大神的说法:


一般操作可以分为两个步骤,


  1. 发起调用


  1. 得到结果


发起调用,立马可以得到结果的是为 同步


发起调用,无法立即得到结果,需要额外操作才能得到结果的是为 异步


因此当前的模型在异步操作中是有问题的


解决方案?


问题的本质是js引擎的单线程工作模式,只专注于一件事情, 必须【执行至完成】


而产生问题的那些操作往往不能直接得到 结果,必须经过额外操作才能得到结果 【异步问题】


思路:可以把这些异步操作分发给其他模块,得到处理结果之后再把回调函数一块放入主线程执行。


这就是 事件循环(Event Loop)的主体思路。


event loop 只是解决异步问题的一种思路 其他的思路还有:


  • 轮询


  • 事件


Web API's


上一章节提到,异步操作可以交给JS引擎之外的其他模块处理, 在浏览器中其他模块就是Web API模块


Web API 其实就是上述的 JS runtime 提供的一系列宿主环境的特性集合。


在浏览器中,主要包括以下能力:


  • Event Listeners


  • HTTP request


  • Timing functions


完美的cover了上述产生异步问题的几类case


因此至此,我们可以得出以下的视图:



JS引擎中,执行栈遇到同步函数调用,直接执行得到结果后,弹出栈,继续下一个函数调用


遇到异步函数调用,将函数分发给Web API模块,然后该异步函数弹出栈,继续下一个函数调用,不会产生阻塞问题。


问题来了?


这些异步操作分发给Web API模块处理之后,不能说不管了,主线程还是需要知道结果做后续操作的,Web API得到结果之后怎么通知主线程呢?


这就需要其他的模块帮忙了。


这个地方提到了主线程,就意味着还有其他的辅助线程。


是的,JS是单线程执行的,但是并不意味着浏览器内核是单线程的。


事实上,web api模块内就有多个线程,每个异步操作处理模块都对应一个线程

http请求线程、事件处理线程、定时器处理线程等


回调队列 (callback queue)


回调队列, 也叫事件队列、消息队列。


这个模块就是用来帮助Web API模块处理异步操作的。


Web API模块中,异步操作在相应的线程中处理完成得到结果之后,会把结果注入异步回调函数的参数中,并且把回调函数推入回调队列中。


但是,只推到回调队列里也不是个事儿,因为前面说到了,所有的JS执行都发生在主线程调用栈里面。这些异步操作拿到结果之后,带着回调函数推入了回调队列,需要在适当的时机进入主线程调用栈执行。


那么,谁知道什么时候是合适的时机呢?


Event Loop 知道。


回调队列


队列是一个FIFO,先进先出的存储结构,


这样意味着异步操作的回调函数会按照进入队列的顺序被执行,而不是调用的顺序被执行


Event Loop


Event Loop 不停地检查主线程调用栈和回调队列,当发现主线程空闲的时候,就把回调队列里第一个任务推入主线程执行。 以此不停地循环。


至此,一个异步操作,兜兜转转最终拿到了结果,成功执行并且没有阻塞其他的操作。


overview


一个完整的图示如下:



至此,本文在宏观结构上按照浏览器执行js文件的步骤,分析了浏览器环境的一些简单机制。 更多的执行细节,比如词法分析、作用域构建等在后续文章中会继续深入。


相关文章
|
1月前
|
设计模式 JavaScript 前端开发
深入理解 JavaScript 中的绑定机制(下)
深入理解 JavaScript 中的绑定机制(下)
|
1月前
|
JavaScript 前端开发
深入理解 JavaScript 中的绑定机制(上)
深入理解 JavaScript 中的绑定机制(上)
|
3月前
|
JavaScript 前端开发 数据库连接
js的异常程序处理机制
js的异常程序处理机制
18 0
|
3月前
|
存储 前端开发 JavaScript
深入理解前端JavaScript执行机制
深入理解前端JavaScript执行机制
43 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
77 0
|
1月前
|
开发框架 JavaScript 前端开发
描述JavaScript事件循环机制,并举例说明在游戏循环更新中的应用。
JavaScript的事件循环机制是单线程处理异步操作的关键,由调用栈、事件队列和Web APIs构成。调用栈执行函数,遇到异步操作时交给Web APIs,完成后回调函数进入事件队列。当调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中常用框架提供更高级别的抽象。
14 1
|
1月前
|
JavaScript 前端开发 算法
深入探讨前端框架Vue.js中的虚拟DOM机制
本文将深入探讨前端框架Vue.js中的虚拟DOM机制,分析其原理、优势以及在实际开发中的应用场景,帮助读者更好地理解Vue.js框架的核心特性。
|
1月前
|
消息中间件 前端开发 JavaScript
深入理解JavaScript中的事件循环机制
JavaScript作为一种前端开发必备的编程语言,在处理异步操作时常常涉及到事件循环机制。本文将深入探讨JavaScript中事件循环的工作原理,帮助读者更好地理解和运用这一关键概念。
|
1月前
|
JavaScript 前端开发 开发者
如果你想在钉钉环境中运行JavaScript脚本
【2月更文挑战第17天】如果你想在钉钉环境中运行JavaScript脚本
34 6
|
1月前
|
自然语言处理 JavaScript 前端开发
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)