Evevt Loop 事件循环
JavaScript 是一门单线程的语言
也就是说,JS一次只能做一件事情。
cpu处理指令速度非常快,远比磁盘I/O和网络I/O速度快,所以一些cpu直接执行的任务就成了优先执行主线任务(即同步任务synchronous),然后需要io返回数据的任务就成了等待被执行的任务(即异步任务asynchronous)
- 同步任务:在主线程上排队执行的任务,前一个任务执行完毕,才能执行后一个任务;
- 异步任务:不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
总之:
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制
一.什么是event Loop的执行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。异步的任务在event table 里面注册函数,当满足触发条件的时候, 进入event queque(就是事件队列)。
(3)一旦"执行栈"中的所有同步任务执行完毕,就去task queue里面查看是否有可执行的异步任务。如果,有就推入到主线程的执行栈(stack)。
(4)主线程不断重复上面的第三步。

参考上图,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
练习 异步任务-setTimeout
练习1:
console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
}, 1000);
console.log('script end');
输出顺序:
script start
script end
setTimeout
练习2:
console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
console.log('script end');
输出顺序:
script start
script end
setTimeout
练习3:
console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
// 具体数字不定,这取决于你的硬件配置和浏览器
for (let i = 0; i < 999999999; i++) {
  // do something
}
console.log('script end');
输出顺序:
script start
script end
setTimeout
练习4:
console.log('1');
setTimeout(() => {
  console.log('2');
}, 0);
setTimeout(() => {
  console.log('3');
}, 1000);
console.log('4');
输出顺序:
1
4
2
3
二 事件队列作用
由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。
同步任务
例1:

window.a = 1;
function foo() {
window.a = window.a + 1;
return foo();
}
foo();
例2:栈溢出(出现死循环):
function foo() {
  return foo();
}
foo();
结果:

例3: 同步阻塞

例4: 同步阻塞
应用
1. ajax请求
通过事件循环机制,我们则不需要等待 ajax 响应之后再进行工作。我们则是设置一个回调函数,将 ajax 请求挂起,然后继续执行后面的代码,至于请求何时响应,对我们的程序不会有影响,甚至它可能永远也不响应,也不会使浏览器阻塞。而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。
例:

输出顺序:
Hi
JSConfEU
ajax data
2. 事件监听器
事件监听器的回调函数也是一个任务,当我们注册了一个事件监听器时,浏览器事件表会进行登记,当我们触发事件时,事件表便将回调函数添加至事件队列当中。
例:

3. 同步遍历和异步遍历
// Synchronous 同步遍历
[1, 2, 3, 4].forEach((i) => {
  console.log(i);
});
// Asynchronous 异步遍历
function asyncForEach(array, cb) {
  array.forEach(() => {
    setTimeout(cb, 0);
  });
}
asyncForEach([1, 2, 3, 4], (i) => {
  console.log(i);
});
采用同步遍历的方法,当数组长度上升到3位数的时候,便会出现阻塞,但是异步遍历却不会出现阻塞现象(除非数组长度非常大,那是因为计算机的内存空间不足)。
这是因为同步遍历方法是一个单独的任务,这个任务会将所有的数组元素遍历一遍,然后才会开始下一个任务。而异步遍历的方法将每一次遍历拆分成一个单独的任务,一个任务只遍历一个数组元素,所以在每个任务之间,我们的浏览器可以进行渲染,所以我们不会看见阻塞的情况。

4. UI渲染
DOM 操作会触发浏览器对文档进行渲染,如修改排版规则,修改背景颜色等等,那么这类操作是如何在浏览器当中奏效的?
至此我们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的情况,这也使 JS 动画成为可能, jQuery 动画在底层均是使用 setTimeout 和 setInterval 来进行实现。
想象一下如果我们同步的执行动画,那么我们不会看见任何渐变的效果,浏览器会在任务执行结束之后渲染窗口。反之我们使用异步的方法,浏览器会在每一个任务执行结束之后渲染窗口,这样我们就能看见动画的渐变效果了。

三. Microtasks 和 Macrotasks
具体到任务队列,又分位 microtasks 和 macrotasks
属于microtasks的任务有:
process.nextTick
promise.then
Object.observe
MutationObserver
属于macrotasks的任务有:
setTimeout
setInterval
setImmediate
I/O
UI渲染
在执行事件时:
- 从script(整体代码)开始第一次循环。执行所有主线程的同步函数,遇到异步函数,分别添加到microtasks和macrotasks任务队列
- 同步函数执行后,开始执行异步函数中的任务队列,首先执行所有的micro-task所有的micro-task执行完成后,循环执行macro-task任务, 再次执行micro-task,这样一直循环下去.
总之:
整体的js代码在执行完主线程的同步任务,然后有microtask执行microtask,没有microtask执行下一个macrotask,如此往复循环至结束
练习
练习1:
setTimeout(() => {
  console.log(4);
}, 0);
new Promise(((resolve) => {
  console.log(1);
  for (let i = 0; i < 10000; i++) {
    i == 9999 && resolve();
  }
  console.log(2);
})).then(() => {
  console.log(5);
});
console.log(3);
输出顺序:
1
2
3
5
4
练习2
console.log('start');
const interval = setInterval(() => {
  console.log('setInterval');
}, 0);
setTimeout(() => {
  console.log('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('promise 3');
  }).then(() => {
    console.log('promise 4');
  }).then(() => {
    setTimeout(() => {
      console.log('setTimeout 2');
      Promise.resolve().then(() => {
        console.log('promise 5');
      }).then(() => {
        console.log('promise 6');
      }).then(() => {
        clearInterval(interval);
      });
    }, 0);
  });
}, 0);
Promise.resolve().then(() => {
  console.log('promise 1');
}).then(() => {
  console.log('promise 2');
});
输出顺序:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
总结
JavaScript 是一门单线程的语言,但是其事件循环的特性使得我们可以异步的执行程序。这些异步的程序也就是一个又一个独立的任务,这些任务包括了 setTimeout、setInterval、ajax、eventListener 等等。关于事件循环,我们需要记住以下几点:
- 事件队列严格按照时间先后顺序将任务压入执行栈执行;
- 当执行栈为空时,浏览器会一直不停的检查事件队列,如果不为空,则取出第一个任务;
- 在每一个任务结束之后,浏览器会对页面进行渲染;
参考:
Evevt Loop 事件循环的更多相关文章
- javascript的event loop事件循环
		javascript的event loop事件循环 这是今天一个朋友发给我的一个面试题, 感觉还挺有意思的, 写个博客以供分享 先看看这个面试题目: 观察下面的代码,写出输出结果 console.lo ... 
