从JS事件循环(Event Loop)机制到vue.nextTick的实现

众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言。

  1. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选择一个主线程来执行代码,以防止冲突。虽然如今添加了webworker等新技术,但其依然只是主线程的子线程,并不能执行诸如I/O类的操作。长期来看,JS将一直是单线程。
  2. 为何非阻塞?因为单线程意味着任务需要排队,任务按顺序执行,如果一个任务很耗时,下一个任务不得不等待。所以为了避免这种阻塞,我们需要一种非阻塞机制。这种非阻塞机制是一种异步机制,即需要等待的任务不会阻塞主执行栈中同步任务的执行。这种机制是如下运行的:
  • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack) 
  • 等待任务的回调结果进入一种任务队列(task queue)。
  • 当主执行栈中的同步任务执行完毕后才会读取任务队列,任务队列中的异步任务(即之前等待任务的回调结果)会塞入主执行栈,
  • 异步任务执行完毕后会再次进入下一个循环。此即为今天文章的主角事件循环(Event Loop) 

用一张图展示这个过程:

1.macro task与micro task

在实际情况中,上述的任务队列(task queue)中的异步任务分为两种:微任务(micro task)和宏任务(macro task)。

  • micro task事件:Promises(浏览器实现的原生Promise)、MutationObserver、process.nextTick 
    <br />
  • macro task事件:setTimeout、setInterval、setImmediate、I/O、UI rendering
    这里注意:script(整体代码)即一开始在主执行栈中的同步代码本质上也属于macrotask,属于第一个执行的task 

microtask和macotask执行规则:

    • macrotask按顺序执行,浏览器的ui绘制会插在每个macrotask之间
    • microtask按顺序执行,会在如下情况下执行:
      • 每个callback之后,只要没有其他的JS在主执行栈中
      • 每个macrotask结束时

下面来个简单例子:

console.log(1);

 

setTimeout(function() {

  console.log(2);

}, 0);

new Promise(function(resolve,reject){

    console.log(3)

    resolve()

}).then(function() {

  console.log(4);

}).then(function() {

  console.log(5);

});

console.log(6);


一步一步分析如下:

 

1.同步代码作为第一个macrotask,按顺序输出:1 3 6

2.microtask按顺序执行:4 5

 

3.microtask清空后执行下一个macrotask:2

再来一个复杂的例子:

// Let's get hold of those elements

var outer = document.querySelector('.outer');

var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the

// outer element

new MutationObserver(function() {

  console.log('mutate');

}).observe(outer, {

  attributes: true

});

// Here's a click listener…

function onClick() {

  console.log('click');

 

  setTimeout(function() {

    console.log('timeout');

  }, 0);

 

  Promise.resolve().then(function() {

    console.log('promise');

  });

 

  outer.setAttribute('data-random', Math.random());

}

// …which we'll attach to both elements

inner.addEventListener('click', onClick);

outer.addEventListener('click', onClick);

假设我们创建一个有里外两部分的正方形盒子,里外都绑定了点击事件,此时点击内部,代码会如何执行?一步一步分析如下:

  • 1.触发内部click事件,同步输出:click
  • 2.将setTimeout回调结果放入macrotask队列
  • 3.将promise回调结果放入microtask
  • 4.将Mutation observers放入microtask队列,主执行栈中onclick事件结束,主执行栈清空
  • 5.依序执行microtask队列中任务,输出:promise mutate
  • 6.注意此时事件冒泡,外部元素再次触发onclick回调,所以按照前5步再次输出:click promise mutate(我们可以注意到事件冒泡甚至会在microtask中的任务执行之后,microtask优先级非常高) 
  • 7.macrotask中第一个任务执行完毕,依次执行macrotask中剩下的任务输出:timeout timeout

 

2.vue.nextTick实现

 

在 Vue.js 里是数据驱动视图变化,由于 JS 执行是单线程的,在一个 tick 的过程中,它可能会多次修改数据,但 Vue.js 并不会傻到每修改一次数据就去驱动一次视图变化,它会把这些数据的修改全部 push 到一个队列里,然后内部调用 一次 nextTick 去更新视图,所以数据到 DOM 视图的变化是需要在下一个 tick 才能完成。这便是我们为什么需要vue.nextTick.

 

