vue的计算属性computed的原理和监听属性watch的原理(新) ...

打印 上一主题 下一主题

主题 1599|帖子 1599|积分 4797

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
侦听属性watch的原理

重要实现方式:当定义一个侦听属性时,Vue会在内部创建一个Watcher实例来监视指定的数据。这个Watcher实例会在初始化时获取初始值,并将自身添加到数据的依赖列表中。当被侦听的数据发生厘革时,Watcher实例会被通知,并触发相应的回调函数。
  1. 1.侦听属性的初始化
  2. 在Vue实例化(initMixin(vue)的_init)时,会对侦听器进行初始化在initState中调用initWatch。在initWatch中Watch会对数组进行遍历处理,然后才调用createWatcher,通过原型方法$watch传入处理参数创建一个观察者收集依赖变化。
  3. // src/state.js
  4. // 统一初始化数据的方法
  5. export function initState(vm) {
  6.   // 获取传入的数据对象
  7.   const opts = vm.$options;
  8.   if (opts.watch) {
  9.     //侦听属性初始化
  10.     initWatch(vm);
  11.   }
  12. }
  13. // 初始化watch
  14. function initWatch(vm) {
  15.   let watch = vm.$options.watch;
  16.   for (let k in watch) {
  17.     const handler = watch[k]; //用户自定义watch的写法可能是数组 对象 函数 字符串
  18.     if (Array.isArray(handler)) {
  19.       // 如果是数组就遍历进行创建
  20.       handler.forEach((handle) => {
  21.         createWatcher(vm, k, handle);
  22.       });
  23.     } else {
  24.       createWatcher(vm, k, handler);
  25.     }
  26.   }
  27. }
  28. // 创建watcher的核心
  29. function createWatcher(vm, exprOrFn, handler, options = {}) {
  30.   if (typeof handler === "object") {
  31.     options = handler; //保存用户传入的对象
  32.     handler = handler.handler; //这个代表真正用户传入的函数
  33.   }
  34.   if (typeof handler === "string") {
  35.     //   代表传入的是定义好的methods方法
  36.     handler = vm[handler];
  37.   }
  38.   //   调用vm.$watch创建用户watcher
  39.   return vm.$watch(exprOrFn, cb, options);
  40. }
复制代码
2.$watch 

  1. //  src/state.js
  2. import Watcher from "./observer/watcher";
  3. Vue.prototype.$watch = function (exprOrFn, cb, options) {
  4.   const vm = this;
  5.   //  user: true 这里表示是一个用户watcher
  6.   let watcher = new Watcher(vm, exprOrFn, cb, { ...options, user: true });
  7.   // 如果有immediate属性 代表需要立即执行回调
  8.   if (options.immediate) {
  9.     handler(); //如果立刻执行watch中的回调函数
  10.   }
  11. };
复制代码
3.Watcher
  1. // src/observer/watcher.js
  2. import { pushTarget, popTarget } from "./dep";
  3. // 全局变量id  每次new Watcher都会自增
  4. let id = 0;
  5. export default class Watcher {
  6.   constructor(vm, exprOrFn, cb, options) {
  7.     this.vm = vm;
  8.     this.exprOrFn = exprOrFn;
  9.     this.cb = cb; //回调函数 比如在watcher更新之前可以执行beforeUpdate方法
  10.     this.options = options; //额外的选项 true代表渲染watcher
  11.     this.id = id++; // watcher的唯一标识
  12.     this.deps = []; //存放dep的容器
  13.     this.depsId = new Set(); //用来去重dep
  14.     this.user = options.user; //标识用户watcher
  15.     // 如果表达式是一个函数
  16.     if (typeof exprOrFn === "function") {
  17.       this.getter = exprOrFn;
  18.     }
  19.     // 实例化就会默认调用get方法
  20.     this.get();
  21.   }
  22.   get() {
  23.     pushTarget(this); // 在调用方法之前先把当前watcher实例推到全局Dep.target上
  24.     this.getter(); //如果watcher是渲染watcher 那么就相当于执行  vm._update(vm._render()) 这个方法在render函数执行的时候会取值 从而实现依赖收集
  25.     popTarget(); // 在调用方法之后把当前watcher实例从全局Dep.target移除
  26.   },
  27.   set(newValue) {
  28.       if (newValue === value) return;
  29.       // 如果赋值的新值也是一个对象 需要观测
  30.       observe(newValue);
  31.       value = newValue;
  32.       dep.notify(); // 通知渲染watcher去更新--派发更新 },
  33. }     
  34.   //把dep放到deps里面 同时保证同一个dep只被保存到watcher一次  同样的  同一个watcher也只会保存在dep一次
  35.   addDep(dep) {
  36.     let id = dep.id;
  37.     if (!this.depsId.has(id)) {
  38.       this.depsId.add(id);
  39.       this.deps.push(dep);
  40.       //   直接调用dep的addSub方法  把自己--watcher实例添加到dep的subs容器里面
  41.       dep.addSub(this);
  42.     }
  43.   }
  44.   //   这里简单的就执行以下get方法  之后涉及到计算属性就不一样了
  45.   update() {
  46.     this.get();
  47.   }
  48.   
  49.   //run方法会在监听的数据发生改变时,set劫持的数据通知渲染watcher去更新--派发更新,在这个时候执行 run()
  50.   run() {
  51.     const newVal = this.get(); //新值
  52.     const oldVal = this.value; //老值
  53.     this.value = newVal; //现在的新值将成为下一次变化的老值
  54.     if (this.user) {
  55.       // 如果两次的值不相同  或者值是引用类型 因为引用类型新老值是相等的 他们是指向同一引用地址
  56.       if (newVal !== oldVal || isObject(newVal)) {
  57.         this.cb.call(this.vm, newVal, oldVal);
  58.       }
  59.     } else {
  60.       // 渲染watcher
  61.       this.cb.call(this.vm);
  62.     }
  63.   }
  64. }
