- 为什么js是单线程,web works是多线程?
- 为什么js单线程却拥有异步?
- event loop?
- 为什么setTimeout时间时间不准确
浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示您选择的网络资源。这里所说的资源一般是指 HTML 文档,也可以是 PDF、图片或其他的类型。资源的位置由用户使用 URI(统一资源标示符)指定。
浏览器的高层结构
用户界面:地址栏,浏览器页面前进后退,刷新和停止刷新按钮,书签...
浏览器引擎:用户界面与呈现引擎之间传送指令
呈现引擎:显示请求内容,解析html&css
网络:网络调用。
用户界面后端:用户绘制基本的小窗口部件
JS解释器:用于解析和执行js代码
数据存储:浏览器在硬盘上保存的各种数据。
浏览器是多进程的
浏览器设置中可以找到浏览器的任务管理
可以看每个页面都是独立的进程,
进程分一下几种:
- 主进程:主进程,只有一个
- 浏览器渲染进程:进程之间互不影响
- 第三方插件进程:使用时创建进程
- GPU进程:用于3D绘制
浏览器输入网页之后发生了什么?
呈现引擎示意图(webkit举例)
呈现引擎解析html文档,讲标记转化成‘树’,同时解析外包css文件。
创建呈现树,包含视觉属性(颜色和尺寸),排列顺序就是屏幕展示顺序
呈现树中每个节点应该出现在屏幕的确切坐标
为了更好的用户体验,引擎会力求尽快将内容展示在屏幕上。
解析器遇到<script>标记时会立即解析脚本,直到资源加载完成,也可以通过添加’defer‘,这样不会停止文档解析。
网络操作与以上同时进行。
js解析,由单独的脚本引擎解析执行,通常状态是动态的改变dom树,如果js是多线程,当两个线程同时修改同一个dom节点,下达两个矛盾的命令时,浏览器的执行就成了问题
以上解决了为什么js是单线程,web works是多线程?
为什么js单线程却拥有异步?
单线程是指cpu一次只能做一件事,js在任务队列中提取任务放到主任务中执行,浏览器为异步任务单独开辟线程。就不得不说到Event Loop。
Event Loop
JS是单线程的,当遇到大量任务或耗时任务时会卡死,线程大部分事件都在空等I/O的返回结果。这种方式称之为’同步‘。
Event Loop解决了此类问题,在程序中设置两个线程,一个主线程,负责程序本身的运行,一个另一个负责与其他线程之间的通信。
首先要了解常用宏任务,微任务
宏任务:
- setTimeout
- setInterval
微任务 - Promise
- nextTick(node)
Event Loop流程: - 执行全局同步代码,调用栈清空
- 取出微任务队列首位任务,放入调用栈中。直到全部微任务执行完成。
- 取出宏任务队列首位任务,放入调用栈中执行,其中产生的微任务放入微任务队列,宏任务同理。
- 重复二三步操作,直到全部执行完成。
setTimeout(()=>{
console.log('time1');
Promise.resolve().then(()=>{
console.log('p1')
})
})
setTimeout(()=>{
console.log('time2');
Promise.resolve().then(()=>{
console.log('p2')
})
})
如图
setTimeout时间时间不准确
:很多原因都会导致setTimeout 没有按时执行,原因之一异步执行setTimeout,将setTimeout放入宏任务栈队列,等执行到宏任务队列时,代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。所以会有一定程度的延迟。
var date = new Date();
setTimeout(()=>{
new Promise(
function (resolve, reject) {
setTimeout(resolve)
}
).then(()=>{
console.log(new Date()-date,'p');
})
})
setTimeout(function(e) {
console.log(new Date()-date,'s');
});
输出1 "s" 3 'p'
虽然setTimeout中没有设置时间,但还是延迟执行了一段时间。
在浏览器中,
setTimeout()/setInterval()
的每调用一次定时器的最小间隔是4ms(摘自MDN),也会导致setTimeout没有按时执行
例如:
var date = new Date();
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
console.log(new Date()-date);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
如果每个间隔是4ms,输出应该是20,实际输出是16 17 ...,实践证明其实间隔不一定是4ms
setTimeout(function(){console.log('4');},4)
setTimeout(function(){console.log('3');},3)
setTimeout(function(){console.log('2');},2)
setTimeout(function(){console.log('1');},1)
setTimeout(function(){console.log('0');},0)
以上代码你觉得会输出什么?
实际输出 1 0 2 3 4,不同浏览器输出结果不同,每次输出结果也有可能不同。
是因为,setTimeout在宏任务队列中,主线程按顺序执行宏任务队列,发现没有可以执行的宏任务,遍历队伍有没有可以执行的任务,4ms时间还没到,每次挂起一个settimeout的时候都会循环一遍事件队列。1比0先执行是因为1先被挂起。
参考资料
浏览器的工作原理:新式网络浏览器幕后揭秘:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Event_loop
什么是 Event Loop?阮一峰:
http://www.ruanyifeng.com/blog/2013/10/event_loop.html
MDN:
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout