线程机制与事件机制②
JS是单线程执行的
我们可以用以下代码证明JS是单线程执行的:
<script type="text/javascript">
setTimeout(function () {
console.log('timeout 2222')
alert('22222222')
}, 2000)
setTimeout(function () {
console.log('timeout 1111')
alert('1111111')
}, 1000)
setTimeout(function () {
console.log('timeout() 00000')
}, 0)
function fn() {
console.log('fn()')
}
fn()
console.log('alert()之前')
alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
console.log('alert()之后')
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
alert()函数有暂停当前主线程的执行, 同时暂停计时的功能
有运行结果可以知道:
setTimeout()的回调函数是在主线程执行的,定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行。
为什么js要用单线程模式, 而不用多线程模式?
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
例如我们可以想象一下:我们现在有两个线程对一个div进行操作,其中一个线程是设置样式的,另一个线程是删除的。有这么一种情况它是有发生的可能性的:当运行其中一个线程进行样式设置时,突然线程切换,把div删除了,当回到原来的线程的时候就会出现错误。
我们可以将JS中的代码从另一种角度分类:
- 初始化代码
- 回调代码
js引擎执行代码的基本流程:先执行初始化代码: 包含一些特别的代码 --> 回调函数(异步执行,后面在某个时刻才会执行回调代码)
常见的回调函数:
- 设置定时器
- 绑定事件监听
- 发送ajax请求
特别注意:setTimeout()是初始化代码,它里面的回调函数才是回调代码!
事件循环模型
原理图:
WEB APIS中的三个模块负责处理对应的回调代码。从此图我们可以明显的发现这三个模块处理器(由浏览器提供)是处于分线程的位置,而JS引擎处于主线程的位置(它也只可能处于主线程的位置)
模型的2个重要组成部分:
- 事件(定时器/DOM事件/Ajax)管理模块
- 回调队列(callback queue)
模型的运转流程:
执行初始化代码, 将事件回调函数交给对应模块管理。当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中,只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行。
相关概念:
- 执行栈(execution stack):所有的代码都是在此空间中执行的
- 任务队列(task queue)
- 消息队列(message queue)
- 事件队列(event queue)
- 上面三个队列等同于回调队列(callback queue)
- 事件轮询:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
此时我们可以解释在线程机制与事件机制①中为什么定时器不准的原因。真正帮我们计时的是setTimeout回调处理模块,JS引擎是不会帮我们计时的。在时间到了之后并不意味着马上执行!而是模块把回调函数放入到回调队列中,这个时候处于一个等待执行的状态。只有在执行栈中的代码执行完了以后,才会把回调队列中的代码放到执行栈中去执行。当执行栈中的初始化代码所花时间过长的时候,我们的回调代码会晚进入执行栈,就给我们计时不准的错觉。
H5 Web Workers实现多线程
Web Workers 是 HTML5 提供的一个javascript多线程解决方案。我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面。但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质
使用步骤:
- 创建在分线程执行的js文件
- 在主线程中的js中发消息并设置回调
相关API
- Worker: 构造函数, 加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息
我们模拟一个场景:用分线程计算斐波那契数列,在计算的这段时间之内不冻结用户界面,可以自由操作。
主线程:
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
var input = document.getElementById('number')
document.getElementById('btn').onclick = function () {
var number = input.value
//创建一个Worker对象
var worker = new Worker('worker.js')
// 绑定接收消息的监听
worker.onmessage = function (event) {
console.log('主线程接收分线程返回的数据: '+event.data)
alert(event.data)
}
// 向分线程发送消息
worker.postMessage(number)
console.log('主线程向分线程发送数据: '+number)
}
// console.log(this) // window
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
分线程:
function fibonacci(n) {
return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2) //递归调用
}
console.log(this)
this.onmessage = function (event) {
var number = event.data
console.log('分线程接收到主线程发送的数据: '+number)
//计算
var result = fibonacci(number)
postMessage(result)
console.log('分线程向主线程返回数据: '+result)
// alert(result) alert是window的方法, 在分线程不能调用
// 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
//console是每个浏览器都提供的实现,跟window没有关系
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
注意:
- alert是window的方法, 在分线程不能调用
- 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
- console是每个浏览器都提供的实现,跟window没有关系
图解:
不足之处:
- 不能跨域加载JS
- worker内代码不能访问DOM(更新UI)
- 不是每个浏览器都支持这个新特性
文章来源: blog.csdn.net,作者:十八岁讨厌编程,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/zyb18507175502/article/details/124127128
- 点赞
- 收藏
- 关注作者
评论(0)