复制代码


计算属性computed的原理

具体原理如下:

  • 在Vue实例化时,会对计算属性举行初始化。计算属性的定义包罗一个计算函数和一个缓存属性。
  • 当计算属性被访问时,会触发计算函数的实行。在计算函数中,可以访问其他响应式数据。
  • 计算函数会根据依赖的响应式数据举行计算,并返回计算结果。
  • 计算属性会将计算结果缓存起来,下次访问时直接返回缓存的值。
  • 当依赖的响应式数据发生厘革时,计算属性会被标记为"dirty"(脏),下次访问时会重新计算并更新缓存的值。
通过计算属性,我们可以将复杂的逻辑封装成一个属性,并在模板中直接使用。计算属性会自动追踪依赖的数据,并在必要时举行更新,提供了一种轻便和高效的方式来处理衍生数据。
留意: 计算属性实用于那些依赖其他响应式数据的场景,而不实用于必要举行异步操纵或有副作用的场景。对于这些情况,可以使用侦听器(watcher)或使用methods来处理。
接上文,在Vue实例化(initMixin(vue)_init)时,不仅会对侦听器举行初始化同时也会对计算属性举行初始化。
  1. 在initComputed函数中,遍历计算属性对象,为每个计算属性创建一个Watcher实例,并将其存储在vm._computedWatchers中。
  2. // src/state.js
  3. export function initState(vm) {
  4.   // 获取传入的数据对象
  5.   const opts = vm.$options;
  6.   if (opts.watch) {
  7.     //侦听属性初始化
  8.     initWatch(vm);
  9.   }
  10.   if(opts.computed) {
  11.     initComputed(vm)
  12.   }
  13. }
  14. function initComputed(vm) {
  15.   const computed = vm.$options.computed;
  16.   //用来存放计算watcher
  17.   const watchers = vm._computedWatchers = Object.create(null);
  18.   for (const key in computed) {
  19.      //获取用户定义的计算属性
  20.     const userDef = computed[key];
  21.     //创建计算属性watcher使用
  22.     const getter = typeof userDef === 'function' ? userDef : userDef.get;
  23.     //-vm:计算属性所属的Vue实例。
  24.     //- getter:计算属性的获取函数,用于计算计算属性的值。
  25.     //- noop:一个空操作函数,作为回调函数的占位符。  
  26.     //- computedWatcherOptions:一个包含特定于计算属性观察者的选项的对象。
  27.    // watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
  28.    // 创建计算watcher lazy设置为true
  29.    watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true });
  30.     if (!(key in vm)) {
  31.       defineComputed(vm, key, userDef);
  32.     }
  33.   }
  34. }
复制代码

2.对计算属性举行属性挟制

defineComputed 方法重要是重新定义计算属性,实在最重要的是挟制get方法也就是计算属性依赖的值。
为啥要挟制呢? 因为我们必要根据依赖值是否发生厘革来判定计算属性是否必要重新计算
createComputedGetter方法就是判定计算属性依赖的值是否厘革的焦点了 我们在计算属性创建的Watcher增加dirty标志位,如果标志变为true代表必要调用watcher.evaluate来举行重新计算了
  1. function defineComputed(target, key, userDef) {
  2. // 定义普通对象用来劫持计算属性
  3.   const sharedPropertyDefinition = {
  4.     enumerable: true,
  5.     configurable: true,
  6.     get: noop,
  7.     set: noop
  8.   };
  9.   // 重新定义计算属性 对get和set劫持
  10.   if (typeof userDef === 'function') {
  11.   // 如果是一个函数 需要手动赋值到get上
  12.     sharedPropertyDefinition.get = createComputedGetter(key);
  13.   } else {
  14.   // 利用Object.defineProperty来对计算属性的get和set进行劫持
  15.     sharedPropertyDefinition.get = createComputedGetter(key);
  16.     sharedPropertyDefinition.set = userDef.set;
  17.   }
  18.   
  19.   Object.defineProperty(target, key, sharedPropertyDefinition);
  20. }
  21. // 在getter函数中,首先获取对应的Watcher实例,然后判断是否需要重新计算计算属性的值。如果需要重新计算,则调用watcher.evaluate()进行计算,并在需要时进行依赖收集。最后,返回计算属性的值
  22. function createComputedGetter(key) {
  23.   return function computedGetter() {
  24.   //获取对应的计算属性watcher
  25.     const watcher = this._computedWatchers && this._computedWatchers[key];
  26.    
  27.     if (watcher) {
  28.     //计算属性取值的时候 如果是脏的 需要重新求值
  29.       if (watcher.dirty) {
  30.         watcher.evaluate();
  31.       }
  32.      // 如果Dep还存在target 这个时候一般为渲染watcher 计算属性依赖的数据也需要收集
  33.       if (Dep.target) {
  34.         watcher.depend();
  35.       }
  36.       
  37.       return watcher.value;
  38.     }
  39.   };
  40. }
复制代码

计算属性computed和侦听属性watch照旧有很大区别的:
计算属性一般用在必要对依赖项举行计算而且可以缓存下来,当依赖项厘革会自动实行计算属性的逻辑,一般用在模板里面较多。
watch属性用法是对某个响应式的值举行观察(也可以观察计算属性的值)一旦厘革之后就可以实行本身定义的方法

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表