这样一个功能和事件循环非常相似,在每个 task 运行完以后,UI 都会重渲染,那么很容易想到在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

 

所以在vue 2.4之前使用microtask实现nextTick,直接上源码

 

var counter = 1var observer = new MutationObserver(nextTickHandler)var textNode = document.createTextNode(String(counter))

 

observer.observe(textNode, {

 

    characterData: true

 

})

 

timerFunc = () => {

 

    counter = (counter + 1) % 2

 

    textNode.data = String(counter)

 

}

可以看到使用了MutationObserver

然而到了vue 2.4之后却混合�使用microtask macrotask来实现,源码如下

/* @flow *//* globals MessageChannel */

import { noop } from 'shared/util'import { handleError } from './error'import { isIOS, isNative } from './env'

const callbacks = []let pending = false

function flushCallbacks () {

  pending = false

  const copies = callbacks.slice(0)

  callbacks.length = 0

  for (let i = 0; i < copies.length; i++) {

    copies[i]()

  }

}

// Here we have async deferring wrappers using both micro and macro tasks.// In < 2.4 we used micro tasks everywhere, but there are some scenarios where// micro tasks have too high a priority and fires in between supposedly// sequential events (e.g. #4521, #6690) or even between bubbling of the same// event (#6566). However, using macro tasks everywhere also has subtle problems// when state is changed right before repaint (e.g. #6813, out-in transitions).// Here we use micro task by default, but expose a way to force macro task when// needed (e.g. in event handlers attached by v-on).let microTimerFunclet macroTimerFunclet useMacroTask = false

// Determine (macro) Task defer implementation.// Technically setImmediate should be the ideal choice, but it's only available// in IE. The only polyfill that consistently queues the callback after all DOM// events triggered in the same loop is by using MessageChannel./* istanbul ignore if */if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {

  macroTimerFunc = () => {

    setImmediate(flushCallbacks)

  }

} else if (typeof MessageChannel !== 'undefined' && (

  isNative(MessageChannel) ||

  // PhantomJS

  MessageChannel.toString() === '[object MessageChannelConstructor]'

)) {

  const channel = new MessageChannel()

  const port = channel.port2

  channel.port1.onmessage = flushCallbacks

  macroTimerFunc = () => {

    port.postMessage(1)

  }

} else {

  /* istanbul ignore next */

  macroTimerFunc = () => {

    setTimeout(flushCallbacks, 0)

  }

}

// Determine MicroTask defer implementation./* istanbul ignore next, $flow-disable-line */if (typeof Promise !== 'undefined' && isNative(Promise)) {

  const p = Promise.resolve()

  microTimerFunc = () => {

    p.then(flushCallbacks)

    // in problematic UIWebViews, Promise.then doesn't completely break, but

    // it can get stuck in a weird state where callbacks are pushed into the

    // microtask queue but the queue isn't being flushed, until the browser

    // needs to do some other work, e.g. handle a timer. Therefore we can

    // "force" the microtask queue to be flushed by adding an empty timer.

    if (isIOS) setTimeout(noop)

  }

} else {

  // fallback to macro

  microTimerFunc = macroTimerFunc

}

/**

 * Wrap a function so that if any code inside triggers state change,

 * the changes are queued using a Task instead of a MicroTask.

 */export function withMacroTask (fn: Function): Function {

  return fn._withTask || (fn._withTask = function () {

    useMacroTask = true

    const res = fn.apply(null, arguments)

    useMacroTask = false

    return res

  })

}

export function nextTick (cb?: Function, ctx?: Object) {

  let _resolve

  callbacks.push(() => {

    if (cb) {

      try {

        cb.call(ctx)

      } catch (e) {

        handleError(e, ctx, 'nextTick')

      }

    } else if (_resolve) {

      _resolve(ctx)

    }

  })

  if (!pending) {

    pending = true

    if (useMacroTask) {

      macroTimerFunc()

    } else {

      microTimerFunc()

    }

  }

  // $flow-disable-line

  if (!cb && typeof Promise !== 'undefined') {

    return new Promise(resolve => {

      _resolve = resolve

    })

  }

}

可以看到使用setImmediate、MessageChannel等mascrotask事件来实现nextTick。

