资讯专栏INFORMATION COLUMN

VueJS源码学习——MutationObserver实现nextTick

xumenger / 2660人阅读

摘要:倡导开发者尽量不直接操作,但有的时候由于各种需求让开发者不得不这样做,于是的实现就是让开发者在修改数据后,能够在数据更新到后才执行对应的函数,从而获取最新的数据。

Vue 倡导开发者尽量不直接操作 DOM,但有的时候由于各种需求让开发者不得不这样做,于是 nextTick 的实现就是让开发者在修改数据后,能够在数据更新到 DOM 后才执行对应的函数,从而获取最新的 DON 数据。

原文地址
项目地址

那么如何实现 nextTick呢,我们首先可以想到的是利用 setTimeout 的异步回调来实现,不过由于各个浏览器的不同,setTimeout 的延迟很高,因此在 nextTick 中只作为最后的备胎,首选的方案则是 MutationObserver(在后面的内容中 MO 代表 MutationObserver)

nextTick 的源码实现
export const nextTick = (function () {
  var callbacks = []
  var pending = false
  var timerFunc
  function nextTickHandler () {
    pending = false
    var copies = callbacks.slice(0)
    callbacks = []
    for (var i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }
  /* istanbul ignore if */
  if (typeof MutationObserver !== "undefined") { // 首选 MutationObserver 
    var counter = 1
    var observer = new MutationObserver(nextTickHandler) // 声明 MO 和回调函数
    var textNode = document.createTextNode(counter)
    observer.observe(textNode, { // 监听 textNode 这个文本节点
      characterData: true // 一旦文本改变则触发回调函数 nextTickHandler
    })
    timerFunc = function () {
      counter = (counter + 1) % 2 // 每次执行 timeFunc 都会让文本在 1 和 0 间切换
      textNode.data = counter
    }
  } else {
    timerFunc = setTimeout // 如果不支持 MutationObserver, 退选 setTimeout
  }
  return function (cb, ctx) {
    var func = ctx
      ? function () { cb.call(ctx) }
      : cb
    callbacks.push(func)
    if (pending) return
    pending = true
    timerFunc(nextTickHandler, 0)
  }
})()
MutationObserver 的功能和作用

MO 给开发者提供了一种能在某个范围内的DOM数发生变化时作出适当反应的能力 ——MDN

用人话说是开发者能通过它创建一个观察者对象,这个对象会监听某个DOM元素,并在它的DOM树发生变化时执行我们提供的回调函数。

具体参考这个 DEMO

比较特别的是实例化的时候需要先传入回调函数:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  })
})

然后才配置观察选项,包括观察节点和观察的属性:

// 选择目标节点
var target = document.querySelector("#some-id");
 
// 配置观察选项:
var config = { attributes: true, childList: true, characterData: true }
 
// 传入目标节点和观察选项
observer.observe(target, config);
 
// 随后,你还可以停止观察
observer.disconnect();

对于老版本的谷歌和火狐,则需要使用带前缀的 MO:

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
MutationObserver 和 microtask

那么为什么优选使用 MutationObserver呢?

一开始以为是 MO 就是用来监听 DOM 变化的,那么使用 textnode 模拟 DOM 变化再利用 MO 来监听触发从而实现 nextTick 不就很适合,直到了解看到了知乎上的问答才知道是因为 MO 会比 setTimeout 早执行的缘故,

这里需要了解JS的运行运行机制(重新刷新了我的三观), JS 的事件运行机制执行的时候会区分 taskmicrotask, 引擎在每个 task 执行完毕,并在从队列里取下一个task来执行之前, 执行完所有的 microtask 队列中的 microtask


