JS异步与性能

简介: js的event机制

一、背景

看了《你不知道的javascript》上卷以及中卷之后,总结一下js的event机制。

二、事件循环

JavaScript 引擎并不是独立运行的,它运行在宿主环境中,对多数开发者来说通常就是Web 浏览器。处理程序中多个块的执行,且执行每块时调用JavaScript 引擎,这种机制被称为事件循环。

先通过一段伪代码了解一下这个概念:


// eventLoop是一个用作队列的数组
// (先进,先出)
var eventLoop = [ ];
var event;
// “永远”执行
while (true) {
    // 一次tick
    if (eventLoop.length > 0) {
        // 拿到队列中的下一个事件
        event = eventLoop.shift();
        // 现在,执行下一个事件
        try {
            event();
        }
        catch (err) {
            reportError(err);
        }
    }
}

这里可以看到,有一个用while 循环实现的持续运行的循环,循环的每一轮称为一个tick。对每个tick 而言,如果在队列中有等待事件,那么就会从队列中摘下一个事件并执行。这些事件称之为回调函数。

一定要清楚,setTimeout(..) 并没有把你的回调函数挂在事件循环队列中。它所做的是设定一个定时器。当定时器到时后,宿主环境会把你的回调函数放在事件循环中,这样,在未来 某个时刻的tick 会摘下并执行这个回调。

如果这时候事件循环中已经有20 个项目了会怎样呢?你的回调就会等待。它得排在 其他项目后面——通常没有抢占式的方式支持直接将其排到队首。这也解释了为什么 setTimeout(..) 定时器的精度可能不高。大体说来,只能确保你的回调函数不会在指定的 时间间隔之前运行,但可能会在那个时刻运行,也可能在那之后运行,要根据事件队列的 状态而定。

回调



listen("click", function handler(evt){
    setTimeout( function request(){
        ajax( "http://some.url.1", function response(text){
            if (text == "hello") {
                handler();
            }
            else if (text == "world") {
                request();
            }
        } );
    }, 500) ;
} );

这里我们得到了三个函数嵌套在一起构成的链,其中每个函数代表异步序列(任务,“进程”)中的一个步骤。这种代码常常被称为回调地狱(callback hell),有时也被称为毁灭金字塔(pyramid of doom,得名于嵌套缩进产生的横向三角形状)。

  • 线性跟踪

doA( function(){
    doB();
    doC( function(){
        doD();
    } )
    doE();
} );
doF();

执行顺序是?

A、F、B、C、E、D

在线性(顺序)地追踪这段代码的过程中,我们不得不从一个函数跳到下一个,再跳到下一个,在整个代码中跳来跳去以“查看”流程。而且别忘了,这还是简化的形式,只考虑了最优情况。我们都知道,真实的异步JavaScript程序代码要混乱得多,这使得这种追踪的难度会成倍增加。

我们的顺序阻塞式的大脑计划行为无法很好地映射到面向回调的异步代码。这就是回调方式最主要的缺陷:对于它们在代码中表达异步的方式,我们的大脑需要努力才能同步得上。

  • 信任问题
// A
ajax( "..", function(..){
    // C
} );
// B

这是回调驱动设计最严重(也是最微妙)的问题。它以这样一个思路为中心:有时候ajax(..)(也就是你交付回调continuation 的第三方)不是你编写的代码,也不在你的直接控制下。多数情况下,它是某个第三方提供的工具。

  • 回调设计

(1)为了更优雅地处理错误,有些API 设计提供了分离回调(一个用于成功通知,一个用于出错通知):


function success(data) {}
function error(data) {}
ajax('...', success(data), error(data));

(2)还有一种常见的回调模式叫作“error-first 风格”(有时候也称为“Node 风格”,因为几乎所有Node.js API 都采用这种风格),其中回调的第一个参数保留用作错误对象(如果有的话)。如果成功的话,这个参数就会被清空/ 置假(后续的参数就是成功数据)。不过,如果产生了错误结果,那么第一个参数就会被置起/ 置真(通常就不会再传递其他结果):

function respon(err, data) {
  if(err){
  // error
 }
  else {
  // success
 }
}
ajax('...', respon);

setTimeout

教科书里面的setTimeout 定义很简单
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。广泛应用场景:定时器,轮播图,动画效果,自动滚动等等。但是setTimeout真的有那么简单吗?

for(var i=1; i<=5; i++) {
 setTimeout(function timer() {
   console.log(i);
 }, i*1000);
}

结果:以一秒的频率连续输出五个6