为什么会如此修改,其实看之前的事件冒泡例子就可以知道,由于microtask优先级太高,甚至会比冒泡快,所以会造成一些诡异的bug。如 issue #4521、#6690、#6556;但是如果全部都改成 macro task,对一些有重绘和动画的场景也会有性能影响,如 issue #6813。所以最终 nextTick 采取的策略是默认走 micro task,对于一些 DOM 交互事件,如 v-on 绑定的事件回调函数的处理,会强制走 macro task。

 

 

转载于:https://www.cnblogs.com/Regina-wisdom/p/10635300.html

Vue学习记录之十九 Event Loop,宏任务和微任务 当一个宏任务执行完毕后,会检查是否有微任务队列,如果有,则将微任务队列中的所有任务依次执行完毕,然后再继续选择下一个宏任务执行。这样的过程循环进行,形成了事件循环。微任务通常在宏任务执行完毕后立即执行,因此微任务具有更高的优先级,可以在页面渲染前执行,从而提供更好的用户体验。微任务的优先级高于宏任务,因此微任务会在本轮宏任务执行结束后立即执行,而不是等到下一个事件循环。按顺序执行, 同步任务直接执行,异步任务进入异步队列,执行完以后,在进行入异步列队,然后在看是同步还是异步任务,如此循环执行。 阅读详情

相关推荐

JavaScript 事件循环 (Event Loop) 机制详解

JavaScript采用单线程模型,通过事件循环(Event Loop)机制协调同步和异步任务执行。核心机制是将异步任务分为宏任务(如setTimeout)和微任务(如Promise.then),执行顺序遵循"同步代码>微任务>DOM渲染>宏任务"的优先级规则。典型执行流程:先执行同步代码,然后清空微任务队列,进行DOM渲染,最后执行一个宏任务并循环该过程。通过案例分析展示了async/await、Promise等异步操作的执行时序,并指出Promise构造函数是同步执

前端程序猿的博客 342

vue.js——事件循环机制

一、事件循环机制介绍 JS是单线程的语言,浏览器和Node.js定义了各自的Event Loop(事件循环机制)则是用来解决异步问题。将程序分为“主线程(执行栈)”与“Event Loop线程”,“主线程”自上而下依次执行同步任务,“Event Loop线程”将异步任务推入宏任务队列与微任务队列去执行。 事件循环机制从整体上告诉了我们 JavaScript 代码的执行顺序 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript...

涵盖了计算机专业基础知识、数学建模相关实践、复杂网络论文研究、LeetCode算法刷题经验、C语言开发经验、前端Vue、React框架开发实战相关知识 3032

解读vue源码之nextTick:何谓 event-loop, 浏览器的事件机制,task/microtasks

前言: 目录: nextTick 与异步渲染UI 浏览器的event-loop nextTick 与异步渲染UI 浏览器的event-loop

凝视深渊 832

Vue.nextTick 的原理和用途

转载自https://segmentfault.com/a/1190000012861862 概览 官方文档说明: 用法: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 疑问: DOM 更新循环是指什么? 下次更新循环是什么时候? 修改数据之后使用,是加快了数据更新进度吗? 在什么情况下要用到? ...

weixin_30469895的博客 311

tkinter动态表格 - 实时更新数据(TkinterTable)

tkinter动态表格 - 实时更新数据(TkinterTable) 参考 [TkinterTable] Tkinter上的表格GUI】 https://github.com/dmnfarrell/tkintertable/wiki/Usage https://github.com/dmnfarrell/tkintertable/issues/47 python使用tkinter编写一个数据显示窗口(实时显示更新的数据) 首先安装TkinterTable,即pip install tki

qq_33934427的博客 1万+

CentOS8安装NVIDIA显卡驱动、CUDA和Anaconda

CentOS8安装NVIDIA显卡驱动和CUDA 参考:CentOS nvidia+cuda+cudnn 安装 安装英伟达显卡驱动 1、检查是否安装了NVIDIA的GPU(硬件层面): lspci | grep -i nvidia 2、安装GCC、kernal组件、dkms等 sudo yum install gcc sudo yum install gcc-c++ sudo yum -y install kernel-devel sudo yum -y install kernel-headers s

智慧的旋风的博客 1万+

Vue.js 的事件循环(Event Loop)机制

Vue.js 的事件循环(Event Loop)行为本质上遵循 JavaScript 的标准事件循环机制,这是因为 Vue 是构建在 JavaScript 之上的。不过,Vue 在其数据响应式系统中巧妙地利用了这一机制来实现高效的视图更新。

