Vue源码学习-初始化、更新流程分析

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


很简单的例子,一个父组件一个子组件,子组件接受一个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. 【Vue源码学习-初始化、更新流程分析】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 = https://www.it610.com/article/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里收集依赖,如下图。
    Vue源码学习-初始化、更新流程分析
    文章图片

    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 = https://www.it610.com/article/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 = https://www.it610.com/article/this.get() } }updateComponent = function () { vm._update( vm._render(), hydrating ); };

  4. 最后在 _update 方法中,进行 patch 操作,patch 里的具体逻辑就不在这里说了,有兴趣的小伙伴可以去看看我的另一篇文章《Vue源码学习-虚拟DOM+Diff算法》。
结尾
我是周小羊,一个前端萌新,写文章是为了记录自己日常工作遇到的问题和学习的内容,提升自己,如果您觉得本文对你有用的话,麻烦点个赞鼓励一下哟~

    推荐阅读