人人都看得懂的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文件的步骤,分析了浏览器环境的一些简单机制。 更多的执行细节,比如词法分析、作用域构建等在后续文章中会继续深入。


相关文章
|
3月前
|
存储 JavaScript 前端开发
深入理解JavaScript中的事件循环(Event Loop):机制与实现
【10月更文挑战第12天】深入理解JavaScript中的事件循环(Event Loop):机制与实现
151 3
|
2月前
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
4月前
|
JavaScript 安全 前端开发
乾坤js隔离机制
乾坤js隔离机制
|
2月前
|
JavaScript 安全 中间件
深入浅出Node.js中间件机制
【10月更文挑战第36天】在探索Node.js的奥秘之旅中,中间件的概念如同魔法一般,它让复杂的请求处理变得优雅而高效。本文将带你领略这一机制的魅力,从概念到实践,一步步揭示如何利用中间件简化和增强你的应用。
|
2月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
385 1
|
2月前
|
消息中间件 JavaScript 中间件
深入浅出Node.js中间件机制
【10月更文挑战第24天】在Node.js的世界里,中间件如同厨房中的调料,为后端服务增添风味。本文将带你走进Node.js的中间件机制,从基础概念到实际应用,一探究竟。通过生动的比喻和直观的代码示例,我们将一起解锁中间件的奥秘,让你轻松成为后端料理高手。
47 1
|
3月前
|
移动开发 JavaScript 前端开发
【JavaScript】JS执行机制--同步与异步
【JavaScript】JS执行机制--同步与异步
35 1
|
3月前
|
JSON JavaScript 中间件
深入浅出Node.js中间件机制
本文将带你探索Node.js中一个核心概念——中间件机制。我们将通过浅显的语言和生动的比喻,揭示中间件如何作为请求和响应之间的“交通枢纽”,在应用程序中起到至关重要的作用。从基础原理到实际应用,你将了解到中间件不仅简化了代码结构,还提高了开发效率,是Node.js开发不可或缺的一部分。
65 1
|
3月前
|
JavaScript 前端开发 开发者
原型链深入解析:JavaScript中的核心机制
【10月更文挑战第13天】原型链深入解析:JavaScript中的核心机制
45 0
|
4月前
|
前端开发 JavaScript Java
JavaScript的运行原理
JavaScript 的运行原理包括代码输入、解析、编译、执行、内存管理和与浏览器交互几个步骤。当打开网页时,浏览器加载 HTML、CSS 和 JavaScript 文件,并通过 JavaScript 引擎将其解析为抽象语法树(AST)。接着,引擎将 AST 编译成字节码或机器码,并在执行阶段利用事件循环机制处理异步操作,确保单线程的 JavaScript 能够高效运行。同时,JavaScript 引擎还负责内存管理和垃圾回收,以减少内存泄漏。通过与 DOM 的交互,JavaScript 实现了动态网页效果,提供了灵活且高效的开发体验。

热门文章

最新文章