Vue响应式原理(2.6版本)

前言:此文章主要是分析Vue的响应式原理,从new Vue开始,看源码究竟发生了什么,其中会忽略掉大量边界处理以及“不重要”的代码,最后再验证一下,建议可以自己clone源码,然后跟着文中的步骤跟着看,文中对代码不会有太多讲解,源码本身的命名结构都是很清晰的(在去除边界处理代码之后),主要是记录过程,帮助以后自己回忆,如果你是对响应式原理完全不懂的,建议可以先看看Introduction - Advanced Components | Vue Mastery或者Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery

看源码!

  1. src/core/index.js中声明了Vue函数
function Vue(options) { this._init(options); }
// 设置Vue.prototype._init
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
  1. 接着来到src/core/instance/init.js,在initMixin中我们可以找到Vue中_init函数的声明,它主要是初始化了Vue组件的声明周期、事件、Render,然后触发了beforeCreate的钩子,再接着初始化Injections、Provide,再触发了created的钩子,最后挂载组件
export function initMixin (Vue: Class<Component>) {
    Vue.prototype._init = function(options?: Object) {
        const vm: Component = this;
        // ...Normalizing options ...
        vm._self = vm;
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initInjections(vm);
        initState(vm);
        initProvide(vm);
        callHook(vm, 'created');
        // ...
        vm.$mount(vm,$options.el);
    }
}
  1. 这其中我们需要关注的就是initState,来到src/core/instance/state.js
export function initState (vm: Component) {
    vm._watchers = [];
    const opts = vm.$options;
    if (opts.props) initProps(vm, opts.props)
    if (opts.methods) initMethods(vm, opts.methods);
    if (opts.data) {
        initData(vm);
    } else {
        observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) initComputed(vm, opts.computed);
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
    }
}

可以看到,在initState中主要是初始化Props、Methods、Data、Computed(计算属性)和Watch(侦听器)

  1. 我们要关注的是initData,initData还是在state.js中
function initData (vm: Component) {
    let data = vm.$options.data;
    // ...
    observe(data, true /* asRootData */);
}
  1. 紧接着是observe(src/core/observer/index.js)
/**
* 尝试为value创建一个observer实例,并返回这个observer
*/
export function observe(value: any, asRootData: ?boolean): Observer | void {
    // ...
    ob = new Observer(value);
    return ob;
}
  1. 继续在src/core/observer/index.js找到Observer的Class声明
export class Observer {
    value: any;
    dep: Dep;

    constructor (value: any) {
        this.value = value;
        // ...
        if (Array.isArray(value)) {
            // ...
            this.observeArray(value);
        } else {
            this.walk(value);
        }
    }

    walk (obj: Object) {
        const keys = Object.keys(obj);
        for (let i = 0; i< keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]]);
        }
    }

    observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[I]);
        }
    }
}

export function defineReactive (...) {
    const dep = new Dep();
    // ...
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        get: function reactiveGetter () { dep.depend(); },
        set: function reactiveSetter (newVal) { dep.notify(); }
    });
    //  ...
}
  1. 下面要看看Dep是如何定义的了,来到src/core/observer/dep.js
export default class Dep {
    static target: ?Watcher;
    subs: Array<Watcher>;
    constructor () {
        this.subs = [];
    }

    addSub (sub: Watcher) {
        this.subs.push(sub);
    }

    depend () {
        if (Dep.tartget) {
            Dep.target.addDep(this);
        }
    }

    notify () {
        const subs = this.subs.slice();
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    }
}
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
  1. 最后就是Watcher,让我们把目光转移到src/core/observer/watcher.js
import Dep, { pushTarget, popTarget } from './dep'

export deafult class Watcher {
    vm: Component;
    newDepIds: ISet;
  getter: Function;
    value: any;

    constructor (vm: Component, expOrFn: string | Function ...) {
        // ...
        this.vm = vm;
        this.getter = expOrFn;
        // ...
        this.value = this.get();
    }

    get () {
        pushTarget(this);
        const vm = this.vm;
        let value = this.getter.call(vm, vm);
        // ...
        popTarget();
        return value;
    }
    
    update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

梳理一下

总的来理一遍:
首先,new Vue会调用initState,为data创建一个Observer类的实例,同时递归为data中的Object类型的property创建一个Observer类的实例(如果这些property中还有Object类型的属性,则继续递归创建Observer),并为每一个Observer都创建了一个Dep类的实例;
然后使用了Object.defineProperty来拦截并重写property的get和set,在defineProperty中为每个property都创建了一个Dep类的实例,再在get和set中分别调用了dep.depend()和dep.notify()用于将当前的Watcher添加到dep的订阅列表中和触发订阅列表中Wacther的updat方法;而Watcher才是真正记录了使用响应式数据的函数的类

验证一下

来看一个简单的例子:

<template>
  <div>
    <h1>{{ obj.name }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      obj1: {
        name: "test",
        obj2: {
          test: 'test'
        },
      },
    };
  },
  mounted() {
    console.log(this.obj);
  },
};
</script>

来看看控制台的打印:

  1. 首先,data中的obj是有一个ob指向它的Observer的(data也有,但并没有直接有一个ob属性指向它的Observer)

  2. 然后我们也能看到obj中的obj也是有一个ob属性的,同样指向它的Observer,同时也能看到每一个Observer都对应的有一个Dep

  3. 接着就是确认get和set中的dep,下面是get name和set name中的dep,可以通过id确认这两个dep是同一个dep,均为defineProperty时闭包中的dep




    至此响应式原理分析和验证告一段落,最后放一张图帮助理解


    14F77938-0913-4378-99E6-04B75BE27E5B.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容