JS是一门单线程语言
单线程就是所执行的代码必须按照顺序来执行,同一时间只能做一件事。因为JS的主要用途是与用户互动,以及操作DOM,如果它是多线程的,可能会带来一些Bug的产生。不过,为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JS单线程的本质。
Event loop
为了防止主线程堵塞,javaScript有了同步和异步的概念,js里面有一个主线程,里面有同步任务和异步任务。
同步任务:非耗时任务,指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务:耗时任务,是js主线程委托给宿主环境执行的任务,当异步任务执行完成后,会通知主线程读取异步任务队列,从而执行异步任务队列中的回调函数。
-
异步任务又分为了宏任务和微任务,宏任务包括异步ajax、计时器和文件操作等等,微任务包括Promise.then(),promise.catch()和promise.finally等。
-
异步任务中宏任务先执行,宏任务执行完毕后,会去微任务队列查看是否有需要执行的微任务,如果没有,则直接进行下一个宏任务,如果有,则执行完所有的微任务后,再执行下一个宏任务,直到所有的宏任务执行完毕,这就是一个事件循环event loop。
两道面试题
输出:2431
因为JS解释器执行代码时,是按顺序执行的,遇到setTimeout这个异步方法,就会把它交给宿主环境处理,处理成功后,会把它的回调函数放入异步任务队列中,然后执行同步代码new Promise,输出2,然后将.then这个微任务放入微任务队列中,然后执行同步代码,输出4,当主线程为空时,则先查看当前的微任务队列中是否为空,则会执行promise.then方法,输出3,然后主线程会查看异步任务队列中是否有需要执行的宏任务,从而输出4。
输出:156234789
JS中实现并发
Web Worker
Web Worker 的三大主要特征:能够长时间运行(响应),理想的启动性能以及理想的内存消耗,在 HTML5 中引入的工作线程使得浏览器端的 JavaScript 引擎可以并发地执行 JavaScript 代码,从而实现了对浏览器端多线程编程的良好支持。
我们可以让一些计算量比较大的工作交给分线程( Web Worker)去运行,从而不冻结用户界面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" class="ipt">
<button class="btn">计算</button>
<script>
var input = document.querySelector('.ipt')
var btn = document.querySelector('.btn')
btn.addEventListener('click', function() {
var number = input.value
var worker = new Worker('./worker.js')
//向分线程发送消息
worker.postMessage(number)
//接收worker传过来的值,绑定监听
worker.onmessage = function(event) {
console.log('主线程接收分线程的值' + event.data)
}
})
</script>
</body>
</html>
worker.js,在worker.js里面,它有自己的this指向,并不是指向window的
function f(n) {
return n <= 2 ? 1 : f(n-1)+f(n-2)
}
var onmessage = function(event) {
//分线程接收到主线程的数据
var number = event.data
var result = f(number)
// 分线程向主线程发送数据
postMessage(result)
}
缺点:
- 处理起来会慢一点点
- 不能跨域加载JS
- 不能直接操作DOM元素
- 部分浏览器有兼容性问题
图解Worker
在主线程和分线程中都会有一个Message Queue来存放Message的回调,首先是主线程通过postMessage方法向分线程发送数据,则分线程队列中就会push进一个message的回调任务,分线程会从Message Queue中依次取任务来执行,一次只能执行一个任务,处理完该回调任务后,分线程就会向主线程通过postMessage方法发送数据,则此时主线程队列就会push进一个message的回调任务,主线程也会从Message Queue中依次取任务来执行,一次只能执行一个任务。