资讯专栏INFORMATION COLUMN

Javascript事件循环入门到“忘记”(一).md

codeGoogle / 3273人阅读

摘要:中叫做调用栈先进后出,后进先出。如下图这是典型的内存溢出,可能会出现在某些场景下需要递归,但业务逻辑中的判断又没能正常计算进入到预设情况,于是调用栈中不断进入,又无法执行完,就造成内存溢出了。

本文主要介绍Javascript事件循环在浏览器上的一些特性和应用介绍。
Javascript小知识

JavaScript的并发模型基于"事件循环"(Event Loop)。这个模型与像C或者Java这种其它语言中的模型截然不同。它永不阻塞,处理I/O通常通过事件和回调来执行,所以当一个应用正等待IndexedDB查询返回或者一个XHR请求返回时,它仍然可以处理其它事情,如用户输入。【参:并发模型与事件循环】

需要了解的几点:

单线程编程语言(Single Threaded)

只有一个主线程(one thread),并且只有一个调用栈(Call Stack),因此同一时间只能执行同一件事情。【参:Philip Roberts: What the heck is the event loop anyway? | JSConf EU (4:15)】

执行上下文(Execution Context)

Javascript代码执行时,会进入一个执行上下文。它可以理解为当前代码的运行环境(包括三种:全局环境、函数环境、Eval环境)。【参:Javascript核心技术开发解密 Page-11】

纠正一点分享会可能存在的错误,它和作用域(Scope)不同!作用域是针对变量的一个可访问区域,而执行上下文是属于函数的指向的对象。(Scope pertains to the visibility of variables, and context refers to the object to which a function belongs.)【参:Why Should We Care About Scope and Context ?】

栈(stack)

函数调用形成了一个栈帧。JavaScript中叫做调用栈(Call Stack);先进后出,后进先出(LIFO)。

堆(heap)

对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。

队列(queue)

一个JavaScript运行时包含了一个待处理的消息队列。每一个消息都有一个为了处理这个消息相关联的函数。

任务(Task)

主要是队列中要执行的函数。主要包含以下两大类:

macrotask:包含执行整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI render

microtask:更新应用程序状态的任务,包括promise回调,MutationObserver,process.nextTick,Object.observe

下图展示了Event Loop的机制

这些代码会发生什么? 基于JQuery的Ajax示例,如果没有异步多么可怕!
// This is assuming that you"re using jQuery
jQuery.ajax({
  url: "https://api.example.com/endpoint",
  success: function(response) {
    // This is your callback.
  },
  async: false // And this is a terrible idea
});
// 原文網址:https://itw01.com/2Z6WE2L.html

这里使用了JQuery的Ajax函数,并为参数设置为同步执行。那么将遇到一种可怕的情况,这段代码在success回调前,后面的Javascript代码将不再执行。也就造成了可怕的阻塞(blocking)。

这段代码什么鬼,看着有点晕XD
let bar = 0
function foo() {
  bar++
  if (bar > 0) {
    return foo()
  }
}
foo()

没错,如果你不晕,说明你太棒了。这段代码也会产生严重的问题。如下图:

这是典型的内存溢出,可能会出现在某些场景下需要递归,但业务逻辑中的判断又没能正常计算进入到预设情况,于是调用栈中不断进入foo(),又无法执行完,就造成内存溢出了。

纠正一处分享会中的错误,这个入栈过程没有任何函数退出,所以会只进不出,导致内存爆炸。另外道哥提到的不断累加到最大值为负数的情况,我测试了一下JS下,会变成Infinite。某些其他语言(例如:C)是会变成-1,和二进制进位有关。
小测验:Demo - 1
setTimeout(() => {
  console.log(1);
}, 0);
console.log(2);
for (let i = 0; i < 3; i++) {
  console.log(i);
}
console.log(4);

输出结果:

A: 1, 2, 0, 1, 2, 4
B: 2, 4, 0, 1, 2, 1
C: 2, 0, 1, 2, 4, 1
D: 2, 4, 0, 1, 2, 1

小测验:Demo - 2
console.log(1);
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log("2-" + i);
  }, 0);
}
console.log(3);

输出结果:

A: 1, 2-2, 2-2, 2-2, 3
B: 1, 3, 2-2, 2-2, 2-2
C: 1, 2-0, 2-1, 2-2, 3
D: 1, 3, 2-0, 2-1, 2-2