- node.js中对Event Loop事件循环的理解
		javascript是单线程的,所以任务的执行都需要排队,任务分为两种,一种是同步任务,一种是异步任务. 同步任务是进入主线程上排队执行的任务,上一个任务执行完了,下一个任务才会执行. 异步任务是不进 ... 
- 为什么JS是单线程?JS中的Event Loop(事件循环)?JS如何实现异步?setimeout?
		https://segmentfault.com/a/1190000012806637 https://www.jianshu.com/p/93d756db8c81 首先,请牢记2点: (1) JS是 ... 
- Event Loop事件循环,GET!
		JS中比较让人头疼的问题之一要算异步事件了,比如我们经常要等后台返回数据后进行dom操作,又比如我们要设置一个定时器完成特定的要求.在这些同步与异步事件里,异步事件肯定是在同步事件之后的,但是异步事件 ... 
- 浅谈 Event loop (事件循环)
		从Event Loop谈JS的运行机制 先来理解一个概念: JS分为同步任务和异步任务 同步任务都在主线程上执行,形成一个执行栈 Execute Content Stack 主线程之外,事件触发线程管 ... 
- 进程,线程,Event Loop(事件循环),Web Worker
		线程,是程序执行流的最小单位.线程可与同属一个进程的其他线程共享所拥有的全部资源,同一进程中的多个线程之间可以并发执行.线程有就绪,阻塞,运行三种基本状态. 阮一峰大神针对进程和线程的类比,很是形象: ... 
- js event loop事件循环
		浏览器环境 以下两段代码是等价的.req对事件的回调设置,实际上就是当前主线程任务队列的任务. var req = new XMLHttpRequest(); req.open('GET', url) ... 
- JavaScript event loop事件循环  macrotask与microtask
		macrotask 姑且称为宏任务,在很多上下文也被简称为task.例如: setTimeout, setInterval, setImmediate, I/O, UI rendering. mic ... 
- JavaScript Concurrency model and Event Loop 并发模型和事件循环机制
		原文地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop JavaScript 有一个基于 event loop 的 ... 
随机推荐
- Java面向对象程序设计第15章5
			5. 利用URLConnetction对象编写程序返回某网站的首页,并将首页的内容存放到文件当中. import java.net.*; import java.io.*; public class ... 
- 018.Kubernetes二进制部署插件coredns
			一 修改配置文件 1.1 下载解压 [root@k8smaster01 ~]# cd /opt/k8s/work/kubernetes/ [root@k8smaster01 kubernetes]# ... 
- PHP 从另一个角度来分析 Laravel 框架的依赖注入功能
			从根本上说,依赖注入不是让对象创建一个依赖关系,也不是让工厂对象去创建对象,而是将所需的依赖变成一个外部对象,使之成为一个"某些人的问题” 你为"某些人的问题”注入了类的依赖关系. ... 
- PHP 将数据从 Laravel 传送到 vue 的四种方式
			在过去的两三年里,我一直在研究同时使用 Vue 和 Laravel 的项目,在每个项目开发的开始阶段,我必须问自己 “我将如何将数据从 Laravel 传递到 Vue ?”.这适用于 Vue 前端组件 ... 
- Django 项目笔记
			Django 环境的搭建 Django 安装 pip install django==2.1.4 Django 创建项目 django-admin startproject mysite Django ... 
- nyoj 125-盗梦空间 (数学ans += temp * 60 * pow(0.05, cnt))
			125-盗梦空间 内存限制:64MB 时间限制:3000ms 特判: No 通过数:8 提交数:10 难度:2 题目描述: <盗梦空间>是一部精彩的影片,在这部电影里,Cobb等人可以进入 ... 
- 在CentOS安装消息中间件RabbitMQ
			一.在安装前,请确认CentOS是否可以联网 1.可以用SecureCRT工具连接centos,这方面请自行百度. 2.CRT连接成功后可以通过ping www.baidu.com 查看是否成功,确认 ... 
- 学习记录:《C++设计模式——李建忠主讲》5.“对象性能”模式
			对象性能模式:面向对象很好地解决了抽象地问题,但是必不可免地要付出一定地代价.对于通常情况来讲,面向对象地成本大都可以忽略不计,但某些情况,面向对象所带来地成本必须谨慎处理. 典型模式:单件模式(Si ... 
- [FPGA]Verilog实现JK触发器组成的8421BCD码十进制计数器
			目录 概述 电路分析 代码实现 参考文献 概述 本文以异步时序计数器为例,用Verilog实现以\(JK\)触发器组成的8421BCD码十进制异步计数器,并用ModelSim软件进行仿真验证. 电路分 ... 
- Android的系统框架的深入认识
			Android采用层次化系统架构,官方公布的标准架构如下图所示.Android由底层往上分为4个主要功能层,分别是linux内核层(Linux Kernel),系统运行时库层(Libraries和An ... 
