提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、渲染引擎、js引擎的关系
首先,浏览器内核由两部分组成:渲染引擎(layout engineer或者RenderingEngine)和JS引擎。但是随着js引擎的越来越独立,浏览器的内核就更多的指的是渲染引擎。
1.渲染引擎
渲染引擎的作用:负责对页面语法的解释,把 html、css 分别用 parser 解析成 dom 和 cssom,然后合并到一起,并计算布局样式成绝对的坐标,生成渲染树,之后把渲染树的内容复制到显存就可以由显卡来完成渲染。
2.js引擎
js引擎的作用:负责对JavaScript进行解释、编译和执行,以使网页达到一些动态的效果。
js 引擎包括 parser、解释器、gc 再加一个 JIT 编译器这几部分
parser: 负责把 javascript 源码转成 AST(抽象语法树,一种数据结构,实际上就是JSON对象)
interperter:解释器, 负责转换 AST 成字节码,并解释执行
JIT compiler:对执行时的热点函数进行编译,把字节码转成机器码,之后可以直接执行机器码
gc(garbage collector):垃圾回收器,清理堆内存中不再使用的对象
3.渲染引擎与js引擎的相互配合
JS 引擎只知道执行 JS,渲染引擎只知道渲染,它们两个并不知道彼此,该怎么配合呢?
答案就是 event loop。
什么是event loop?
官方解释:JavaScript主线程从"任务队列"中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种机制又称为EventLoop(事件循环)
本质上,事件循环,是指浏览器或者Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用的异步的原理。
首先要明白,event loop 并不是js引擎提供的,而是所处的宿主环境提供,例如浏览器、node。不同的宿主环境提供的event loop 针对不同的业务场景,是不同的,主要是一下几点的不同。
注入全局的api不同
node会注入一些全局的require api,同时提供fs(文件模块)、os(操作系统模块)等内置模块
浏览器则会提供一些符合w3c标准的api
event loop 的实现不同
浏览器里面主要是调度渲染和 JS 执行,还有 worker。
node 里面主要是调度各种 io。
浏览器中的event loop
首先,js是单线程的,那也就意味着在多个任务中,js会依次执行。那么问题来了,假如说有一个任务执行的时间很长,那么其后面的任务岂不是要等待很久,甚至会造成页面卡顿、白屏等(例如图片的加载),因此,就产生了一种js任务的划分:同步任务、异步任务
同步任务:又称非耗时任务,在主线程中排队执行的任务
异步任务:又称耗时任务,异步任务由js交给宿主环境执行,当异步任务执行完毕之后,会通知js主线程执行异步任务的回调函数
文字描述:同步任务会进入主线程,按照顺序依次执行,异步任务会在异步任务有了结果之后,将回调函数放入任务队列中,等待主线程空闲的时候(主线程调用栈被清空),被读取到主线程中执行。上述过程会不断执行,也就是我们所说的事件循环(event loop)
除了广义的同步任务和异步任务的定义之外,对js的任务还有更精确的划分:
宏任务(macro-task):script(可以理解为外层同步代码,作为入口 )、setTimeout、setInterval、postMessage、MessageChannel、及Node.js 环境中的setImmediate.
微任务(micro-task):Promise.then、Object.observe、MutationObserver、及Node.js 环境中的process.nextTick.
微任务和宏任务都是异步任务,它们都属于一个队列,此外,宏任务是宿主环境发起的任务、微任务是js引擎发起的任务
那么现在,js任务的执行顺序是怎样的呢?
答案是:先执行同步再执行异步,异步遇到微任务,先执行微任务,直到所有的微任务执行完,在执行宏任务.
node中的event loop
首先,node中的事件循环共有六个阶段
第一个阶段:timers(定时器阶段–setTimeout,setInterval)
第二个阶段:pending callbacks (系统阶段)
第三个阶段:idle, prepare (准备阶段,仅系统内部使用)
第四个阶段:poll(轮询阶段,核心)
如果回调队列里有待执行的回调函数(setTimeout,setInterval,setImmediate之外),则从回调队列中取出回调函数,同步执行(一个一个执行),直到回调队列为空了,或者达到系统最大限度。
如果回调队列为空,事件循环会进行判断,如果Timer和Check队列都为空的话,那么事件循环会在这里阻塞。在poll阶段等待的时候,主要是为了等待新的I / O 事件,因为在设计上,事件循环是希望优先处理 I / O 事件的。
第五个阶段:check (专门用于执行setImmediate所设置的回调)
第六个阶段:close callbacks (关闭回调阶段)
注:以上第二个阶段和第三个阶段都是系统的操作,暂不深入了解。
其次,我们需要知道的是,node中所有的异步API分为以下三类:
定时器(setTimeout、setInterval)
I / O 操作(文件读写、数据库操作、网络请求)
node独有(process.nextTick()、setImmediate)
根据以上三类异步操作,事件循环内部初始化了三个任务队列:
Timer队列 => 定时器(setTimeout、setInterval)
Poll队列 => I / O 操作(文件读写、数据库操作、网络请求),事件循环会在空闲的情况下暂停以等待新的 I / O事件
Check队列=> setImmediate
任务队列初始化完成之后,事件循环会自上而下不停的循环执行
那么node中代码执行的顺序是怎样的呢
代码执行过程:
同步代码执行完毕后,事件循环开始。
注意:在node中,setTimeout的第二个参数最小取值为1ms,而不是0,即使人为设置参数为0。当系统运行得足够快的时候,可能用不了1ms事件循环已经启动了,此时定时器可能还来不及将回调函数加入到Timer队列中,那么事件循环就跳过了Timer队列进入到Check队列了,先去执行setImmediate的回调函数了,即受系统运行状况的影响,并不能保证这段代码(下图)的执行顺序。
怎么确保两者的执行顺序呢?
我们可以将他们放到一个 I / O 操作的回调中执行,因为I / O 操作在poll阶段执行,setTimeout进入Timer队列,setImmediate进入Check队列,此时事件循环处于poll阶段,然后会接着向下执行,调用Check队列中的回调函数,然后再调用Timer队列中的回调函数,这样的话setImmediate总会被优先调用。
对于process.nextTick(),是Node中另一个异步模块,它的优先级比事件循环更高,在node的事件循环中,从Timer队列到Check队列,运行一周,称为一个tick,process.nextTick()的作用就是将函数插入到每个tick的头部,优先于下一个tick的执行,在下一个tick执行之前执行。
对于微任务,其在process.nextTick()后面,事件循环的前面。
在node当中,一段脚本总是自上而下的执行,同步代码阻塞执行,异步代码进入异步模块以非阻塞的形式执行,对应的异步函数会在异步代码执行完后被派发到不同的队列中。同步代码执行完毕后,先执行nextTick队列中的任务,再执行微任务队列中的任务,最后再执行事件循环中的队列的任务。
总结
以上就是对js引擎、渲染引擎以及浏览器的event loop、node的event loop的介绍,如有错误,欢迎指出。