本质上来说,JavaScript是同步的、阻塞的、单线程语言,不管是在浏览器中还是nodejs环境下。浏览器在执行js代码和渲染DOM节点都是在同一个线程中,执行js代码就无法渲染DOM,渲染DOM的时候就无法执行js代码。如果按照这种同步方式执行,页面的渲染将会出现白屏甚至是报错,特别是遇到一些耗时比较长的网络请求或者js代码,因此在实际开发中一般是通过异步的方式解决。

什么是异步?js是一步一步执行代码的,遇到alert这种阻塞代码时,js将会停止往下执行直到阻塞代码执行完毕。异步就是将函数放在单独的异步队列中,不会产生阻塞,js可以继续往下执行,等到同步代码执行完毕后再执行异步队列中的函数。因此,js会先执行完同步代码,才会执行异步代码。

异步也有细分,每一个异步函数可当作是一个待执行的任务,可分为宏任务和微任务。本文重点介绍浏览器的事件循环,nodejs环境下的事件循环放在下一篇中继续探讨。

宏任务

宏任务,也可简单的说成是任务,在下一轮DOM渲染之后执行。常见的宏任务有:

setTimeout:设置一个定时器,该定时器会在设置的延迟时间到期后执行一个函数或者指定的代码块。值得注意的是,setTimeout不一定会在延迟时间到达后就立即执行函数,而是会判断执行队列中是否还有函数没有处理,如果没有了并且栈为空,setTimeout才会在延迟时间到达后执行函数。

// setTimeout 延迟执行不等于到期时立即执行
let now = new Date().getSeconds();
setTimeout(() => {
    console.log('this is setTimeout 0');
}, 0);
setTimeout(() => {
    console.log('this is setTimeout 200');
}, 200);
while(true) {
    if (new Date().getSeconds() - now >= 2) {
        console.log('break out while loop');
        break;
    }
}

运行结果

break out while loop
this is setTimeout 0
this is setTimeout 200

先执行同步代码,再执行异步。setTimeout(() => {}, 0)表示0毫秒后立即执行函数,但是当前执行队列中还有未处理完的while循环,因此需要等到while循环执行完毕后,才会根据延迟到期时间执行函数。

setInterval:设置定时器,表示在固定的时间间隔内,重复执行某一函数或者特定的代码块。注意使用setInterval有最小延迟时间限制以及确保执行时间要小于间隔时间,如果执行时间无法确定,则应采用递归调用setTimeout的方式代替。

网络请求:只要是指XMLHttpRequest等网络请求

微任务

微任务,在下一轮DOM渲染之前执行,微任务比宏任务更早执。常见的微任务有:

promise:表示一个异步操作最终的结果和返回值,可能会失败,也可能成功。异步函数在执行时,什么时候返回结果是不可预料的,Promise把异步操作的返回值和函数关联起来,保证在异步执行结束后会执行对应的函数,并通过函数返回操作值。这种效果就类似于把异步代码“同步执行”。

queueMicrotask:将函数添加到微任务队

console.log('start');
// 微任务队列
Promise.resolve().then(() => {
    console.log('promise then');
});
queueMicrotask(() => {
    console.log('queueMicrotask');
});
console.log('end');

运行结果

start
end
promise then
queueMicrotask

事件循环

因为有异步操作的存在,所以出现了事件循环,如果都是同步操作,一行一行执行代码,事件循环也就失去了用武之地。在了解事件循环前,还需要补充js的执行过程:

js在执行代码时,遇到函数就会将其添加到调用栈中,每一帧都会存储当前函数的参数和局部变量,当一个函数执行完毕,则会从调用栈中弹出,直到栈被清空,那么程序也就执行完毕。在执行的过程中,需要的引用数据都是从堆中获取。

在实际开发中,往往是同步代码和异步代码都有。在js执行时,还是从第一行代码开始执行,遇到函数就将其添加到栈中,然后执行同步操作;如果遇到异步函数,则根据其类型,宏任务就添加到宏任务队列,微任务添加到微任务队列。直到同步代码执行完毕,则开始执行异步操作。

