vue中watch和computed为什么能监听到数据的改变以及不同之处

先来个流程图,水平有限,凑活看吧-_-||

首先在创建一个Vue应用时:

var app = new Vue({
  el: '#app',data: {
    message: Hello Vue!
  }
})

Vue构造函数源码:

//创建Vue构造函数
function Vue (options) {
  if (!(this instanceof Vue)
  ) {
    warn(Vue is a constructor and should be called with the `new` keyword);
  }
  ._init(options);
}
_init方法,会初始化data,watch,computed等
Vue.prototype._init = function (options) {
  var vm = ;
   a uid
  vm._uid = uid$3++;
  ......
   expose real self
  vm._self = vm;
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook(vm,beforeCreate);
  initInjections(vm);  resolve injections before data/props
  initState(vm);
  ......
};

在initState方法中会初始化data、watch和computed,并调用observe函数监听data(Object.defineProperty):

function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm,opts.props); }
   (opts.methods) { initMethods(vm,opts.methods); }
   (opts.data) {
    initData(vm);//initData中也会调用observe方法
  } else {
    observe(vm._data = {},true /* asRootData */);
  }
   (opts.computed) { initComputed(vm,opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm,opts.watch);
  }
}

1、observe

observe在initState 时被调用,为vue实例的data属性值创建getter、setter函数,在setter中dep.depend会把watcher实例添加到Dep实例的subs属性中,在getter中会调用dep.notify,调用watcher的update方法。

/**
 * Attempt to create an observer instance for a value,* returns the new observer if successfully observed,* or the existing observer if the value already has one.
 * 该函数在initState中有调用
 */
function observe (value,asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value,'__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  re * Observer class that is attached to each observed
 * object. Once attached,the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value,'__ob__',this);
  if (Array.isArray(value)) {
    if (hasProto) {
      protoAugment(value,arrayMethods);
    } else {
      copyAugment(value,arrayMethods,arrayKeys);
    }
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};
/**
 * Walk through all properties and convert them into
 * getter/setters. This method should only be called when
 * value type is Object.
 */
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj,keys[i]);
  }
};
/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
  obj,key,val,customSetter,shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj,key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  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(obj,{
    enumerable: true,configurable: true,get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
    //Dep.target 全局变量指向的就是指向当前正在解析生成的 Watcher
    //会执行到dep.addSub,将Watcher添加到Dep对象的Watcher数组中 if (Dep.target) { dep.depend(); 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) { 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.notify();
//如果数据被重新赋值了,调用 Dep 的 notify 方法,通知所有的 Watcher
 } }); }

2、Dep

Watcher的update方法是在new Dep的notify的方法中被调用的

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};
//设置某个Watcher的依赖
//这里添加Dep.target,用来判断是不是Watcher的构造函数调用
//也就是其this.get调用 Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } }; //在该方法中会触发subs的update方法 Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); if (!config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a,b) { return a.id - b.id; }); } for (var i = 0,l = subs.length; i < l; i++) { subs[i].update(); } };

3、watch

初始化watch,函数中会调用createWatcher,createWatcher会调用$watch,$watch调用new Watcher实例。

 initWatch (vm,watch) {
  for (var key in watch) {
    var handler = watch[key];
     (Array.isArray(handler)) {
      var i = 0; i < handler.length; i++) {
        createWatcher(vm,handler[i]);
      }
    }  {
      createWatcher(vm,handler);
    }
  }
}
 createWatcher (
  vm,expOrFn,handler,options
) {
   (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn,options)
}
Vue.prototype.$watch =  (
  expOrFn,cb,1)"> (isPlainObject(cb)) {
     createWatcher(vm,options)
  }
  options = options || {};
  options.user = truevar watcher = new Watcher(vm,options);
   (options.immediate) {
    try {
      cb.call(vm,watcher.value);
    } catch (error) {
      handleError(error,vm,("callback for immediate watcher \"" + (watcher.expression) + "\""));
    }
  }
  return  unwatchFn () {
    watcher.teardown();
  }
};
}

2、computed

初始化computed,调用new Watcher(),并通过defineComputed函数将计算属性挂载到vue实例上,使计算属性可以在模板中使用