(task 和 microtask 摘自 https://jakearchibald.com/)

setTimeout 回调会被分配到一个新的task中等待执行,而 Promise 的 resolver、MO 的 回调都会被分配到 microtask 的队列中,所以会比 setTimout 先执行

除了比 setTimout 快之外,还有 渲染性能 的问题,根据HTML Standard, 每个 task 运行完以后, UI 都会重新渲染,那么在 microtask 中就完成数据更新, 当前 task 结束就可以得到最新的 UI, 反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

所以性价比如此高的 MO 自然成为了首选

关于 microtask,具体可以阅读 Jake 写的 Tasks, microtasks, queues and schedules

nextTick 的版本迭代

上面关于 nextTick 的源码实现属于 vue 最早的版本 v1.0.9,在深挖 mutationObserver 的时候发现 nextTick 在vue的版本迭代中也在不断的进化,同事也发生过退化,非常有趣:

先说说退化的事件,尤大(vue的作者)曾经使用 window.postMessage 来替代 MO 实现 nextTick,结果开发者使用后发现了问题,可以看看这两个 JSFiddle:jsfiddle1 和 jsfiddle2, 两个例子用了不同版本来实现元素的绝对定位,第一个使用的是 2.0.0-rc6,这个版本采用的是 MO,而后来因为 IOS 9.3 的 WebView 里 MO 有 bug,尤大便换成 window.postMessage来实现,即第二个实例版本为 2.0.0-rc7, 但是由于 postMessage 会将回调放到 macrotask 其实也就是 task 里面,导致可能执行了多次 UI 的task都没有执行 window.postMessage 的 task,也就延迟了更新DOM操作的时间。尤大在后续版本撤回了这一次修改,具体的讨论可以看issue

关于进化,在后续的版本里,由于 es6 的新语法,nextTick 开始使用 Promise.then 和 MO 来做首选和次选,在前面的讨论中已经提到,Promise.then 也属于 microtask。

资源

MutationObserver MDN

Tasks, microtasks, queues and schedules

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

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

相关文章

  • VueJS源码学习——元素在插入和移出 dom 时的过渡逻辑

    摘要:原文地址项目地址关于中使用效果,官网上的解释如下当元素插入到树或者从树中移除的时候,属性提供变换的效果,可以使用来定义变化效果,也可以使用来定义首先第一个函数是将元素插入,函数实现调用了实现代码如下写的好的代码就是文档,从注释和命名上就 src/transition 原文地址项目地址 关于 vue 中使用 transition 效果,官网上的解释如下: With Vue.js’ tra...

    Dogee 评论0 收藏0
  • VueJS源码学习——工具类函数实现(二)

    摘要:它被当做一个轻量版本的使用,用于存储已排好版的或尚未打理好格式的片段。最大的区别是因为不是真实树的其中一部分,它的变化不会引起树的重新渲染的操作,或者导致性能影响的问题出现。 原文地址项目地址 工具类 /** * Simple bind, faster than native * * @param {Function} fn * @param {Object} ctx * @...

    fish 评论0 收藏0
  • 从Vue.js源码看异步更新DOM策略及nextTick

    摘要:我们发现默认是使用异步执行更新。优先使用,在不存在的情况下使用,这两个方法的回调函数都会在中执行,它们会比更早执行,所以优先使用。是最后的一种备选方案,它会将回调函数加入中,等到执行。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。文章的原地址:https://github.com/ans...

    leo108 评论0 收藏0
  • Vue.nextTick使用和源码分析

    摘要:而中的回调函数则会在页面渲染后才执行。还使用方法复制数组并把数组清空,这里的数组就是存放主线程执行过程中的函数所传的回调函数集合主线程可能会多次使用方法。到这里就已经实现了根据环境选择异步方法,并在异步方法中依次调用传入方法的回调函数。 Vue.nextTick的应用场景 Vue 是采用异步的方式执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲同一事件循环中发生的...

    Jrain 评论0 收藏0
  • 【Vue源码】Vue中DOM的异步更新策略以及nextTick机制

    摘要:本篇文章主要是对中的异步更新策略和机制的解析,需要读者有一定的使用经验并且熟悉掌握事件循环模型。这个结果足以说明中的更新并非同步。二是把回调函数放入一个队列,等待适当的时机执行。通过的主动来触发的事件,进而把回调函数作为参与事件循环。 本篇文章主要是对Vue中的DOM异步更新策略和nextTick机制的解析,需要读者有一定的Vue使用经验并且熟悉掌握JavaScript事件循环模型。 ...

    selfimpr 评论0 收藏0

发表评论

0条评论

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