qq_41513230的博客 1272

别再纠结VS还是VSCode了!新手C#环境搭建保姆级指南(含.NET Core安装验证)

本文为C#新手提供Visual Studio与VS Code的深度对比及环境搭建指南,涵盖.NET Core安装验证、工具选择决策框架及实战配置技巧。帮助开发者根据项目需求选择合适工具,快速搭建高效开发环境,提升编码效率。

weixin_30455023的博客 560

【Vue 2】nextTick方法|异步更新|事件循环

前面提到的事件循环又是什么?我们知道,JavaScript是一门单线程且非阻塞的语言。单线程意味着一次只能执行一个任务,也叫做主线程。非阻塞意味着遇到异步任务(比如网络请求、文件读取、定时器等)时,JavaScript会将这些异步任务挂起,继续执行后面的代码。当异步任务处理完毕后,根据一定的规则(通常是回调函数或Promise)来处理操作的结果。挂起(pending)是指将异步任务放入一个队列里,称为事件队列。而异步任务可以分为微任务和宏任务。微任务放在微任务队列,宏任务放在宏任务队列。

Shadow Kylin的博客 1708

【顾邦小讲堂】第十九期 RC Snubber吸收电路详解:抑制MOSFET振铃现象与过冲的设计技巧!

通过RC Snubber电路有效抑制MOSFET DS端的过冲及振铃现象,帮助工程师们在设计中提升电路的稳定性和可靠性

GBupupup的博客 3782

信创改造实战:SpringBoot项目迁移到宝兰德BES 9.5.5的完整避坑指南(附依赖配置)

本文详细介绍了将SpringBoot项目迁移到宝兰德BES 9.5.5中间件的完整流程,包括环境准备、依赖调整、Web.xml版本适配、部署配置优化及常见问题解决方案。特别针对信创改造需求,提供了排除Tomcat依赖、处理BES专用starter等实用技巧,帮助开发者顺利完成国产中间件迁移。

weixin_30825581的博客 1095

