美文网首页
Vue中的nextTick机制

Vue中的nextTick机制

作者: Gopal | 来源:发表于2017-11-21 16:27 被阅读167次

项目中遇到的坑描述:

在vue中,created的时候,发送请求,并使用返回的数据进行渲染DOM节点,然后在mounted中操作DOM节点的时候,发现得到的DOM节点是undefined

原因:
在created里面开始渲染的DOM节点,但执行到mounted的时候并没有渲染结束,所以找不到DOM元素。因为赋值操作只完成了数据模型的改变并没有完成视图更新。

解决:
使用vm.$nextTick()
官方文档介绍:
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

示例:

new Vue({
  // ...
  methods: {
    // ...
    example: function () {
      // 修改数据
      this.message = 'changed'
      // DOM 还没有更新
      this.$nextTick(function () {
        // DOM 现在更新了
        // `this` 绑定到当前实例
        this.doSomethingElse()
      })
    }
  }
})

源码解读
定义一些变量

var callbacks = [];   // 缓存函数的数组
var pending = false;  // 是否正在执行
var timerFunc;  // 保存着要执行的函数
function nextTickHandler () {
  pending = false;
  //  拷贝出函数数组副本
  var copies = callbacks.slice(0);
  //  把函数数组清空
  callbacks.length = 0;
  // 依次执行函数
  for (var i = 0; i < copies.length; i++) {
    copies[i]();
  }
}

这个函数就是$nextTick内部实际调用的函数

接下来的目标就是延迟调用这个函数了,目标就是把传进来的函数延迟到DOM更新完之后再使用

接下来它用了三种方法,依次优雅降级的使用JavaScript方法做到这一点。

promise.then延迟调用
Promise.then方法将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  var logError = function (err) { console.error(err); };
  timerFunc = function () {
    p.then(nextTickHandler).catch(logError);
    if (isIOS) { setTimeout(noop); }
  };
}

MutationObserver监听变化

else if (typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {

  var counter = 1;
  var observer = new MutationObserver(nextTickHandler);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
}

MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。

具体有一下几点变动的监听

  • childList:子元素的变动
  • attributes:属性的变动
  • characterData:节点内容或节点文本的变动
  • subtree:所有下属节点(包括子节点和子节点的子节点)的变动

可以看出,以上代码是创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起dom节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有dom渲染完后,执行函数,达到我们延迟的效果。

setTimeout延迟器
利用了setTimeout的延迟原理,setTimeout(func,0)将func函数延迟到下一次调用栈的开始,也就是当前函数执行完毕后再接着执行该函数

else {
    timerFunc = function () {
      setTimeout(nextTickHandler, 0);
    };
  }

闭包函数

return function queueNextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) { cb.call(ctx); }
      if (_resolve) { _resolve(ctx); }
    });
    // 如果没有函数队列在执行才执行
    if (!pending) {
      pending = true;
      timerFunc();
    }
    // promise化
    if (!cb && typeof Promise !== 'undefined') {
      console.log('进来了')
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

每次添加函数的时候,都会向callbacks这个函数数组中入栈,然后监听当前是否在执行,如果没有,执行函数。if是为了promise化

promise化

this.$nextTick(function () {

})
// promise化
this.$nextTick().then(function () {

}.bind(this))

直接调用$nextTick函数,然后使用promise格式去书写代码,不过这个then里面需要手动绑定this,vue内部没有做处理

vue之nextTick全面解析

相关文章

网友评论

      本文标题:Vue中的nextTick机制

      本文链接:https://www.haomeiwen.com/subject/crlwvxtx.html