JavaScript 执行— 事件循环、宏观任务、微观任务
从浏览器或者 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 执行— 事件循环、宏观任务、微观任务相关推荐
- 深入理解JavaScript的事件循环(Event Loop) 一、什么是事件循环
深入理解JavaScript的事件循环(Event Loop) 一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为因为它经常被用于类似如下的方式来实现 ...
- 技术干货 | JavaScript 之事件循环(Event Loop)
导读:学过 JavaScript(下文简称 JS) 的都知道它是一门单线程的.非阻塞的脚本语言.单线程意味着,JS 代码在执行的任何时候,都只有一个主线程来处理所有的任务,这也就意味着 JS 无法进行 ...
- 从一道题浅说 JavaScript 的事件循环
阮老师在其推特上放了一道题: new Promise(resolve => {resolve(1);Promise.resolve().then(() => console.log(2)) ...
- 深入理解JavaScript的事件循环
最近阅读<高性能JavaScript>时,第六章谈到"通过定时器将JavaScript执行代码的控制权先让给浏览器用于更新UI状态,然后再将控制权交回给JavaScript代码, ...
- 聊聊Javascript的事件循环
JavaScript.浏览器.事件之间的关系 JavaScript程序采用了异步事件驱动编程(Event-driven programming)模型,维基百科对它的解释是: 事件驱动程序设计(英语:E ...
- JavaScript中事件循环的理解 Event Loop
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...
- js执行机制(宏观,微观)
1.macro-task(宏任务):包括整体script代码,setInterval,setTimeout 2.micro-task(微任务):promise ,process.nexttrick(n ...
- onpaste事件不生效_从实际开发中来看JavaScript事件循环的使用场景
前言: 本文是介绍结合DOM事件流和JavaScript事件循环解决一个工作中的实际问题的过程,很多东西不只是面试的时候才会用得到 文中涉及到的代码demo地址:drag-and-eventloop ...
- JavaScript事件循环探索
一直对js的事件循环不是很清晰,最近看了JavaScript忍者秘籍的第13章后,有了一些感悟,特此总结一下,分享给大家. 单线程 众所周知,JavaScript是单线程执行模型,同一时刻只能执行一个 ...
最新文章
- MySQL图形处理软件Navicat字体配置(乱码解决)
- 第一个libgdx程序--仿别踩白块
- 用python定位手机_使用Python定位android和iphone
- nodejs 框架 中文express 4.xxx中文API手册
- 23种设计模式之《单例模式》
- select2实现全选
- verilog学习记(加法器)
- Oracle中一把梭获取对象DDL创建语句
- kafaka的消息存储机制
- 强制卸载kernel
- 求两个整数最大公约数
- android系统控件大全,安卓系统控件大全
- JAVA设计模式之概述
- 前端之jquery-jQuery中$(function(){})与(function($){})(jQuery)、$(document).ready(function(){})区别
- 车牌输入法 车牌号快捷输入法 支持普通车牌新能源车牌
- Hipo-iPod 处理对象
- php识别人脸并提取特征值,C#人脸识别入门篇--提取人脸特征值及人脸识别
- Python SDK是什么
- x32计算机控制,Behringer干货|带你用电脑和ipad玩转X32
- tensorflow版本对应关系