JavaScript同步与异步

74 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

进程与线程

  • 进程:程序的一次执行,占有独有的内存空间,JavaScript是单线程的
  • 线程:CPU的基本调度单位,是程序执行的一个完整流程

JavaScript的单线程决定了它运行程序是从上往下,顺序处理任务的(因为JavaScript最初设计的时候就是为了用于浏览器,为了避免混乱的交互场景才设计成单线程)。

一个进程中可以同时运行多个线程,即程序是多线程运行的。但一个进程中一般至少有一个运行的线程,即主线程。一个进程内的数据供多个线程直接共享但多个进程中的数据不能直接共享。

浏览器内核

浏览器内核由很多模块组成,比如:

  1. html,css文档解析模块
  2. dom/css模块
  3. 布局和渲染模块
  4. 定时器模块
  5. 网络请求模块
  6. 事件相应模块... 浏览器是多线程的,对应的模块都有自己的线程。

同步任务与异步任务

  • 异步任务(回调执行代码):需要回调去处理的任务,主要包含定时器、网络请求、事件响应。
  • 同步任务(初始化执行代码):除异步任务之外的代码,同步任务在JavaScript线程上排队,顺序执行。
//同步任务
var a = 1;
console.log(a);
console.log(a+1);
//会顺序执行,输出1 2


console.log(a+2);
//定时器,异步
setTimeout(
    function(){
    console.log('异步');
},1000)
console.log(a+3);
//3 4 异步

事件循环

image.png 事件触发线程:浏览器渲染引擎提供的线程之一,用于维护回调队列。

当JS引擎执行代码遇到异步任务时,就会去调用Web API(定时器,DOM事件,网络请求等)中相应的线程,主线程继续执行代码。

当满足相应异步任务的条件时事件触发线程就会在回调队列中加入相应的回调函数,当主线程的代码执行完后(执行栈空了)就会去回调队列读取任务,执行相应的回调函数。

执行栈不断循环地从任务队列中读取事件就称作事件循环(event loop)。

代码说明:

console.log(1);

setTimeout(
    function(){
    console.log('异步');
},3000)

console.log(2);
  1. 首先JS引擎读取第一行代码,为同步任务顺序执行,输出1。
  2. 然后读取定时器代码,发现为回调执行代码,调用Web API,JS引擎继续执行同步代码,输出2。过了3s后,事件触发线程将回调函数function(){console.log('异步')}加入到队列中。
  3. 此时执行栈已经为空,事件触发线程就去回调队列取出function(){console.log('异步')}放入执行栈中执行。

事件循环说明:在主线程的代码执行完之前回调函数的代码是不会被执行的,因此像定时器这样的回调函数可能会有延迟。

比如定时器的时间是1s,主线程的代码执行时间长于1s,JS引擎会等主线程代码执行完之后才调用定时器,定时器并不会在1s后就执行回调函数。

注意:alert()函数会暂停当前主线程的执行,同时暂停定时器的计时。

H5中的Web Workers

H5规范提供了js分线程的实现, 即Web Workers。

Web Workers的相关API:

  • Worker:构造函数,用于加载分线程执行的js文件
  • Worker.prototype.onmessage: 一个回调函数,用于接收另一个线程发送的数据
  • Worker.prototype.postMessage: 向另一个线程发送消息

分线程实现的实例:

//主线程代码
<body>
    <input type="text" placeholder="数值">
    <button>计算</button>
    <script>
        var btn = document.querySelector('button');
        var input = document.querySelector('input');
        btn.onclick = function(){
            var num = input.value;
            //  向分线程发送数据
            var worker = new Worker('test.js');
            worker.postMessage(num);
            console.log('向分线程发送数据:'+num);
            // 接收分线程的数据
            worker.onmessage = function(event){
                console.log('接收到分线程计算的结果'+event.data);
            }
        }
    </script>
</body>

//分线程代码
function calcu(num){
    return num<=2? 1:calcu(num-1)+calcu(num-2);
}

this.onmessage = function(event){
    var data = event.data;
    var result = calcu(data);
    console.log('分线程接收到主线程数据:'+data);
    postMessage(result);
    console.log('分线程向主线程发送计算结果:'+result);
}

运行结果:

image.png

运行过程:

  1. 当点击计算按钮之后,主线程创建一个Worker实例,并用postMessage()函数向分线程发送数据。
  2. 分线程接收数据之后(触发onmessage回调函数),进行计算,最后用postMessage()向主线程发送数据。
  3. 主线程接收到分线程的数据(触发onmessage回调函数)。