EventLoop 宏任务与微任务

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。 单线程执行任务队列的问题: 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。 2. 同步任务和异步任务 为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类: ① 同步任务(synchronous) ⚫ 又叫做非耗时任务,指的是在主线程上排队执行的那些任务 ⚫ 只有前一个任务执行完毕,才能执行后一个任务 ② 异步任务(asy

m0_60507322的博客 354

零基础入门篇③ PyCharm使用指南(个性化设置、开发必备插件、常用快捷键)

Python从入门到精通👉零基础入门篇③

hacker707的博客 4371

技术文章 | JavaScript Event Loop机制详解与Vue.js中nextTick的实践应用

本文依次介绍了函数调用栈、MacroTask 与 MicroTask 执行顺序、浅析 Vue.js 中 nextTick 实现等内容;本文中引用的参考资料统一声明在 JavaScript 学习与实践资料索引。

CS13522431352的博客 436

【系统分析师】第1章-基础知识:绪论(核心总结)

《系统分析师教程(第二版)绪论》概述了系统分析师的核心角色与知识体系。作为信息系统开发的核心复合型人才,系统分析师需具备技术、业务、管理及道德等多维能力,贯穿需求分析、系统设计、协调沟通和质量保障全流程。知识体系涵盖系统工程方法论(如霍尔三维结构)、信息技术基础(数据库/网络技术)及管理学数学基础。教程梳理了该职业从数据处理专家到现代技术整合者的演变历程,并指出职业发展路径(初级分析师至CTO)。第二版新增大数据/AI等前沿内容,强化案例与实践,是软考官方权威教材,为读者构建系统化的学习框架。

数据知道的博客 6904

大模型的实践应用31-基于通义千问大模型改进台风和海浪预测研究,利用机器学习与通义千问结合的思路实现对台风和海浪预测分析

通义千问大模型是阿里巴巴云推出的一款大规模语言模型,旨在通过强大的自然语言处理能力提供更广泛的服务。从最初的构想到如今的广泛应用,通义千问的发展见证了人工智能领域尤其是自然语言处理技术的巨大进步。自发布以来,通义千问不断迭代升级,不仅在规模上实现了飞跃式的增长,在性能方面也得到了显著提升。本次研究尝试将通义千问强大的自然语言处理能力与经典的时间序列预测算法相结合,旨在提高对未来天气状况预测的精度。具体来说,首先使用通义千问生成高质量的文字描述来增强特征表达力;

微学AI的博客 934

Vue2.0源码阅读笔记(四):nextTick

  在阅读 nextTick 的源码之前,要先弄明白 JS 执行环境运行机制,介绍 JS 执行环境的事件循环机制的文章很多,大部分都阐述的比较笼统,甚至有些文章说的是错误的,以下为个人理解,如有错误,欢迎指正。 一、浏览器中的进程与线程   以 chorme 浏览器为例,浏览器中的每个页面都是一个独立的进程,在该进程中拥有多个线程,通常有以下几个常驻线程: 1、GUI 渲染线程 2、JavaSc...

weixin_30735391的博客 127

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

vue nextTick + 事件循环机制

eventLoop,事件循环机制,vue,nextTick,进程,线程

趟过java,遇见web 2420

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

事件循环(Event loop)

由于vue的更新机制是异步的,所以当数据修改之后,dom还停留在更新之前,此时想要获取更新后的dom,可以使用nextTick,表示的是下次dom更新循环结束后执行的回调。不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,就从任务队列中取出任务放进主线程中来进行执行。4、当主线程内的任务执行完毕(主线程为空时),会检查微任务的任务队列,如果有任务,就进入主线程全部执行,如果没有就从宏任务队列读取下一个宏任务执行;在主线程上排队执行的任务只有前一个任务执行完毕,才能执行后一个任务,形成一个执行栈。

2029

JS事件循环搞不懂?前端打工人必看的Event Loop通关秘籍

写到这,估计你也看累了。但你发现没有,Event Loop这玩意儿,面试时背再多的"先宏后微"、“Node六个阶段”,都不如在实际项目中踩过一次坑来得深刻。我印象最深的一次,是早年负责的一个可视化项目,有大量的WebGL渲染和数据更新。当时没理解透rAF和setTimeout的区别,用setTimeout做动画循环,结果用户电脑卡顿时,setTimeout还在那傻乎乎地执行,积了一堆回调,等CPU缓过来瞬间爆炸,页面卡死。

master_chenchen的博客 946

JS 事件循环(Event Loop)

JavaScript事件循环机制解析:掌握异步执行顺序的关键 摘要:本文深入解析JavaScript的事件循环机制,揭示其作为单线程语言处理异步任务的核心原理。通过典型代码示例演示了同步任务、微任务和宏任务的执行顺序,详细讲解了事件循环的组件构成和执行流程。文章对比了浏览器与Node.js环境的差异,列举了常见误区,并提供了DOM操作、Vue.nextTick等实战应用场景。掌握事件循环的"同步→微任务→宏任务"执行规则,能有效避免异步顺序bug,深入理解框架异步API的底层实现,是进阶

qq_40617614的博客 831

JS事件循环event loop:浏览器与Node环境以及vue.nexttick

秋招面试被问到这个问题,查了很多文章,比较零散,所以自己集百家之长,做个整理 什么是事件? 事件:事件就是由于某种外在或内在的信息状态发生的变化,从而导致出现了对应的反应。比如说用户点击了一个按钮,就是一个事件;HTML页面完成加载,也是一个事件。一个事件中会包含多个任务。 JavaScript引擎又称为JavaScript解释器,是JavaScript解释为机器码的工具,分别运行在浏览器和Node中。而根据上下文的不同,Event loop也有不同的实现:其中Node使用了libuv库来实现Event l

李格格 372

从 JS Event Loop 机制看 Vue 中 nextTick 的实现原理

本文来自作者 大师兄 在 GitChat 上分享「从 JS Event Loop 机制看 Vue 中 nextTick 的实现原理」,「阅读原文」查看交流实录 「文末高能」 编辑 | 泰龙 作为一名前端,一直以来以精通 Javascript 为目标。其实说实话精通真的挺难,不是你记住全部的 API 就算是精通。 JavaScript 的知识非常零散而庞杂,很多情况下上周

技术杂谈 2915
上一篇: LeetCode每日一题:链表的中间结点(No.876)
下一篇: 基于 OpenResty 的 API 网关 Orange 正式发布 0.7 版本
weixin_34026484
博客等级 码龄11年 4642粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值