Vue双向数据绑定的原理源码解析

Vue追踪数据变化

把一个普通的js对象传入Vue实例作为data选项,Vue将遍历该对象的所有属性,并使用Object.defineProperty转为getter/setter

每个组件实例都对应一个watcher实例,会在组件渲染的过程中把涉及到的数据属性记录为依赖。之后当依赖项的setter触发时,就会通知watcher,进而重新渲染相关的组件。

注意事项

属性必须在data对象上存在才能让Vue将其转换为响应式的

对于已经创建的实例,Vue不允许动态添加根级别的响应式属性,可以用Vue.set()来添加

vue的双向数据绑定通过数据劫持+发布订阅实现的

  1. 数据劫持的实现
    vuejs里定义了一个observe(value, asRootData)方法, 该方法会检测传入的value是否已经被劫持过,如果是直接返回,如果不是,会用value实例化一个Observer实例,Observer方法中,通过Object.defineProperty对对象属性转为getter/setter,从而在数据发生变化时进行一系列操作。

observe方法在以下阶段有执行过:

  • initMixin() -> initState() -> initData()中执行,对vue实例的data属性进行处理
  • initMixin() -> initState() -> initProps()-> validateProp()中执行, 对props里的属性进行处理
 function observe (value, asRootData) {
    if (!isObject(value) || value instanceof VNode) {
      return
    }
    var ob;
    //‘__ob__’属性时,初次实例Observer时定义的。
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__;
    } else if (
      ...
    ) {
      ob = new Observer(value); //实例化Observer
    }
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob
  }

Observer实例

var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep(); //1 订阅者容器, 用于存放所有订阅者
    this.vmCount = 0;
    def(value, '__ob__', this); //通过Object.defineProperty给value定义一个__ob__属性
    if (Array.isArray(value)) { //数组
      if (hasProto) { // var hasProto = '__proto__' in {};
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else { 
      this.walk(value);
    }
  };
//Observer的原型方法:
Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]); //重写obj对象keys[i]属性的get和set方法
    }
  };
Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]); //对于数组的处理,每一项都要observe一次
    }
  };
  
  //1 订阅者构造函数
var Dep = function Dep () {
    this.id = uid++;
    this.subs = []; //存放的是watcher
  };
Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };

Dep.prototype.removeSub = function removeSub (sub) {
   remove(this.subs, sub); //从数组中删除指定项
 };

 Dep.prototype.depend = function depend () {
   if (Dep.target) {
     Dep.target.addDep(this); //当前订阅者,watcher实例,执行watcher实例的addDep方法
   }
 };

 Dep.prototype.notify = function notify () {
  ...
   for (var i = 0, l = subs.length; i < l; i++) {
     subs[i].update(); //watcher实例,依次执行watcher实例的update方法
   }
 };

以上是Observer的主要源码,对传入的对象进行处理,如果已有__ob__属性,说明已经拦截处理过,直接返回__ob__属性的值即可。否则,针对传入的对象时数组还是对象,分别处理。最终都是要经过defineReactive$$1()方法来重写对象属性的get和set方法。

function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep(); //新建一个订阅者容器实例

    var property = Object.getOwnPropertyDescriptor(obj, key);
    //如果属性是不可配置的,跳过
    if (property && property.configurable === false) {
      return
    }
    //获取get和set方法
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }
    var childOb = !shallow && observe(val);
    //通过Object.defineProperty()对对象的属性进行处理,主要是get和set方法的处理
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          dep.depend(); //调用了depend()方法,实际是调用了当前watcher的addDep()
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        var 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 (customSetter) { //如果有自定义的set方法,执行
          customSetter();
        }
        if (getter && !setter) { return }
        if (setter) { //执行setter方法
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify(); //关键,上面提过,dep.notify() 实际是调用了watcher的update方法,执行更新
      }
    });
  }

Watcher

