JS是一门单线程语言,对于这个,你了解多少吗?

263 阅读3分钟

JS是一门单线程语言

单线程就是所执行的代码必须按照顺序来执行,同一时间只能做一件事。因为JS的主要用途是与用户互动,以及操作DOM,如果它是多线程的,可能会带来一些Bug的产生。不过,为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JS单线程的本质。

Event loop

为了防止主线程堵塞,javaScript有了同步和异步的概念,js里面有一个主线程,里面有同步任务和异步任务。

同步任务:非耗时任务,指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务:耗时任务,是js主线程委托给宿主环境执行的任务,当异步任务执行完成后,会通知主线程读取异步任务队列,从而执行异步任务队列中的回调函数。

image.png

  • 异步任务又分为了宏任务和微任务,宏任务包括异步ajax、计时器和文件操作等等,微任务包括Promise.then(),promise.catch()和promise.finally等。

  • 异步任务中宏任务先执行,宏任务执行完毕后,会去微任务队列查看是否有需要执行的微任务,如果没有,则直接进行下一个宏任务,如果有,则执行完所有的微任务后,再执行下一个宏任务,直到所有的宏任务执行完毕,这就是一个事件循环event loop。

两道面试题

image.png

输出:2431

因为JS解释器执行代码时,是按顺序执行的,遇到setTimeout这个异步方法,就会把它交给宿主环境处理,处理成功后,会把它的回调函数放入异步任务队列中,然后执行同步代码new Promise,输出2,然后将.then这个微任务放入微任务队列中,然后执行同步代码,输出4,当主线程为空时,则先查看当前的微任务队列中是否为空,则会执行promise.then方法,输出3,然后主线程会查看异步任务队列中是否有需要执行的宏任务,从而输出4。

image.png

输出: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)
}

缺点:

  1. 处理起来会慢一点点
  2. 不能跨域加载JS
  3. 不能直接操作DOM元素
  4. 部分浏览器有兼容性问题

图解Worker

image.png

在主线程和分线程中都会有一个Message Queue来存放Message的回调,首先是主线程通过postMessage方法向分线程发送数据,则分线程队列中就会push进一个message的回调任务,分线程会从Message Queue中依次取任务来执行,一次只能执行一个任务,处理完该回调任务后,分线程就会向主线程通过postMessage方法发送数据,则此时主线程队列就会push进一个message的回调任务,主线程也会从Message Queue中依次取任务来执行,一次只能执行一个任务。