我想大家应该都正确答出来了吧:D,接下来我将详细分析一些示例,以便于理解事件循环。

事件循环流程分析 示例分析1:

这里我借用了作者稀土掘金的深入理解事件迴圈和非同步流程控制文中的一段示范。

console.log("Hi")
setTimeout(function cb1() {
  console.log("cb1")
}, 5000)
console.log("Bye")

不论是否懂得事件循环的初学者,看到这段代码应该也能猜出来答案是: Hi Bye cb1。毕竟cb1有一个5s的定时器。但是执行细节是怎样的呢。我们来看下面这张gif图。

图中已经很清楚的展示了整个Javascript代码是如何运作的。相信大家已经有较大的收获了。

示例分析2:

我们来看这个页面中的Javascript部分:

function one() {
  throw new Error("Oops!")
}
function two() {
  one()
}
function three() {
  two()
}
three()

我们在浏览器端执行时,打个断点在throw new Error("Oops!")这一行。如下图:

在了解了事件循环的执行顺序后,我们可以轻松知道他的执行顺序,通过Chrome开发者工具、我们观察图中Call Stack区域,箭头指向的one也正是我们断点的地方,下面依次是two、three、(anonymous),这个是完全符合栈的先进后出,后进先出(last-in-first-out)的特征~

我们在实际开发中,也可以通过Call Stack里面观察,找出上一层入口,分析异常原因。会有很大的帮助呢~

接着关闭断点继续执行,浏览器会抛出错误,错误信息如下,也是符合栈特点的

其他

该文章中有部分内容在我制作的PPT中并未体现出来,对于这次分享会,我对Javascript一些运行机制有更深的理解,由于时间仓促也就在本次分享做了一点入门介绍。下期我将会结合更多的示例,对Node.js的事件循环与浏览器端的差异等等进行更深入的介绍,当大家都有所收获后,就大可忘记了。

文中参考的一些资料(★表示推荐等级)

深入理解js事件循环机制(浏览器篇) ★★★★☆

深入理解事件迴圈和非同步流程控制 ★★★★☆

Philip Roberts: What the heck is the event loop anyway? | JSConf EU ★★★★★

这是一段来自Youtube的演讲视频,视频中有用到一个工具"loupe - 模拟执行顺序的工具",值得研究! ★★★★★

Tasks, microtasks, queues and schedules ★★★★★

文中有例子通过动画来展示执行顺序问题感觉超级棒!也对不通浏览器的结果有做分析,当然也许部分内容有些不一致,需要注意。

[The JavaScript Event Loop [Presentation]](https://thomashunter.name/pos...

文中所提到的参考内容及使用到的PPT资料(有惊喜)

JS事件循环PPT - Whidy

JavaScript Event Loop - Thomas Hunter

JS Event Loop - Sonle

All you need to know about the JavaScript event loop - @sasatatar & @codaxy

全部整包下载

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99017.html

相关文章

  • Node.js 指南(阻塞与非阻塞概述)

    摘要:标准库中的所有方法都提供非阻塞的异步版本,并接受回调函数,某些方法还具有对应的阻塞方法,其名称以结尾。比较代码阻塞方法同步执行,非阻塞方法异步执行。 阻塞与非阻塞概述 此概述介绍了Node.js中阻塞与非阻塞调用之间的区别,此概述将引用事件循环和libuv,但不需要事先了解这些主题,假设读者对JavaScript语言和Node.js回调模式有基本的了解。 I/O主要指与libuv支持的...

    zebrayoung 评论0 收藏0
  • 个人分享--web前端学习资源分享

    摘要:前言月份开始出没社区,现在差不多月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了一般来说,差不多到了转正的时候,会进行总结或者分享会议那么今天我就把看过的一些学习资源主要是博客,博文推荐分享给大家。 1.前言 6月份开始出没社区,现在差不多9月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了!一般来说,差不多到了转正的时候,会进行总结或者分享会议!那么今天我就...

    sherlock221 评论0 收藏0
  • 【连载】前端个人文章整理-从基础入门

    摘要:个人前端文章整理从最开始萌生写文章的想法,到着手开始写,再到现在已经一年的时间了,由于工作比较忙,更新缓慢,后面还是会继更新,现将已经写好的文章整理一个目录,方便更多的小伙伴去学习。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 个人前端文章整理 从最开始萌生写文章的想法,到着手...

    madthumb 评论0 收藏0

发表评论

0条评论

codeGoogle

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<