博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Vue.nextTick 与 eventLoop
阅读量:7041 次
发布时间:2019-06-28

本文共 5353 字,大约阅读时间需要 17 分钟。

理论层面

  • nextTick函数是传入一个回调函数callback,使得回调函数中的内容,会放到异步任务队列中执行。
  • vue源码使用promisesetTimeout等方法在microtask中创建异步事件,目的是在当前调用栈执行完毕以后,才回去执行这个事件(放到异步队列里)。

关于 macro taskmicro task 的概念,简单用代码表示,不细讲,有兴趣的去看另一篇文章...

for (macroTask of macroTaskQueue) {    // 1. Handle current MACRO-TASK    handleMacroTask();          // 2. Handle all MICRO-TASK    for (microTask of microTaskQueue) {        handleMicroTask(microTask);    }}复制代码

源码层面

这是vue源码包中 用于实现 nextTick 的一个文件,想去看尤大神源码,以下是一些理解性注释

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]()  }}let microTimerFunc; // 对应微任务的回调函数let macroTimerFunc;  //对应宏任务的回调函数let useMacroTask = false;  // 是否使用宏任务/** * 这一段是对于 macro task 的实现 * 优先检测是否支持原生 setImmediate,这是一个高版本 IE 和 Edge 才支持的特性, *不支持的话再去检测是否支持原生的 MessageChannel * 如果也不支持的话就会降级为 setTimeout 0 * 注意:在宏任务分类的优先级中,也是  setImmeiate > MessageChannel  > setTimeout */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 {  macroTimerFunc = () => {    setTimeout(flushCallbacks, 0)  }}/** * 这一段是对于 microtask 的实现 */if (typeof Promise !== 'undefined' && isNative(Promise)) {  // 创建一个状态为 resolve的 promise 异步任务  const p = Promise.resolve()  // 创建微任务回调函数  microTimerFunc = () => {    p.then(flushCallbacks)    // 对 iOS的兼容    if (isIOS) setTimeout(noop)  }} else {  // 要是 浏览器不支持 Promise 的话,只能用 宏任务 的方式去实现。  microTimerFunc = macroTimerFunc}/** * 封装一个方法,在数据状态发生了改变的时候, * 用于确保这些改变会在下一个宏任务中执行, * 而不是在本次宏任务的微任务阶段执行DOM的更新。 */export function withMacroTask (fn: Function): Function {  return fn._withTask || (fn._withTask = function () {    useMacroTask = true;    const res = fn.apply(null, arguments);    useMacroTask = false;    return res  })}/* * 对外暴露的 nextTick主函数*/export function nextTick (cb?: Function, ctx?: Object) {  let _resolve  // 收集传入的 cb回调事件到 全局 callback 回调数组。  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()    }  }  // 当调用 $nextTick 方法的时候,如没有传入 cb 回调,则创建一个空回调的异步微任务  if (!cb && typeof Promise !== 'undefined') {    return new Promise(resolve => {      _resolve = resolve    })  }}复制代码

代码中涉及的 MessageChannelsetImmediate稍后再细作了解


应用层面

内部应用 withMacroTask

对外的暴露的 widthMacroTask 方法并没有暴露给用户使用,这个方法在vue内部事件机制platforms\web\runtime\modules\event.js 管理器中有应用。 比如对于一些 DOM 交互事件,如 v-on 绑定的事件回调函数的处理,会强制走 macro task

import { withMacroTask, isIE, supportsPassive } from 'core/util/index'// 节选部分function add (  event: string,  handler: Function,  once: boolean,  capture: boolean,  passive: boolean) {  handler = withMacroTask(handler)  if (once) handler = createOnceHandler(handler, event, capture)  target.addEventListener(    event,    handler,    supportsPassive      ? { capture, passive }      : capture  )}复制代码

vue DOM 更新机制

请去看另一篇笔记

开发者应用场景

官方api

Vue.nextTick( [callback, context] )参数:

  • {Function} [callback]
  • {Object} [context]

这就是我们平时在开发的过程中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行

Api.getData(data).then(()=>{    this.treeNode = data.treeNode;     this.$nextTick(()=>{        this.currentNode = this.    })})复制代码

小实验①

根据上面的知识点去想想,DOM被更新了几次?

复制代码
export default {  name: 'app',  data() {    return {      msg: 'Welcome to Your Vue.js App',      val: 0,      count: 0,    }  },  mounted() {    this.val = 333;    this.$nextTick(_ => {      this.val = 444;    })    this.$nextTick(_ => {      this.val = 555;    })    setTimeout(() => {      for (let i = 2000; i < 3000; i++) {        this.val = i;      }    }, 3000)  },  watch: {    val(newVal, oldVal) {      this.count += 1;      console.log("val  is changed")    }  }}复制代码
结果过程


小实验②

若把上一个实验的script部分改为如下内容,猜猜结果是如何呢?

export default {  name: 'app',  data() {    return {      msg: 'Welcome to Your Vue.js App',      val: 0,      count: 0,    }  },  beforeMount() {    console.log(111)    this.val = 111;    this.$nextTick(_ => {      console.log(222)      this.val = 222;    })  },  mounted() {    console.log(333)    this.val = 333;    this.$nextTick(_ => {      console.log(444)      this.val = 444;    })    this.$nextTick(_ => {      console.log(555)      this.val = 555;    })    setTimeout(() => {      for (let i = 2000; i < 3000; i++) {        this.val = i;      }    }, 3000)  },  watch: {    val(newVal, oldVal) {      this.count += 1;      console.log("val  is changed")    }  }}复制代码
输出结果

说明同步修改不分生命周期,都会在第一个eventLoop中进行,而$nextTick也不区分生命周期,都会在本轮执行栈的微任务阶段完成修改。

文章有误还请各位多多指出,蟹蟹


参考文章

转载地址:http://taxal.baihongyu.com/

你可能感兴趣的文章
流程控制-for序列、流程控制-for字典
查看>>
Go语言之反射
查看>>
dTree JS 基本用法
查看>>
Android Things创客DIY第一课-用Android Things展示你的智能设备创意-基础篇
查看>>
[Lab1]-EIGRP试验
查看>>
bash的算术运算和条件测试语句基础
查看>>
uwsgi+django+nginx
查看>>
安装MASM32
查看>>
***如何优雅的选择字体(font-family)
查看>>
11.python并发入门(part12 初识协程)
查看>>
华为NE40 V800 XPL功能初体验
查看>>
thinkphp3.1随机取数据库中几条记录
查看>>
设计一个Shell程序,在/userdata目录下建立50个目录,即user1~user50,
查看>>
ORA-01652 even though there is sufficient space in RECYCLE BIN
查看>>
Could not use /usr/local/apache/logs/slowquery.log for logging (error 13).
查看>>
mogilefs-企业级分布式存储应用与实战
查看>>
nginx改tengine,gitlab重装操作步骤
查看>>
spring中的相互引用问题
查看>>
sql server2005 jdbc解决自动增长列统一处理问题
查看>>
GLUT and OpenGL Utility Libraries
查看>>