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


Posted in Javascript onDecember 27, 2019

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

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);
   }
  }
 };

总结

以上所述是小编给大家介绍的vue中watch和computed为什么能监听到数据的改变以及不同之处,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
用JavaScript实现单继承和多继承的简单方法
Mar 29 Javascript
js 无提示关闭浏览器页面的代码
Mar 09 Javascript
深入理解JavaScript作用域和作用域链
Oct 21 Javascript
js面向对象设计用{}好还是function(){}好(构造函数)
Oct 23 Javascript
JS 加入收藏夹的代码(主流浏览器通用)
May 13 Javascript
jQuery Mobile的loading对话框显示/隐藏方法分享
Nov 26 Javascript
js限制checkbox选中个数以限制六个为例
Jul 15 Javascript
JavaScript中的style.cssText使用教程
Nov 06 Javascript
node.js开机自启动脚本文件
Dec 24 Javascript
JavaScript实现强制重定向至HTTPS页面
Jun 10 Javascript
javascript 实现动态侧边栏实例详解
Nov 11 Javascript
详解VueJs前后端分离跨域问题
May 24 Javascript
React中使用UMEditor的方法示例
Dec 27 #Javascript
node.js express捕获全局异常的三种方法实例分析
Dec 27 #Javascript
JavaScript中变量提升机制示例详解
Dec 27 #Javascript
vue 中 elment-ui table合并上下两行相同数据单元格
Dec 26 #Javascript
Vue使用虚拟dom进行渲染view的方法
Dec 26 #Javascript
node.js Promise对象的使用方法实例分析
Dec 26 #Javascript
js回调函数仿360开机
Dec 26 #Javascript
You might like
zen_cart实现支付前生成订单的方法
2016/05/06 PHP
PHP实现二维数组中的查找算法小结
2018/06/09 PHP
浅谈laravel框架与thinkPHP框架的区别
2019/10/23 PHP
js this函数调用无需再次抓获id,name或标签名
2014/03/03 Javascript
js实现获取当前时间是本月第几周的方法
2015/08/11 Javascript
HTML页面,测试JS对C函数的调用简单实例
2016/08/09 Javascript
详解JavaScript 中getElementsByName在IE中的注意事项
2017/02/21 Javascript
微信小程序调用PHP后台接口 解析纯html文本
2017/06/13 Javascript
简单实现js进度条加载效果
2020/03/25 Javascript
JS正则表达式完美实现身份证校验功能
2017/10/18 Javascript
Vue-Router模式和钩子的用法
2018/02/28 Javascript
vue使用jsonp抓取qq音乐数据的方法
2018/06/21 Javascript
vue实现codemirror代码编辑器中的SQL代码格式化功能
2019/08/27 Javascript
TypeScript高级用法的知识点汇总
2019/12/17 Javascript
js如何验证密码强度
2020/03/18 Javascript
[01:12:27]EG vs Secret 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
[41:05]Serenity vs Pain 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Java实现的执行python脚本工具类示例【使用jython.jar】
2018/03/29 Python
python框架中flask知识点总结
2018/08/17 Python
对python产生随机的二维数组实例详解
2018/12/13 Python
Python Pandas 转换unix时间戳方式
2019/12/07 Python
django model的update时auto_now不被更新的原因及解决方式
2020/04/01 Python
Python常用数据分析模块原理解析
2020/07/20 Python
Pycharm-community-2020.2.3 社区版安装教程图文详解
2020/12/08 Python
美国牛仔品牌:True Religion
2018/11/16 全球购物
创先争优活动个人总结
2015/03/04 职场文书
2015年护士节慰问信
2015/03/23 职场文书
教师节倡议书2015
2015/04/27 职场文书
有关西游记的读书笔记
2015/06/25 职场文书
高三教师工作总结2015
2015/07/21 职场文书
幼儿园园长新年寄语
2015/08/17 职场文书
小学英语教学经验交流材料
2015/11/02 职场文书
辞职申请书范本
2019/05/20 职场文书
nginx结合openssl实现https的方法
2021/07/25 Servers
简单聊一聊SQL注入及防止SQL注入
2022/03/23 MySQL
浅谈音视频 pts dts基本概念及理解
2022/08/05 数码科技