JavaScript 事件循环
JavaScript事件循环机制
浏览器内核
浏览器是多进程的,每一个 tab页可能代表一个独立的进程(也可能多个tab合并成一个进程),浏览器内核(浏览器的渲染引擎)属于浏览器多进程的一种。
浏览器中的多种线程。
GUI渲染进程:
负责渲染页面,解析HTML,CSS构成的DOM树等,当页面重绘或者某一操作引起回流都会调起该线程。
与JS引擎线程互斥,当JS引擎在工作的时候,GUI渲染会被挂起,GUI更新被放入JS任务队列中,等JS引擎线程空闲的时候继续执行。
JS引擎线程:
单线程,负责解析运行JavaScript脚本。
和GUI渲染线程互斥,JS运行耗时过长会导致页面阻塞。
事件触发线程:
当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到任务队列的队尾,等JS引擎处理。
定时器触发线程:
浏览器的定时任务并不是由JS引擎计数的,阻塞会导致计时不准确。
开启定时器触发线程来计时并触发计时,计时完成后会被添加到任务队列中,等JS引擎处理。
HTTP请求线程:
HTTP请求的时候会开启一条请求线程。
请求完成有结果后,将请求的回调添加的任务队列中等待JS引擎处理。
js引擎
JavaScript引擎是单线程的,即每次只能执行一项任务,只有当前任务结束才会执行下一项,任务都是按顺序等待被执行。最新的HTML5提出web-worker,但JS引擎是单线程的核心并未改变。
web-worker允许JavaScript创建多线程,但是创建的子线程完全受主线程控制,不可访问DOM,子线程都的全局对象也不是Window。
所有的JavaScript的多线程都是单线程模拟出来的。
如果js不是单线程的,那么删除一个dom,编辑一个dom,浏览器应该执行谁,
js事件循环机制
JavaScript有一个main thread主线程和call-stack调用栈(执行栈),所有任务都会被放到调用栈等待主线程执行。
js调用栈
JavaScript调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后会从顶部移出函数直至清空。
同步/异步任务
同步任务在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步事件有了结果后将注册的回调函数添加到任务队列 (消息队列)中等待主线程空闲的时候,被读取到栈中等待主线程执行,这就是任务队列。
事件循环
同步和异步任务进入不同的执行“场所”,同步的进入主线程,异步的进入Event Table并注册函数;
当指定的事情完成时,Event Table会将这个函数移入Event Queue;
主线程内的任务执行完毕为空时,任务队列会去Event Queue读取函数,进入主线程执行;(不同的任务源的回调会放到不同的任务列表中[ 如:setTimeout 与 Promise ],)
以三步不断重复。
** Comment:第一次事件循环,js引擎会将script当成一段代码执行,执行完成后,再检查本次循环是否有微任务,若存在一次从微任务的任务队列中读取并执行完所有的微任务,若无,再进行第二次循环读取宏任务中的任务列表执行,再执行微任务,以此循环。
定时器
定时器会开启一条定时器触发线程来触发计时,定时器会在等待了指定的时间后将事件放入任务队列中等待读取主线程执行。定时器指定的毫秒数其实并不准确,因为定时器只是到了指定事件将事件的回调函数放入到任务队列中,必须等同步任务和现有的任务队列中的事件全部执行完成后,才会去读取定时器的事件到主线程执行,中间可能存在耗时较长的任务,那就不能保证在指定的时间执行。
微任务与宏任务
宿主环境提供的叫宏任务,由语言标准提供的叫微任务。
宿主环境:JavaScript的运行环境,宿主环境内所有的内建或自定义的变量/函数都是global(node的全局对象)/window(浏览器的全局对象)这个全局对象的属性/方法,由宿主环境提供的也叫宏任务。
语言标准:由标准语言提供的就是微任务,如:ES6提供的promise。