var computedWatcherOptions = { lazy: true }
 initComputed (vm,computed) {
   $flow-disable-line
  var watchers = vm._computedWatchers = Object.create(null);
   computed properties are just getters during SSR
  var isSSR = isServerRendering();

   computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
  //getter也就是computed的函数
if (getter == ) { warn( ("Getter is missing for computed property \"" + key + "\"."),vm ); } if (!isSSR) { create internal watcher for the computed property. watchers[key] = Watcher( vm,getter || noop,noop,computedWatcherOptions ); } 组件定义的计算属性已在 组件原型。我们只需要定义定义的计算属性 在这里实例化。 if (!(key vm)) { defineComputed(vm,userDef); } { if (key vm.$data) { warn(("The computed property \"" + key + "\" is already defined in data."else if (vm.$options.props && key vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop." this
);
};
}
Object.defineProperty(target,sharedPropertyDefinition);
}
//computed的getter函数,在模板获取对应computed数据时会调用
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {//true
watcher.evaluate();//该方法会调用watcher.get方法,也就是computed对应的函数
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
 

通过以上代码可以看到watch和computed都是通过new Watcher实例实现数据的监听的,但是computed的options中lazy为true,这个参数导致它们走的是两条不同路线。

computed:模板获取数据时,触发其getter函数,最终调用watcher.get,也就是调用对应回调函数。

watch:模板获取数据时,触发其getter函数,将watcher添加到对应的Dep.subs中,在之后setter被调用时,Dep.notify通知所有watcher进行update,最终调用watcher.cb,也就是调用对应回调函数。

3、Watcher

构造函数在是watch时,会最后调用this.get,会触发属性的getter函数,将该Watcher添加到Dep的subs中,用于通知数据变动时调用。

调用Watcher实例的update方法会触发其run方法,run方法中会调用触发函数。其depend方法会调用new Dep的depend方法,dep的depend会调用Watcher的addDep方法,最终会把该watcher实例添加到Dep的subs属性中

*
   *观察者解析表达式,收集依赖项,
   *并在表达式值更改时激发回调。
   *这用于$watch()api和指令。
   */
  var Watcher =  Watcher (
    vm,options,isRenderWatcher
  ) {
    this.vm = vm;
  ......
    this.cb = cb;触发函数
    this.id = ++uid$2;  uid for batching
    this.active = ;
    this.dirty = this.lazy; // for lazy watchers
  ......
    this.value = this.lazy ? undefined ? this.get();computed会返回undefined,而watch会执行Watcher.get
  };

  *
   * Scheduler job interface.
   * Will be called by the scheduler.
   * 该方法会执行触发函数
   */
  Watcher.prototype.run =  run () {
    .active) {
      var value = .get();
       (
        value !== this.value ||
         Deep watchers and watchers on Object/Arrays should fire even
         when the value is the same,because the value may
         have mutated.
        isObject(value) ||
        .deep
      ) {
         set new value
        var oldValue = .value;
        this.value = value;
        .user) {
           {
            this.cb.call(this.vm,value,oldValue);
          }  (e) {
            handleError(e,this.vm,("callback for watcher \"" + (this.expression) + "\""));
          }
        }  {
          *
   * Evaluate the getter,and re-collect dependencies.
   
  Watcher.prototype.get =  get () {
    pushTarget();
    var value;
    .vm;
     {
      value = .getter.call(vm,vm);
    }  (e) {
      .user) {
        handleError(e,("getter for watcher \"" + ());
      }  {
        throw e
      }
    } finally "touch" every property so they are all tracked as
       dependencies for deep watching
      .deep) {
        traverse(value);
      }
      popTarget();
      .cleanupDeps();
    }
     value
  };

  *
   * Subscriber interface.
   * Will be called when a dependency changes.
   * 在方法中调用Watcher的run方法
   
  Watcher.prototype.update =  update () {
     istanbul ignore else */
    .lazy) {
      ;
    } .sync) {
      .run();
    }  {
      queueWatcher(this);该方法最终也会调用run方法
    }
  };
  *
   * Depend on all deps collected by this watcher.会调用new Dep的depend方法,dep的depend会调用Watcher的addDep方法
   
  Watcher.prototype.depend =  depend () {
    var i = .deps.length;
    while (i--) {
      .deps[i].depend();
    }
  };
  *
   * Add a dependency to this directive.
   
  Watcher.prototype.addDep =  addDep (dep) {
    var id = dep.id;
    .newDepIds.has(id)) {
      .newDepIds.add(id);
      .newDeps.push(dep);
      .depIds.has(id)) {
        dep.addSub();
      }
    }
  };

 

相关文章

https://segmentfault.com/a/1190000022018995 https://www....
ES6 (ECMAScript 6)中的模块是一个包含 JavaScript 代码的...
from https://mp.weixin.qq.com/s/-rc1lYYlsfx-wR4mQmIIQQ V...
D:\Temp&gt;npm init vite@latest vue3study --temp...
文章浏览阅读1.2k次。最近自己从零撸起的甘特图组件需要子组...
文章浏览阅读3.3k次,点赞3次,收藏16次。静默打印是什么?简...