异步操作后于同步操作,异步操作内部也是分先后顺序的。总的来说:

  • 微任务先于宏任务执行
  • 微任务与微任务之间根据先后顺序执行,宏任务与宏任务之间根据延迟时间顺序执行
  • 微任务在下一轮DOM渲染前执行,宏任务在下一轮DOM渲染之后执行
  • 每个任务的执行都是一次出栈操作,直到栈被清空

微任务比宏任务先执行

console.log('start');
// 宏任务队列
setTimeout(() => {
    console.log('setTimeout');
});
// 微任务队列
Promise.resolve().then(() => {
    console.log('promise then');
});
console.log('end');

执行结果

start
end
promise then
setTimeout

微任务在下一轮DOM渲染前执行,宏任务在之后执行

let div = document.createElement('div');
div.innerHTML = 'hello world';
document.body.appendChild(div);
let divList = document.getElementByTagName('div');
console.log('同步任务 length ---', list.length);
console.log('start');
setTimeout(() => {
    console.log('setTimeout length ---', list.length);
    alert('宏任务 setTimeout 阻塞'); // 使用alert阻塞js执行
});
Promise.resolve().then(() => {
    console.log('promise then length ---', list.length);
    alert('微任务 promise then 阻塞);
});
console.log('end');

事件循环

event loop会持续监听是否有异步操作,如果有则添加到对应的队列中,等待执行。例如在宏任务中添加微任务,或者在微任务中添加宏任务,当前任务执行完后,可能还会有新的任务添加到事件循环中。

宏任务与微任务

微任务中创建宏任务

    new Promise((resolve) => {
      console.log('promise 1');
      setTimeout(() => {
        console.log('setTimeout 1');
      }, 500);
      resolve();
    }).then(() => {
      console.log('promise then');
      setTimeout(() => {
        console.log('setTimeout 2');
      }, 0);
    });
    new Promise((resolve) => {
      console.log('promise 2');
      resolve();
    })

运行结果

promise 1
promise 2
promise then
setTimeout 2
setTimeout 1

解析

js执行代码,遇到两个Promise,则分别添加到微任务队列,同步代码执行完毕。

在微任务队列中根据先进先出,第一个Promise先执行,遇到setTimeout,则添加到宏任务队列,resolve()返回执行结果并执行then,事件循环将其继续添加到微任务队列;第一个Promise执行完毕,执行第二个Promise。

继续执行微任务队列,直到清空队列。遇到setTimeout,并将其添加到宏任务队列

宏任务队列现在有两个任务待执行,由于第二个setTimeout的延迟事件更小,则优先执行第二个;如果相等,则按照顺序执行。

继续执行宏任务队列,直到清空队列。

宏任务中创建微任务

    setTimeout(() => {
      console.log('setTimeout 1');
      new Promise((resolve) => {
        console.log('promise 1');
        resolve();
      }).then(() => {
        console.log('promise then');
      })
    }, 500);
    setTimeout(() => {
      console.log('setTimeout 2');
      new Promise((resolve) => {
        console.log('promise 2');
        resolve();
      })
    }, 0);

运行结果

setTimeout 2
promise 2
setTimeout 1
promise 1
promise then

解析

js执行代码,遇到两个setTimeout,将其添加到宏任务队列,同步代码执行完毕

先检查微任务队列中是否有待处理的,刚开始肯定没有,因此直接执行宏任务队列中的任务。第二个为零延迟,需要优先执行。遇到Promise,将其添加到微任务队列。第一个宏任务执行完毕

在执行第二个宏任务时,微任务队列中已经存在待处理的,因此需要先执行微任务。

微任务执行完毕,并且延迟时间到期,第一个setTimeout开始执行。遇到Promise,将其添加到微任务队列中

执行微任务队列中的Promise,执行完毕后遇到then,则将其继续添加到微任务队列

直到所有微任务执行完毕

宏任务中创建宏任务

    setTimeout(() => {
      console.log('setTimeout 1');
      setTimeout(() => {
        console.log('setTimeout 2');
      }, 500);
      setTimeout(() => {
        console.log('setTimeout 3');
      }, 500);
      setTimeout(() => {
        console.log('setTimeout 4');
      }, 100);
    }, 0);

运行结果

setTimeout 1
setTimeout 4
setTimeout 2
setTimeout 3

解析

宏任务中创建宏任务,执行顺序一般来说是按照先后顺序的。对于setTImeout来说,延迟时间相同,则按照先后顺序执行;延迟时间不同,则按照延迟时间的大小先后顺序执行

微任务中创建微任务

    new Promise((resolve) => {
      console.log('promise 1');
      new Promise((resolve) => {
        console.log('promise 2');
        resolve();
      });
      new Promise((resolve) => {
        console.log('promise 3');
        resolve();
      })
      resolve();
    })

运行结果

promise 1
promise 2
promise 3

解析

微任务中创建微任务,执行顺序一般来说是按照先后顺序执行的。

总结

  • 同步代码直接执行,异步代码添加到宏任务队列或者微任务队列
  • 微任务在下一轮DOM渲染前执行,宏任务在下一轮DOM渲染之后执行
  • 事件循环持续监听
  • 如果存在异步操作,需要将关联代码放在异步函数中执行
  • 如果代码层次比较复杂,同步、异步代码混杂,一定要理清代码的执行顺序。避免因为异步,导致代码出现难以察觉的bug

到此这篇关于详解JS事件循环及宏任务微任务的原理的文章就介绍到这了,更多相关JS宏任务 微任务内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

详解JS事件循环及宏任务微任务的原理的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  4. Swift与Js通过WebView交互

    开发环境:Swfit2.3XCode8.2基础概念jscontext,jscontext是代表JS的执行环境,通过-evaluateScript:方法就可以执行一JS代码JSValue,JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等JSExport,JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用Swif

  5. JSCore swift

    如果双方相互引用,会造成循环引用,而导致内存泄露。以上是Jscore的基本使用,比较简单

  6. Swift WKWebView的js调用swift

    最近项目需求,需要用到JavaScriptCore和WebKit,但是网上的资源有限,而且比较杂,都是一个博客复制另外一个博客,都没有去实际敲代码验证,下面给大家分享一下我的学习过程。

  7. Swift WKWebView的swift调用js

    不多说,直接上代码:在html里面要添加的的代码,显示swift传过去的参数:这样就实现了swift给js传参数和调用!

  8. 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器

    你有強烈的好奇心,希望在你的iOS專案中使用JavaScript。jscontext中的所有值都是JSValue對象,JSValue類用於表示任意類型的JavaScript值。因此,我們既需要寫Swift代碼也要寫JavaScript代碼。此外,我們還會在JavaScript中按照這個類的定義來創建一個對象并對其屬性進行賦值。從Swift中呼叫JavaScript就如介紹中所言,JavaScriptCore中最主要的角色就是jscontext類。一個jscontext對象是位於JavaScript環境和本

  9. swift - WKWebView JS 交互

    本文介绍WKWebView怎么与js交互,至于怎么用WKWebView这里就不介绍了HTML代码APP调JS代码结果JS给APP传参数首先注册你需要监听的js方法名2.继承WKScriptMessageHandler并重写userContentController方法,在该方法里接收JS传来的参数3.结果

  10. swift 开发UIWebView跟JS的交互

    前言作为小白的我,才开始入门IOS,选择了swift来进行入门学习,学习做着公司一个简单的小小项目,该项目需要进行跟H5进行交互,然后我就开始研究了UIWebView的使用,其实基本原理跟Android的一样,因为我是Android开发的,所以就顺水推舟了。))//这里设置你需要加载的地址}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()//disposeofanyresourcesthatcanberecreate

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部