面试题:created生命周期中两次修正数据,会触发几回页面更新?

一、同步的

先举个简单的同步的例子:

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    }
  },
  created() {
    this.count = 2;
    this.count = 3;
  },
});

created生命周期中,经过this.count = 2this.count = 3的办法将this.count从头赋值。

这儿直接抛出答案:烘托一次。


为什么?

这个与数据的呼应式处理有关,先看呼应式处理的逻辑:

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 要点:创建一个发布者实例
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 要点:进行当时正在核算的烘托Watcher的搜集
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 要点:当数据发生改变时,发布者实例dep会告诉搜集到的watcher进行更新
      dep.notify()
    }
  })
}

在数据呼应式处理阶段,会实例化一个发布者dep,并且经过Object.defineProperty的办法为当时数据界说getset函数。在生成虚拟vNode的阶段,会触发get函数中会进行当时正在核算的烘托Watcher的搜集,此刻,发布者depsubs中会多一个烘托Watcher实例。在数据发生改变的时分,会触发set函数,告诉发布者depsubs中的watcher进行更新。

至于数据修正会触发几回更新,就与当时发布者depsubs中搜集了几回烘托watcher有关了,再看watcher搜集和created履行之间的顺序:

Vue.prototype._init = function (options) {
    // ...
    initState(vm);
    // ...
    callHook(vm, 'created');
    // ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
}

咱们知道在initState(vm)阶段对数据进行呼应式处理,可是此刻发布者depsubs仍是空数组。当履行callHook(vm, 'created')的时分,会履行this.count = 2this.count = 3的逻辑,也的确会触发set函数中的dep.notify告诉搜集到的watcher进行更新。可是,此刻depsubs是空数组,相当于啥也没做。

只有在vm.$mount(vm.$options.el)履行过程中,生成虚拟vNode的时分才会进行烘托Watcher搜集,此刻,depsubs才不为空。最终,经过vm.$mount(vm.$options.el)进行了页面的一次烘托,并未由于this.count=2或许this.count=3而触发多余的页面更新。

简言之,就是created钩子函数内的逻辑的履行是在烘托watcher搜集之前履行的,所以未引起由于数据改变而导致的页面更新。

二、异步的

同步的场景说完了,咱们再举个异步的例子:

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    }
  },
  created() {
    setTimeout(() => {
      this.count = 2;
    }, 0)
    setTimeout(() => {
      this.count = 3;
    }, 0)
  },
});

created生命周期中,经过异步的办法履行this.count = 2this.count = 3的办法将this.count从头赋值。

这儿直接抛出答案:初次烘托一次,由于数据改变导致的页面更新两次。


为什么?

这个就与eventLoop事情循环机制有关了,咱们知道javascript是一个单线程履行的语言,当咱们经过new Vue实例化的过程中,会履行初始化办法this._init办法,开始了Vue底层的处理逻辑。当遇到setTimeout异步操作时,会将其推入到异步行列中去,等候当时同步使命履行完以后再去异步行列中取出队首元素进行履行。

当时例子中,在initState(vm)阶段对数据进行呼应式处理。当履行callHook(vm, 'created')的时分,会将this.count = 2this.count = 3的逻辑推入到异步行列等候履行。持续履行vm.$mount(vm.$options.el)的过程中会去生成虚拟vNode,从而触发get函数的烘托Watcher搜集,此刻,depsubs中就有了一个烘托watcher

等初次页面烘托完结以后,会去履行this.count=2的逻辑,数据的修正会触发set函数中的dep.notify,此刻发布者depsubs不为空,会引起页面的更新。同理,this.count=3会再次引起页面数据的更新。也就是说,初次烘托一次,由于this.count=2this.count=3还会导致页面更新两次。

三、附加

假如我改变的值和data中界说的值共同呢?

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    }
  },
  created() {
    setTimeout(() => {
      this.count = 1;
    }, 0)
  },
});

这个时分,在触发set的逻辑中,会当履行到if (newVal === value || (newVal !== newVal && value !== value)) { return }的逻辑,不会再履行到dep.notify,这种场景下数据的数据也不会引起页面的再次更新。

总结

从生命周期created和页面烘托的先后顺序,Object.defineProperty触发getset函数的机理,以及eventLoop事情循环机制下手,去剖析created中两次数据修正会触发几回页面更新的问题就会清晰很多。