欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-09-28 20:56:02
先来个流程图,水平有限,凑活看吧-_-|| 首先在创建一个Vue应用时: Vue构造函数源码: 在initState方法中会初始化data、watch和computed,并调用observe函数监听data(Object.defineProperty): 1、observe observe在init ......

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

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');
  }
  this._init(options);
}
//_init方法,会初始化data,watch,computed等
vue.prototype._init = function (options) {
  var vm = this;
  // 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); }
  if (opts.methods) { initmethods(vm, opts.methods); }
  if (opts.data) {
    initdata(vm);//initdata中也会调用observe方法
  } else {
    observe(vm._data = {}, true /* asrootdata */);
  }
  if (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, key, {
    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实例。

function initwatch (vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (array.isarray(handler)) {
      for (var i = 0; i < handler.length; i++) {
        createwatcher(vm, key, handler[i]);
      }
    } else {
      createwatcher(vm, key, handler);
    }
  }
}
function createwatcher (
  vm,
  exporfn,
  handler,
  options
) {
  if (isplainobject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {
    handler = vm[handler];
  }
  return vm.$watch(exporfn, handler, options)
}
vue.prototype.$watch = function (
  exporfn,
  cb,
  options
) {
  var vm = this;
  if (isplainobject(cb)) {
    return createwatcher(vm, exporfn, cb, options)
  }
  options = options || {};
  options.user = true;
  var watcher = new watcher(vm, exporfn, cb, options);
  if (options.immediate) {
    try {
      cb.call(vm, watcher.value);
    } catch (error) {
      handleerror(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
    }
  }
  return function unwatchfn () {
    watcher.teardown();
  }
};
}

2、computed

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


var computedwatcheroptions = { lazy: true }
function initcomputed (vm, computed) {
  // $flow-disable-line
  var watchers = vm._computedwatchers = object.create(null);
  // computed properties are just getters during ssr
  var isssr = isserverrendering();

  for (var key in computed) {
    var userdef = computed[key];
    var getter = typeof userdef === 'function' ? userdef : userdef.get;
  //getter也就是computed的函数 if (getter == null) { warn( ("getter is missing for computed property \"" + key + "\"."), vm ); } if (!isssr) { // create internal watcher for the computed property. watchers[key] = new watcher( vm, getter || noop, noop, computedwatcheroptions ); } //组件定义的计算属性已在 //组件原型。我们只需要定义定义的计算属性 //在这里实例化。 if (!(key in vm)) { definecomputed(vm, key, userdef); } else { if (key in vm.$data) { warn(("the computed property \"" + key + "\" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("the computed property \"" + key + "\" is already defined as a prop."), vm); } } } }
function definecomputed (
target,
key,
userdef
) {
var shouldcache = !isserverrendering();//true
if (typeof userdef === 'function') {
sharedpropertydefinition.get = shouldcache
? createcomputedgetter(key)
: creategetterinvoker(userdef);
sharedpropertydefinition.set = noop;
} else {
sharedpropertydefinition.get = userdef.get
? shouldcache && userdef.cache !== false
? createcomputedgetter(key)
: creategetterinvoker(userdef.get)
: noop;
sharedpropertydefinition.set = userdef.set || noop;
}
if (sharedpropertydefinition.set === noop) {
sharedpropertydefinition.set = function () {
warn(
("computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
object.defineproperty(target, key, 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 = function watcher (
    vm,
    exporfn,
    cb,
    options,
    isrenderwatcher
  ) {
    this.vm = vm;
  ......
    this.cb = cb;//触发函数
    this.id = ++uid$2; // uid for batching
    this.active = true;
    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 = function run () {
    if (this.active) {
      var value = this.get();
      if (
        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) ||
        this.deep
      ) {
        // set new value
        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);
        }
      }
    }
  };
  /**
   * evaluate the getter, and re-collect dependencies.
   */
  watcher.prototype.get = function get () {
    pushtarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleerror(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw 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
  };

  /**
   * subscriber interface.
   * will be called when a dependency changes.
   * 在方法中调用watcher的run方法
   */
  watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queuewatcher(this);//该方法最终也会调用run方法
    }
  };
  /**
   * depend on all deps collected by this watcher.会调用new dep的depend方法,dep的depend会调用watcher的adddep方法
   */
  watcher.prototype.depend = function depend () {
    var i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  };
  /**
   * add a dependency to this directive.
   */
  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);
      }
    }
  };