从浏览器或者 Node 开发者的角度来看,我们应该如何使用 JS 引擎?

当拿到一段 JS 代码时,浏览器或者 Node 环境首先要做的就是;传递给 JS 引擎,并要求它去执行。然而,执行 JS 并非是一锤子买卖,宿主环境当遇到一些事件时,会继续把代码传递给 JS 引擎去执行。此外,我们还会提供 API 给 JS 引擎,比如 setTimeout 这样的 API,它会允许 JS 在特定的时机执行。

所以,一个 JavaScript 引擎一定是常驻于内存中,它等待着宿主把 JS 代码或者函数传递给它执行。

在 ES3 及更早版本,JavaScript 本身还没有异步执行代码的能力,所以宿主环境传递给 JS 引擎的代码会直接顺次执行。但是,ES5 之后,JavaScript 引入了 Promise,这样无需宿主的安排,JS 引擎本身也可以发起任务。

宏观和微观任务

在 JavaScript 中,我们把宿主发起的任务称为「宏观任务」,把 JS 引擎发起的任务称为「微观任务」。

JavaScript 引擎等待宿主环境分配宏观任务,在操作系统中,通常等待的行为都是一个「事件循环」,所以在 Node 术语中,这部分也叫做「事件循环」。在底层 C/C++ 代码中,这个事件循环是一个跑在独立独立线程中的循环。整个循环做的事情基本就是「等待 - 执行」。这里每次的执行过程,其实就是一个「宏观任务」。可以理解为:「宏观任务的队列」就相当于「事件循环」。

在宏观任务中,JS 的 Promise 还会产生异步代码,JS 必须保证这些异步代码在一个宏观任务中完成,因此每个宏观任务中又包含了一个「微观任务队列」。

有了「宏观任务」和「微观任务」之后,我们就可以实现 JS 引擎级和宿主级的任务了。例如,Promise 永远在队列尾部添加「微观任务」。setTimeout 等宿主的 API 会添加「宏观任务」。

Promise

Promise 是 JavaScript 语言提供一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其他异步操作的函数,不返回真是结果,而是返回一个「承诺」,函数的调用方可以在合适的机会,选择等待这个承诺的兑现(通过 Promise 的 then 方法回调)。

Promise 的执行顺序

var r = new Promise(function(resolve, reject){console.log("a");resolve()
});
r.then(() => console.log("c"));
console.log("b")

上面代码输出的顺序是 a、b、c。在执行 console.log("b") 之前,毫无疑问 r 已经得到了 resolve,但是 Promise 的 resolve 是异步操作,所以 c 无法出现自 b 之前。

接下来试试 setTimeout 与 Promise 一起使用

setTimeout(()=>console.log("d"), 0)var r = new Promise(function(resolve, reject){resolve()
});
r.then(() => {var begin = Date.now();while(Date.now() - begin < 1000);console.log("c1")new Promise(function(resolve, reject){resolve()}).then(() => console.log("c2"))
});

上面代码的输出 c1、c2、d。我们发现,不论代码的顺序如何,d 必定是发生在 c 之后。因为 Promise 产生的是微观任务,setTimeout 产生的是宏观任务,而微观任务总是先于宏观任务被执行。

通过一系列的实验,我们可以总结如何分析异步执行的顺序:

  • 首先我们要分析有多少个宏任务;

  • 每个宏任务中,又有多少个微任务;

  • 根据调查次序,确定宏任务中的微观任务执行顺序;

  • 根据宏任务的触发规则和调用次序,确定宏任务的执行次序;

  • 确定整个顺序。

function sleep(duration) {return new Promise(function(resolve, reject) {console.log("b");setTimeout(resolve,duration);})
}
console.log("a");
sleep(5000).then(()=>console.log("c"));

这是一段代码常用的封装方法,利用 Promise 把 setTimeout 封装成可以用于异步的函数。

setTimeout 把整个代码分割成 2 个宏观任务,第一个宏观任务中,包含了先后同步执行的 console.log("a") 和 console.log("b")。在 setTimeout 之后,第二个宏观任务调用了 resolve,然后 then 中的代码的到执行,所以调用了 console.log("a"),最终输出的顺序才是 a、b、c。

新特性:async/await

async/await 是 ES6 中新加入的特性。它提供了用 for、if 等代码结构来编写异步代码的方式。它的运行时基础是 Promise。

async 函数必定返回 Promise,我们把所有返回 Promise 的函数都可认为是异步函数。

async 是一种特殊语法,在 function 关键字前加上 async 关键字,这样就定义了一个 async 函数,现在我们可以在函数中使用 await 来等待一个 Promise。

function sleep(duration) {return new Promise(function(resolve, reject) {setTimeout(resolve,duration);})
}
async function foo(name){await sleep(2000)console.log(name)
}
async function foo2(){await foo("a");await foo("b");
}

此外 generator/iterator 也常常跟异步一起来讲,但是 generator/iterator 并非异步代码,只是在缺少 async/await 的地方,来模拟 async/await。

