Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue nextTick的实现 #27

Open
Jmingzi opened this issue Apr 2, 2018 · 0 comments
Open

Vue nextTick的实现 #27

Jmingzi opened this issue Apr 2, 2018 · 0 comments

Comments

@Jmingzi
Copy link
Owner

Jmingzi commented Apr 2, 2018

nextTick的实现本质是采用了js Event Loop的知识实现。

其使用场景在赋值state后使用,目的是等待watcher触发更新界面后调用回调。本文旨在理解Event Loop的基础上查看vue nextTick的实现,以便于在使用过程中更好的解决问题。

nextTick 伪代码

function nextTick(cb, context) {
  // 处理cb
  callbacks.push(() => {
     if (cb) {
       // 回调模式
       cb.call(context)
     } else {
       // 不传cb时,为promise形式
       _resolve()
     }
  })

  // 保证只有一次调用
  if (!pending) {
    pending = true
    // 当前事件队列结束后, 触发回调数组
    // 此处涉及事件队列知识,请戳https://github.com/Jmingzi/blog/issues/2
    // 至于为何要用microtasks和tasks,注释说明
    // 在vue2.4以下都只用microtasks,但是后来发现在连续的事件或同一事件的冒泡中,会有问题
    // 因为microtasks的优先级始终是最高的。默认使用microtasks
    // 何时用tasks?
    // 当组件内部的变化导致state变化时,就会使用tasks
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }

  // 如果没传cb,则返回一个promise对象
  if (!cb) {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

然后再就是定义macroTimerFunc和microTimerFunc时所涉及的点

  • 定义macroTimerFunc
    在ie中支持setImmediate,非ie中,都是使用MessageChanel发送消息来保持callback始终以队列的形式调用的。除非二者不支持,最后才用的setTimeout保底。
if (setImmediate) {
  setImmediate(flushCallbacks)
} else if (MessageChanel) {
  // 关于MessageChanel,请戳
  // http://www.zhangxinxu.com/study/201202/web-messing-channel-messaging-two-iframe.html
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  setTimeout(() => {
    flushCallbacks()
  }, 0)
}
  • 定义microTimerFunc
    用Promise模拟的,但是在iOS UIWebViews中有个bug,Promise.then并不会被触发,除非浏览器中有其他事件触发,例如处理setTimeout。所以手动加了个空的setTimeout

如果Promise都不支持,那microTimerFunc = macroTimerFunc

  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)
  }

疑问

  • 关于vue实例中使用microTask与task的区别(也就是注释中的问题描述)
  • 为何要使用MessageChanel来模拟task,而不是直接使用setTimeout
    w3c html规范中定义了setTimeout的默认最小时间为4ms,而嵌套的timeout表现为10ms,也就是说即使你赋值0,而实际却不是。再由于,setTimeout的时间,会受到任务队列的影响(或其它原因?)其实际时间远大于10ms,这或许就是vue不优先使用setTimeout的原因吧。
    此处参考
  • 何时用task,何时用microTask
    vue内置了一个函数withMacroTask,当组件的state变化时,就会使用task,其它默认都是microTask

推荐阅读

Tasks, microtasks, queues and schedules

最后,注释中的2个问题#4521, #6690,欢迎一起讨论。

@Jmingzi Jmingzi changed the title nextTick的实现 Vue nextTick的实现 Apr 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant