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 相关文章推荐
js中更短的 Array 类型转换
Oct 30 Javascript
有关于JS辅助函数inherit()的问题
Apr 07 Javascript
枚举的实现求得1-1000所有出现1的数字并计算出现1的个数
Sep 10 Javascript
JavaScript获得url所有参数键值表的方法
Mar 21 Javascript
JQuery选中checkbox方法代码实例(全选、反选、全不选)
Apr 27 Javascript
详解jQuery中的empty、remove和detach
Apr 11 Javascript
jquery配合.NET实现点击指定绑定数据并且能够一键下载
Oct 28 Javascript
微信小程序获取手机号授权用户登录功能
Nov 09 Javascript
详解处理Vue单页面应用SEO的另一种思路
Nov 09 Javascript
JavaScript ES2019中的8个新特性详解
Feb 20 Javascript
vue项目中定义全局变量、函数的几种方法
Nov 08 Javascript
javascript实现文字跑马灯效果
Jun 18 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
php 静态变量的初始化
2009/11/15 PHP
PHP中if和or运行效率对比
2014/12/12 PHP
PHP正则表达式入门教程(推荐)
2016/05/18 PHP
PHP数组编码gbk与utf8互相转换的两种方法
2016/09/01 PHP
php自定义时间转换函数示例
2016/12/07 PHP
详解thinkphp实现excel数据的导入导出(附完整案例)
2016/12/29 PHP
PHP实现链式操作的三种方法详解
2017/11/16 PHP
yii2 url重写并隐藏index.php方法
2018/12/10 PHP
Javascript延迟执行实现方法(setTimeout)
2010/12/30 Javascript
JavaScript 图像动画的小demo
2012/05/23 Javascript
JQuery解析HTML、JSON和XML实例详解
2014/03/29 Javascript
js如何判断输入字符串长度
2015/12/16 Javascript
详解nodejs 文本操作模块-fs模块(三)
2016/12/22 NodeJs
基于JS实现翻书效果的页面切换样式
2017/02/16 Javascript
$.browser.msie 为空或不是对象问题的多种解决方法
2017/03/19 Javascript
原生JS实现层叠轮播图
2017/05/17 Javascript
php main 与 iframe 相互通讯类(js+php同域/跨域)
2017/09/14 Javascript
基于vue 动态加载图片src的解决方法
2018/02/05 Javascript
微信小程序实现弹出菜单
2018/07/19 Javascript
关于vue利用postcss-pxtorem进行移动端适配的问题
2019/11/20 Javascript
Vue实现验证码功能
2019/12/03 Javascript
vue.config.js中配置Vue的路径别名的方法
2020/02/11 Javascript
Vue单文件组件开发实现过程详解
2020/07/30 Javascript
JavaScript中展开运算符及应用的实例代码
2021/01/14 Javascript
详细讲解用Python发送SMTP邮件的教程
2015/04/29 Python
Django中在xadmin中集成DjangoUeditor过程详解
2019/07/24 Python
详解Python实现进度条的4种方式
2020/01/15 Python
pytorch中使用cuda扩展的实现示例
2020/02/12 Python
k-means 聚类算法与Python实现代码
2020/06/01 Python
基于python实现计算两组数据P值
2020/07/10 Python
浅析关于Keras的安装(pycharm)和初步理解
2020/10/23 Python
奥地利购买珠宝和手表网站:ELLA JUWELEN
2019/09/03 全球购物
html+css实现分层金字塔的实例
2021/06/02 HTML / CSS
《堡垒之夜》联动《刺客信条》 4月7日正式上线
2022/04/06 其他游戏
Python+DeOldify实现老照片上色功能
2022/06/21 Python
html,css,javascript是怎样变成页面的
2023/05/07 HTML / CSS