解答

  • 1、作用域
    这里我引用《你不知道的javascript》中的一个比喻,可以把作用域链想象成一座高楼,第一层代表当前执行作用域,楼的顶层代表全局作用域。我们在查找变量时会先在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上找,以此类推。到达顶层后(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。
  • 2、任务队列
    事件循环只有一个,但任务队列可能有多个,任务队列可分为宏任务(macro-task)和微任务(micro-task)。XHR回调、事件回调(鼠标键盘事件)、setImmediate、setTimeout、setInterval、indexedDB数据库操作等I/O以及UI rendering都属于宏任务(也有文章说UI render不属于宏任务,目前还没有定论),process.nextTick、Promise.then、Object.observer(已经被废弃)、MutationObserver(html5新特性)属于微任务。注意进入到任务队列的是具体的执行任务的函数。比如上述例子setTimeout()中的timer函数。另外不同类型的任务会分别进入到他们所属类型的任务队列,比如所有setTimeout()的回调都会进入到setTimeout任务队列,所有then()回调都会进入到then队列。当前的整体代码我们可以认为是宏任务。事件循环从当前整体代码开始第一次事件循环,然后再执行队列中所有的微任务,当微任务执行完毕之后,事件循环再找到其中一个宏任务队列并执行其中的所有任务,然后再找到一个微任务队列并执行里面的所有任务,就这样一直循环下去。

参考资料

《你不知道的javascript》

目录
相关文章
|
4月前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指那些不会立即执行完毕,而是会在未来的某个时间点(比如某个操作完成后,或者某个事件触发后)才完成其执行的函数
【6月更文挑战第15天】JavaScript中的异步函数用于处理非同步任务,如网络请求或定时操作。它们使用回调、Promise或async/await。
44 7
|
4月前
|
JavaScript 前端开发
事件委托是JS技巧,通过绑定事件到父元素利用事件冒泡,减少事件处理器数量,提高性能和节省内存。
【6月更文挑战第27天】事件委托是JS技巧,通过绑定事件到父元素利用事件冒泡,减少事件处理器数量,提高性能和节省内存。例如,动态列表可共享一个`click`事件处理器,通过`event.target`识别触发事件的子元素,简化管理和响应动态内容变化。
39 0
|
23天前
|
缓存 JavaScript 中间件
优化Express.js应用程序性能:缓存策略、请求压缩和路由匹配
在开发Express.js应用时,采用合理的缓存策略、请求压缩及优化路由匹配可大幅提升性能。本文介绍如何利用`express.static`实现缓存、`compression`中间件压缩响应数据,并通过精确匹配、模块化路由及参数化路由提高路由处理效率,从而打造高效应用。
64 7
|
24天前
|
JavaScript 前端开发
一个js里可以有多少个async function,如何用最少的async function实现多个异步操作
在 JavaScript 中,可以通过多种方法实现多个异步操作并减少 `async` 函数的数量。
|
25天前
|
JSON 前端开发 JavaScript
一文看懂 JavaScript 异步相关知识
一文看懂 JavaScript 异步相关知识
|
2月前
|
缓存 前端开发 JavaScript
超时空加速秘籍:揭秘JavaScript前端开发中的性能魔法,让您的Web应用瞬间穿越到未来!
【8月更文挑战第27天】本文介绍了一系列实用的JavaScript性能优化方法并提供了示例代码,包括减少DOM操作、使用事件委托、避免阻塞主线程、异步加载资源、利用浏览器缓存、代码分割以及使用Service Worker等技术,帮助开发者有效提升Web应用性能和用户体验。
40 2
|
2月前
|
存储 JavaScript API
Node.js中的异步API
【8月更文挑战第16天】
29 1
|
2月前
|
JavaScript 前端开发
深入理解Node.js事件循环及其对后端性能的影响
【8月更文挑战第31天】 本文将带你一探Node.js的核心概念—事件循环,揭示其工作原理及如何影响后端应用的性能。我们将从基础的事件驱动模型出发,通过代码示例和性能分析,展示如何有效利用事件循环来提升应用响应速度和处理能力。
|
3月前
|
数据采集 JavaScript Python
【JS逆向课件:第十三课:异步爬虫】
回调函数就是回头调用的函数
|
2月前
|
SQL JavaScript 前端开发
【Azure 应用服务】Azure JS Function 异步方法中执行SQL查询后,Callback函数中日志无法输出问题
【Azure 应用服务】Azure JS Function 异步方法中执行SQL查询后,Callback函数中日志无法输出问题
下一篇
无影云桌面