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在一段文字中的光标处插入其他文字
Aug 26 Javascript
JS遮罩层效果 兼容ie firefox jQuery遮罩层
Jul 26 Javascript
jQuery 三击事件实现代码
Sep 11 Javascript
扩展IE中一些不兼容的方法如contains、startWith等等
Jan 09 Javascript
jquery鼠标放上去显示悬浮层即弹出定位的div层
Apr 25 Javascript
jQuery队列操作方法实例
Jun 11 Javascript
js操作XML文件的实现方法兼容IE与FireFox
Jun 25 Javascript
Vue.js实战之使用Vuex + axios发送请求详解
Apr 04 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
Jan 12 Javascript
ES6中Set和Map数据结构,Map与其它数据结构互相转换操作实例详解
Feb 28 Javascript
微信小程序 自定义复选框实现代码实例
Sep 04 Javascript
基于脚手架创建Vue项目实现步骤详解
Aug 03 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删除二维数组中的重复值方法
2018/03/12 PHP
PHP获取当前系统时间的方法小结
2018/10/03 PHP
Array对象方法参考
2006/10/03 Javascript
js通过googleAIP翻译PHP系统的语言配置的实现代码
2011/10/17 Javascript
onbeforeunload与onunload事件异同点总结
2013/06/24 Javascript
iframe的父子窗口之间的对象相互调用基本用法
2013/09/03 Javascript
js获取客户端外网ip的简单实例
2013/11/21 Javascript
jQuery Uploadify 上传插件出现Http Error 302 错误的解决办法
2015/12/12 Javascript
js实现页面跳转的五种方法推荐
2016/03/10 Javascript
js HTML5手机刮刮乐代码
2020/09/29 Javascript
jQuery简单绑定单个事件的方法示例
2017/06/10 jQuery
vue给input file绑定函数获取当前上传的对象完美实现方法
2017/12/15 Javascript
全面介绍vue 全家桶和项目实例
2017/12/27 Javascript
vue使用Proxy实现双向绑定的方法示例
2019/03/20 Javascript
微信小程序实现音乐播放器
2019/11/20 Javascript
微信小程序实现录音功能
2019/11/22 Javascript
vue实现pdf文档在线预览功能
2019/11/26 Javascript
JavaScript前端开发时数值运算的小技巧
2020/07/28 Javascript
python简单猜数游戏实例
2015/07/09 Python
python 顺时针打印矩阵的超简洁代码
2018/11/14 Python
Django 源码WSGI剖析过程详解
2019/08/05 Python
IntelliJ 中配置 Anaconda的过程图解
2020/06/01 Python
Python脚本调试工具安装过程
2021/01/11 Python
匡威德国官网:Converse德国
2019/01/26 全球购物
计算 s=(x*y)1/2,用两个宏定义来实现
2016/08/11 面试题
竞聘上岗演讲稿范文
2014/01/10 职场文书
体育系毕业生求职自荐信
2014/04/16 职场文书
2014年班主任自我评价范文
2014/04/23 职场文书
2014年党的群众路线整改措施思想汇报
2014/10/12 职场文书
2015年党员干部承诺书
2015/01/21 职场文书
2015年乡镇民政工作总结
2015/05/13 职场文书
通讯稿格式及范文
2015/07/22 职场文书
辞职离别感言
2015/08/04 职场文书
阿里云服务器Ubuntu 20.04上安装Odoo 15
2022/05/20 Servers
mysql sock文件存储了什么信息
2022/07/15 MySQL