本篇文章通过一个栗子来聊聊Vue初始化和更新数据的大抵流程:

<div id="demo">    <child :list="list"></child>    <button @click="handleAdd">add</button></div><script>    Vue.component('child', {        props: {            list: {                type: Array,                default: () => []            }        },        template: '<p>{{ list }}</p>'    })    new Vue({        el: "#demo",        data() {          return {              list: [1,2]          }        },        methods: {            handleAdd() {                this.list.push(Math.random())            }        }    })</script>

很简略的例子,一个父组件一个子组件,子组件承受一个list,父组件有个按钮,能够往list里push数据扭转list。

初始化流程:

  1. 首先从 new Vue({el: "#app"}) 开始,会执行 _init 办法。

    function Vue (options) {   // 省略...   this._init(options)}
  2. _init 办法的最初执行了 vm.$mount 挂载实例。

    Vue.prototype._init = function (options) {   var vm = this;   // 省略...   if (vm.$options.el) {       vm.$mount(vm.$options.el);   }}
  3. 如果此时运行的版本是 runtime with compiler 版本,这个版本的 $mount 会被进行重写,减少了把template模板转成render渲染函数的操作,但最终都会走到 mountComponent 办法。

    Vue.prototype.$mount = function (el, hydrating) {     el = el && inBrowser ? query(el) : undefined;     return mountComponent(this,el,hydrating);};var mount = Vue.prototype.$mount; //缓存上一次的Vue.prototype.$mountVue.prototype.$mount = function (el, hydrating) { //重写Vue.prototype.$mount     // 省略,将template转化为render渲染函数     return mount.call(       this,       el,       hydrating     )};
  4. mountComponent 里触发了 beforeMountmounted 生命周期,更重要的是创立了 Watcher,传入的 updateComponent 就是Watcher的 getter

    function mountComponent(vm, el, hydrating) {     // 执行生命周期函数 beforeMount     callHook(vm, 'beforeMount');     var updateComponent;     //如果开发环境     if ("development" !== 'production' && config.performance && mark) {            // 省略...     } else {         updateComponent = function () {             vm._update(                 vm._render(), // 先执行_render,返回vnode                 hydrating             );         };     }     new Watcher(         vm,         updateComponent,         noop,         null,         true // 是否渲染过得观察者     );         if (vm.$vnode == null) {         vm._isMounted = true;         // 执行生命周期函数mounted         callHook(vm, 'mounted');     }     return vm }
  5. 在创立 Watcher 时会触发 get() 办法,pushTarget(this)Dep.target 设置为以后 Watcher 实例。

    function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {   if (typeof expOrFn === 'function') {       this.getter = expOrFn;   }   this.value = this.lazy ?  // 这个有是组件才为真       undefined :       this.get(); //计算getter,并从新收集依赖项。 获取值}; Watcher.prototype.get = function get() {     pushTarget(this);     var value;     var vm = this.vm;     try {         value = this.getter.call(vm, vm);     } catch (e) {     } finally {         popTarget();     }     return value };
  6. Watcherget() 里会去读取数据,触发 initData 时应用 Object.defineProperty 为数据设置的 get,在这里进行依赖收集。咱们晓得Vue中每个响应式属性都有一个 __ob__ 属性,寄存的是一个Observe实例,这里的 childOb 就是这个 __ob__,通过 childOb.dep.depend() 往这个属性的__ob__中的dep里收集依赖,如下图。

    export function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: Function) {  /*在闭包中定义一个dep对象*/  const dep = new Dep()  let childOb = observe(val)  Object.defineProperty(obj, key, {   enumerable: true,   configurable: true,   get: function reactiveGetter () {     /*如果本来对象领有getter办法则执行*/     const value = getter ? getter.call(obj) : val     if (Dep.target) {       /*进行依赖收集*/       dep.depend()       if (childOb) {         childOb.dep.depend()       }       if (Array.isArray(value)) {         dependArray(value)       }     }     return value   },   set: function reactiveSetter (newVal) {   }  })}
  7. 在咱们的例子中,这个list会收集两次依赖,所以它 __ob__ 的subs里会有 两个Watcher,第一次是在父组件 data 中的 list,第二次是在创立组件时调用 createComponent ,而后又会走到 _init => initState => initProps ,在 initProps 内对 props 传入的属性进行依赖收集。有两个Watcher就阐明list扭转时要告诉两个中央,这很好了解。
    .
  8. 最初,触发 getter,下面说过 getter 就是 updateComponent,外面执行 _update 更新视图。

上面来说说更新的流程:

  1. 点击按钮往数组中增加一个数字,在Vue中,为了监听数组变动,对数组的罕用办法做了重写,所以先会走到 ob.dep.notify() 这里,ob 就是 list 的 __ob__ 属性,下面保留着Observe实例,外面的dep中有两个 Watcher,调用 notify 去告诉所有Watcher对象更新视图。

    [  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse'].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator () {   let i = arguments.length   const args = new Array(i)   while (i--) {     args[i] = arguments[i]   }   /*调用原生的数组办法*/   const result = original.apply(this, args)   const ob = this.__ob__   let inserted   switch (method) {     case 'push':       inserted = args       break     case 'unshift':       inserted = args       break     case 'splice':       inserted = args.slice(2)       break   }   if (inserted) ob.observeArray(inserted)   /*dep告诉所有注册的观察者进行响应式解决*/   ob.dep.notify()   return result })})
  2. notify 办法里去告诉所有 Watcher 更新,执行 Watcherupdate 办法,update 里的 queueWatcher 过滤了一些反复的 Watcher, 但最终会走到 Watcherrun() 办法。

    Dep.prototype.notify = function notify() {   var subs = this.subs.slice();   for (var i = 0, l = subs.length; i < l; i++) {       subs[i].update();   }};Watcher.prototype.update = function update() { if (this.lazy) {     this.dirty = true; } else if (this.sync) {     this.run(); } else {     queueWatcher(this); }};
  3. run 办法里会调用 get(), get 办法里回去触发Watcher的 getter,下面说过,getter 就是 updateComponent

    Watcher.prototype.run = function run() {  if (this.active) { /* get操作在获取value自身也会执行getter从而调用update更新视图 */ const value = this.get()  }}updateComponent = function () {  vm._update(      vm._render(),      hydrating  ); };
  4. 最初在 _update 办法中,进行 patch 操作,patch 里的具体逻辑就不在这里说了,有趣味的小伙伴能够去看看我的另一篇文章《Vue源码学习-虚构DOM+Diff算法》。

结尾

我是周小羊,一个前端萌新,写文章是为了记录本人日常工作遇到的问题和学习的内容,晋升本人,如果您感觉本文对你有用的话,麻烦点个赞激励一下哟~