思考

我们现在要实现一个红绿灯,把圆形的 div 按照绿色 3 秒,黄色 1 秒,红色 2 秒循环改变背景色。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Traffic Light</title>
</head>
<style>
#traffic-light {width: 30px;height: 30px;border-radius: 15px;margin: 8px;
}
/* .green-light {background-color: #008000;} */
.yellow-light {background-color: #caca2a;
}
.red-light {background-color: #e01616;
}
</style>
<body>
<div id="traffic-light"></div>
</body><script>let light = document.getElementById("traffic-light");
let green = "#008000";
let yellow = "#caca2a";
let red = "#f44";function sleep(duration) {return new Promise(function(resolve, reject) {setTimeout(resolve, duration);});
}async function changeColor(color, duration) {light.style.backgroundColor = color;await sleep(duration);
}async function main() {while (true) {    // 不能直接放在主线程,会卡主 UIawait changeColor(green, 3000);await changeColor(yellow, 1000);await changeColor(red, 2000);}
}main();
</script></html>

JavaScript 执行— 事件循环、宏观任务、微观任务相关推荐

  1. 深入理解JavaScript的事件循环(Event Loop) 一、什么是事件循环

    深入理解JavaScript的事件循环(Event Loop) 一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为因为它经常被用于类似如下的方式来实现 ...

  2. 技术干货 | JavaScript 之事件循环(Event Loop)

    导读:学过 JavaScript(下文简称 JS) 的都知道它是一门单线程的.非阻塞的脚本语言.单线程意味着,JS 代码在执行的任何时候,都只有一个主线程来处理所有的任务,这也就意味着 JS 无法进行 ...

  3. 从一道题浅说 JavaScript 的事件循环

    阮老师在其推特上放了一道题: new Promise(resolve => {resolve(1);Promise.resolve().then(() => console.log(2)) ...

  4. 深入理解JavaScript的事件循环

    最近阅读<高性能JavaScript>时,第六章谈到"通过定时器将JavaScript执行代码的控制权先让给浏览器用于更新UI状态,然后再将控制权交回给JavaScript代码, ...

  5. 聊聊Javascript的事件循环

    JavaScript.浏览器.事件之间的关系 JavaScript程序采用了异步事件驱动编程(Event-driven programming)模型,维基百科对它的解释是: 事件驱动程序设计(英语:E ...

  6. JavaScript中事件循环的理解 Event Loop

    为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...

  7. js执行机制(宏观,微观)

    1.macro-task(宏任务):包括整体script代码,setInterval,setTimeout 2.micro-task(微任务):promise ,process.nexttrick(n ...

  8. onpaste事件不生效_从实际开发中来看JavaScript事件循环的使用场景

    前言: 本文是介绍结合DOM事件流和JavaScript事件循环解决一个工作中的实际问题的过程,很多东西不只是面试的时候才会用得到 文中涉及到的代码demo地址:drag-and-eventloop ...

  9. JavaScript事件循环探索

    一直对js的事件循环不是很清晰,最近看了JavaScript忍者秘籍的第13章后,有了一些感悟,特此总结一下,分享给大家. 单线程 众所周知,JavaScript是单线程执行模型,同一时刻只能执行一个 ...

最新文章

  1. MySQL图形处理软件Navicat字体配置(乱码解决)
  2. 第一个libgdx程序--仿别踩白块
  3. 用python定位手机_使用Python定位android和iphone
  4. nodejs 框架 中文express 4.xxx中文API手册
  5. 23种设计模式之《单例模式》
  6. select2实现全选
  7. verilog学习记(加法器)
  8. Oracle中一把梭获取对象DDL创建语句
  9. kafaka的消息存储机制
  10. 强制卸载kernel
  11. 求两个整数最大公约数
  12. android系统控件大全,安卓系统控件大全
  13. JAVA设计模式之概述
  14. 前端之jquery-jQuery中$(function(){})与(function($){})(jQuery)、$(document).ready(function(){})区别
  15. 车牌输入法 车牌号快捷输入法 支持普通车牌新能源车牌
  16. Hipo-iPod 处理对象
  17. php识别人脸并提取特征值,C#人脸识别入门篇--提取人脸特征值及人脸识别
  18. Python SDK是什么
  19. x32计算机控制,Behringer干货|带你用电脑和ipad玩转X32
  20. tensorflow版本对应关系

热门文章

  1. 组播IP转换为组播MAC地址【转载】
  2. 动态计算字符串的字体大小并据此设置行间距---NSAttributedString
  3. mysql 获取分区的最大值_MySQL分区表测试
  4. 正则表达式的方法及其匹配规则
  5. javaScript改变css样式
  6. 云端还是本地,数据放哪儿更安全之泄密事件类型分析篇
  7. PERT2型保温管/PERT2型管材焊接前要特别注意什么
  8. java反射原理-重要
  9. 坎 坎为水 坎上坎下
  10. python实现简单小游戏_用python开发一个有趣的猜数字小游戏(实现简单的GUI界面学习)...