var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    ...
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get(); //调用自身的get方法,把自己添加到订阅者列表中
  };
  //原型方法
  Watcher.prototype.get = function get () {
    pushTarget(this); //指定当前Watcher为Dep.target
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      ...
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };
  Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this); //把自身传入订阅者列表中 Dep的subs数组中
      }
    }
  };
 Watcher.prototype.cleanupDeps = function cleanupDeps () {
 //清空
    var i = this.deps.length;
    while (i--) {
      var dep = this.deps[i];
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this);
      }
    }
    var tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  };
  //重点,Watcher的update方法,当数据发生变化时,调用
 Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) { //如果是同步的,立即执行
      this.run();
    } else { //否则传入任务队列
      queueWatcher(this);
    }
  };
  //watcher的更新方法,执行传入的回调方法
 Watcher.prototype.run = function run () {
    if (this.active) {
      var value = this.get();
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        // 获取变化前的值
        var oldValue = this.value;
        //获取当前新值
        this.value = value;
        if (this.user) {
          try { //执行回调
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
          }
        } else { //执行回调
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

Watcher 又是在什么地方实例化的呢

  1. stateMinxin -> vue.protorype.$watch方法中
  2. initMixin -> initData -> initComputed中
  3. initMixin -> vue.prototype.$mount -> mountComponent中

前两者主要是对vue实例中配置的watch和computed处理,同步数据的变化,最后一个是将视图和数据的绑定。

在Vuejs中,全局执行的方法主要有 initMixin(Vue), stateMixin(Vue), eventsMixin(Vue), lifecycleMixin(Vue), renderMixin(Vue), initGlobalAPI(Vue)根据前缀基本可以理解分别是处理的哪些部分。

initMixin(Vue)中,主要是初始化的工作, 例如初始化,initProxy, initLifecycle, initEvents, initRender, initInjection, initState, initProvide等,最后还执行了vm.$mount方法,进行视图的挂载

其中initRender方法中,通过defineReactive$$1对vue实例的$attrs$listeners属性进行了拦截处理

vm.$mount方法中通过mountComponent(this, el, hydrating)指定了挂载和视图更新方法

function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode; //一个空节点
      {
        /* istanbul ignore if */
        if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
          vm.$options.el || el) {
          warn(
            'You are using the runtime-only build of Vue where the template ' +
            'compiler is not available. Either pre-compile the templates into ' +
            'render functions, or use the compiler-included build.',
            vm
          );
        } else {
          warn(
            'Failed to mount component: template or render function not defined.',
            vm
          );
        }
      }
    }
    callHook(vm, 'beforeMount');

    var updateComponent;
    /* istanbul ignore if */
    if (config.performance && mark) {
      updateComponent = function () {
        var name = vm._name;
        var id = vm._uid;
        var startTag = "vue-perf-start:" + id;
        var endTag = "vue-perf-end:" + id;

        mark(startTag);
        var vnode = vm._render();
        mark(endTag);
        measure(("vue " + name + " render"), startTag, endTag);

        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(("vue " + name + " patch"), startTag, endTag);
      };
    } else {
      updateComponent = function () {
        vm._update(vm._render(), hydrating); //更新视图
      };
    }
//实例化Watcher,指定发生变化时的回调方法, 在实例已经挂载并且未销毁前每次更新都会触发beforeUpdate生命周期钩子
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
    hydrating = false;
    //执行vue 实例传入的mounted()方法
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }

vm._update大概介绍,该部分主要是对视图进行更新,通过虚拟DOM

Vue.prototype._update方法是在,initLifecycle中定义的,执行中调用了vm.__patch__方法;
vm.__patch__方法中调用了patch,patch是一个全局方法,被赋值为createPatchFunction({ nodeOps: nodeOps, modules: modules })的返回值, 该方法就是 Virtual DOM的实现方法

综上:
vue初始化阶段,通过new Observer()实例,调用defineReactive$$1方法对对象的属性进行拦截,并重写get和set方法,set方法中如果值发生了改变,会执行订阅者列表中watcher的update方法,进行更新。
挂载dom时,会把生成的DOM树对象new Watcher, 添加到订阅者列表,当视图发生变化时,执行传入_update中的_render方法更新视图

发布了66 篇原创文章 · 获赞 13 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/haoyanyu_/article/details/99713075