精华内容
下载资源
问答
  • Vue computed 实现原理

    千次阅读 2019-03-03 16:35:47
    Vue computed 实现原理 Vue 2.5.17 1、Vue 在初始化的时候会 initState ,这个方法会初始化 props methods data computed watch // 4069 vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm); ...

    Vue computed 实现原理

    Vue 2.5.17

    • 1、Vue 在初始化的时候会 initState ,这个方法会初始化 props methods data computed watch
    // 4069
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');
    
    // 3303
    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);
      } else {
        observe(vm._data = {}, true /* asRootData */);
      }
      if (opts.computed) { initComputed(vm, opts.computed); }
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
      }
    }
    
    • 2、在初始化 computed 的时候首先会拿到对应的 get 方法。其次在非 SSR 下会创建对应的 watcher。随后对其进行初始化
    // 3422
    var computedWatcherOptions = { lazy: true };
    
    function initComputed(vm, computed) {
      var watchers = (vm._computedWatchers = Object.create(null))
      var isSSR = isServerRendering()
      for (var key in computed) {
        var userDef = computed[key]
        var getter = typeof userDef === 'function' ? userDef : userDef.get
        if ('development' !== 'production' && getter == null) {
          warn('Getter is missing for computed property "' + key + '".', vm)
        }
        if (!isSSR) {
          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)
          }
        }
      }
    }
    
    • 3、随后经过一系列的判断对其进行 Object.defineProperty 初始化。非 SSR 下会调用 createComputedGetter 方法对原始的 getter 进行加工
    // 3286
    var sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    };
    
    // 3465
    function defineComputed(target, key, userDef) {
      var shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set ? userDef.set : noop
      }
      if ('development' !== 'production' && sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function() {
          warn('Computed property "' + key + '" was assigned to but it has no setter.', this)
        }
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    
    • 4、我们来看一下 createComputedGetter 是如何加工的,在获取计算属性的值时候其实是拿的 watcher.value。在这里我们先不管 evaluate 和 depend 是干什么的我们先看一下 watcher 如何定义
    // 3489
    function createComputedGetter (key) {
      return function computedGetter () {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate();
          }
          if (Dep.target) {
            watcher.depend();
          }
          return watcher.value
        }
      }
    }
    
    • 5、watcher 的 value 实际上是调用的 watcher.get 方法。这个方法有调用计算属性对应的 getter 方法。但我们在定义 watcher 的时候会传入 { lazy: true} ,这时候我们在定义 watcher 的时候不会触发 get 方法,而反过来看步骤 4,当 dirty = true 的时候首先会调用 watcher.evaluate()
    // 710
    Dep.target = null;
    var targetStack = [];
    function pushTarget (_target) {
      if (Dep.target) { targetStack.push(Dep.target); }
      Dep.target = _target;
    }
    function popTarget () {
      Dep.target = targetStack.pop();
    }
    
    
    // 3082
    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      // ...
      this.dirty = this.lazy;
      // ...
      this.value = this.lazy ? undefined : this.get()
    }
    
    // 3235
    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
    }
    
    • 6、当调用了 evaluate 方法就会计算一次 value,并将 dirty 置为 false。我们不妨设想一下:当我们的一开始的时候 dirty = true 会计算 value 随后 dirty = false,当我们的依赖项发生变化的时候将 dirty = true,这时候我们获取 value 的时候会重新计算。那么我们依赖项发生变化的时候是如何将对应的 dirty 设置为 true 的?
    // 3246
    Watcher.prototype.evaluate = function evaluate () {
      this.value = this.get();
      this.dirty = false;
    };
    
    • 7、我们在初始化 data 或者 props 的时候都会触发 defineReactive 方法
    // 966
    function defineReactive(obj, key, val, customSetter, shallow) {
      // ...
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          var value = getter ? getter.call(obj) : val
          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 ('development' !== 'production' && customSetter) {
            customSetter()
          }
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = !shallow && observe(newVal)
          dep.notify()
        }
      })
    }
    
    
    • 8、在我们调用 watcher.get 的时候会调用 pushTarget 方法,将 Dep.target 设置为当前的作用域(步骤 5 中的代码)。这时候我们调用计算属性的 getter。如果我们的计算属性依赖了 data 数据或者 prop 数据,就会触发其对应的 getter 方法。这时候就会触发依赖收集。
    dep.depend()
    
    // 693
    Dep.prototype.depend = function depend() {
      if (Dep.target) {
        Dep.target.addDep(this)
      }
    }
    
    // 3169
    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)
        }
      }
    }
    
    // 684
    Dep.prototype.addSub = function addSub(sub) {
      this.subs.push(sub)
    }
    
    
    • 9、我们在改变对应的依赖时候触发其 set 方法,dep.notify() 方法最终通知改变 dirty = true,从而重新计算 value
    dep.notify()
    
    // 699
    Dep.prototype.notify = function notify() {
      // stabilize the subscriber list first
      var subs = this.subs.slice()
      for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
      }
    }
    
    // 3200
    Watcher.prototype.update = function update() {
      /* istanbul ignore else */
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    

    运行步骤

    • 初始化 cumputed 的时候会先初始化 props、data 等
    • 初始化 cumputed(!SSR) 时候,会添加其对应的 watcher(创建的时候不会触发 get 方法)。
    • 我们在获取计算属性的值的时候,如果 dirty = true 会调用 watcher.get 方法重新计算。随后 dirty = false
    • 在调用 get 方法的时候启动依赖收集,将 watcher 添加到对应依赖的 sub 里
    • 在依赖项发生变化的时候会调用 set 方法,最终会调用 watcher.update 方法将 dirty = true。这时候获取计算属性的时候会重新调用 watcher.get 方法

    总结

    • 我们在依赖项没有变化的时候,一直使用的是 watcher.value 也就是我们所说的缓存
    • 依赖项发生变化的时候重新计算一次 watcher.value

    补充

    • 10、我们在 computed 收集依赖完成之后,就会调用 cleanupDeps 方法,步骤 5。将 newDeps 中的依赖转存到 deps 中
    // 3176
    Watcher.prototype.cleanupDeps = function cleanupDeps () {
        var this$1 = this;
    
      var i = this.deps.length;
      while (i--) {
        var dep = this$1.deps[i];
        if (!this$1.newDepIds.has(dep.id)) {
          dep.removeSub(this$1);
        }
      }
      var tmp = this.depIds;
      this.depIds = this.newDepIds;
      this.newDepIds = tmp;
      this.newDepIds.clear();
      tmp = this.deps;
      this.deps = this.newDeps;
      this.newDeps = tmp;
      this.newDeps.length = 0;
    };
    
    // 689
    Dep.prototype.removeSub = function removeSub (sub) {
      remove(this.subs, sub);
    };
    
    • 11、我们通常一个 computed 会依赖另外一个 computed。假设 computedB 依赖了 computedA。当我们获取 computedB 的时候第一次调用 get。这时候(步骤 5) Dep.target === computedB -> computedB.getter -> computedA.getter 就会开启依赖收集,执行 computedA 的 watcher.depend()(步骤 4)。其中的 this$1.deps 就是 computedA 的依赖,这个方法将 computedB 的 watcher 添加到 computedA 的依赖中
    // 3254
    Watcher.prototype.depend = function depend () {
        var this$1 = this;
    
      var i = this.deps.length;
      while (i--) {
        this$1.deps[i].depend();
      }
    };
    
    • 12、这时候 computedA 的依赖添加了 computedA 的 watcher 和 computedB 的 watcher。computedB 的依赖添加 computedB 的 watcher。相关依赖触发了 set 会通知到对应的 computed watcher,从而触发重新计算
    • 13、当我们组件注销的时候同时会触发对应的注销函数,清除对应的 watcher
    // 2708
    if (vm._watcher) {
      vm._watcher.teardown();
    }
    var i = vm._watchers.length;
    while (i--) {
      vm._watchers[i].teardown();
    }
    
    // 3266
    Watcher.prototype.teardown = function teardown () {
        var this$1 = this;
    
      if (this.active) {
        // remove self from vm's watcher list
        // this is a somewhat expensive operation so we skip it
        // if the vm is being destroyed.
        if (!this.vm._isBeingDestroyed) {
          remove(this.vm._watchers, this);
        }
        var i = this.deps.length;
        while (i--) {
          this$1.deps[i].removeSub(this$1);
        }
        this.active = false;
      }
    };
    
    • 14、步骤9 中的 update 方法,在不同情况下会触发不同的方法,这里我们就不一一解释了。感兴趣的可以自己去看看
    展开全文
  • 浅谈 Vuecomputed 实现原理

    千次阅读 2018-10-10 19:05:26
    虽然目前的技术栈已由 Vue 转到了 React,但从之前使用 Vue 开发的多个项目实际经历来看还是非常愉悦的,Vue 文档清晰规范,api 设计简洁高效,对前端开发人员友好,上手快,甚至个人认为在很多场景使用 Vue 比React...

    虽然目前的技术栈已由 Vue 转到了 React,但从之前使用 Vue 开发的多个项目实际经历来看还是非常愉悦的,Vue 文档清晰规范,api 设计简洁高效,对前端开发人员友好,上手快,甚至个人认为在很多场景使用 Vue 比React 开发效率更高,之前也有断断续续研读过 Vue 的源码,但一直没有梳理总结,所以在此做一些技术归纳同时也加深自己对 Vue 的理解,那么今天要写的便是 Vue 中最常用到的 API 之一 computed 的实现原理。

    基本介绍

    话不多说,一个最基本的例子如下:

    {{fullName}}
    
    new Vue({
        data: {
            firstName: 'Xiao',
            lastName: 'Ming'
        },
        computed: {
            fullName: function () {
                return this.firstName + ' ' + this.lastName
            }
        }
    })
    

    Vue 中我们不需要在 template 里面直接计算 {{this.firstName + ‘ ‘ + this.lastName}},因为在模版中放入太多声明式的逻辑会让模板本身过重,尤其当在页面中使用大量复杂的逻辑表达式处理数据时,会对页面的可维护性造成很大的影响,而 computed 的设计初衷也正是用于解决此类问题。

    对比侦听器 watch

    当然很多时候我们使用 computed 时往往会与 Vue 中另一个 API 也就是侦听器 watch 相比较,因为在某些方面它们是一致的,都是以 Vue 的依赖追踪机制为基础,当某个依赖数据发生变化时,所有依赖这个数据的相关数据或函数都会自动发生变化或调用。

    虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
    选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

    从 Vue 官方文档对 watch 的解释我们可以了解到,使用 watch 选项允许我们执行异步操作(访问一个 API)或高消耗性能的操作,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态,而这些都是计算属性无法做到的。

    下面还另外总结了几点关于 computed 和 watch 的差异:

    • computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm
      上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)
    • computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而
      watch 则是当数据发生变化便会调用执行函数
    • 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据;

    以上我们了解了 computed 和 watch 之间的一些差异和使用场景的区别,当然某些时候两者并没有那么明确严格的限制,最后还是要具体到不同的业务进行分析。

    原理分析

    言归正传,回到文章的主题 computed 身上,为了更深层次地了解计算属性的内在机制,接下来就让我们一步步探索 Vue 源码中关于它的实现原理吧。

    在分析 computed 源码之前我们先得对 Vue 的响应式系统有一个基本的了解,Vue 称其为非侵入性的响应式系统,数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图便会进行自动更新。
    在这里插入图片描述

    当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项时,Vue 将遍历此对象所有的属性,并使用
    Object.defineProperty 把这些属性全部转为 getter/setter,这些 getter/setter
    对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化,每个组件实例都有相应的 watcher
    实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher
    重新计算,从而致使它关联的组件得以更新。

    Vue 响应系统,其核心有三点:observe、watcher、dep:

    • observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持;
    • dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;
    • watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。

    对响应式系统有一个初步了解后,我们再来分析计算属性。 首先我们找到计算属性的初始化是在 src/core/instance/state.js 文件中的 initState 函数中完成的

    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      // computed初始化
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    

    调用了 initComputed 函数(其前后也分别初始化了 initData 和 initWatch )并传入两个参数 vm 实例和 opt.computed 开发者定义的 computed 选项,转到 initComputed 函数:

    const computedWatcherOptions = { computed: true }
    function initComputed (vm: Component, computed: Object) {
      // $flow-disable-line
      const watchers = vm._computedWatchers = Object.create(null)
      // computed properties are just getters during SSR
      const isSSR = isServerRendering()
    
      for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (process.env.NODE_ENV !== 'production' && 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      )
        }
    
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
          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)
          }
        }
      }
    }
    

    从这段代码开始我们观察这几部分:

    获取计算属性的定义 userDef 和 getter 求值函数

       const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
    

    定义一个计算属性有两种写法,一种是直接跟一个函数,另一种是添加 set 和 get 方法的对象形式,所以这里首先获取计算属性的定义 userDef,再根据 userDef 的类型获取相应的 getter 求值函数。

    计算属性的观察者 watcher 和消息订阅器 dep

    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions)
    

    这里的 watchers 也就是 vm._computedWatchers 对象的引用,存放了每个计算属性的观察者 watcher 实例(注:后文中提到的“计算属性的观察者”、“订阅者”和 watcher 均指代同一个意思但注意和 Watcher 构造函数区分),Watcher 构造函数在实例化时传入了 4 个参数:vm 实例、getter求值函数、noop 空函数、computedWatcherOptions 常量对象(在这里提供给 Watcher 一个标识 {computed:true} 项,表明这是一个计算属性而不是非计算属性的观察者,我们来到 Watcher 构造函数的定义:

    class Watcher {
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
      ) {
        if (options) {
          this.computed = !!options.computed
        } 
    
        if (this.computed) {
          this.value = undefined
          this.dep = new Dep()
        } else {
          this.value = this.get()
        }
      }
    
      get () {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e) {
    
        } finally {
          popTarget()
        }
        return value
      }
    
      update () {
        if (this.computed) {
          if (this.dep.subs.length === 0) {
            this.dirty = true
          } else {
            this.getAndInvoke(() => {
              this.dep.notify()
            })
          }
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      evaluate () {
        if (this.dirty) {
          this.value = this.get()
          this.dirty = false
        }
        return this.value
      }
    
      depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
    }
    

    为了简洁突出重点,这里我手动去掉了我们暂时不需要关心的代码片段。 观察 Watcher 的 constructor ,结合刚才讲到的 new Watcher 传入的第四个参数 {computed:true} 知道,对于计算属性而言 watcher 会执行 if 条件成立的代码 this.dep = new Dep(),而 dep 也就是创建了该属性的消息订阅器。

     export default class Dep {
          static target: ?Watcher;
          subs: Array<Watcher>;
        
          constructor () {
            this.id = uid++
            this.subs = []
          }
        
          addSub (sub: Watcher) {
            this.subs.push(sub)
          }
        
          depend () {
            if (Dep.target) {
              Dep.target.addDep(this)
            }
          }
        
          notify () {
            const subs = this.subs.slice()
            for (let i = 0, l = subs.length; i < l; i++) {
              subs[i].update()
            }
          }
        }
        Dep.target = null
    

    Dep 同样精简了部分代码,我们观察 Watcher 和 Dep 的关系,用一句话总结

    watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个
    watcher 更新。

    defineComputed 定义计算属性

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      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)
      }
    }
    

    因为 computed 属性是直接挂载到实例对象中的,所以在定义之前需要判断对象中是否已经存在重名的属性,defineComputed 传入了三个参数:vm实例、计算属性的 key 以及 userDef 计算属性的定义(对象或函数)。 然后继续找到 defineComputed 定义处:

    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      const shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : userDef
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set
          ? userDef.set
          : noop
      }
      if (process.env.NODE_ENV !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            'Computed property "${key}" was assigned to but it has no setter.',
            this
          )
        }
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    

    在这段代码的最后调用了原生 Object.defineProperty 方法,其中传入的第三个参数是属性描述符sharedPropertyDefinition,初始化为:

    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    

    随后根据 Object.defineProperty 前面的代码可以看到 sharedPropertyDefinition 的 get/set 方法在经过 userDef 和 shouldCache 等多重判断后被重写,当非服务端渲染时,sharedPropertyDefinition 的 get 函数也就是 createComputedGetter(key) 的结果,我们找到 createComputedGetter 函数调用结果并最终改写 sharedPropertyDefinition 大致呈现如下:

    sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
                watcher.depend()
                return watcher.evaluate()
            }
        },
        set: userDef.set || noop
    }
    

    当计算属性被调用时便会执行 get 访问函数,从而关联上观察者对象 watcher 然后执行 wather.depend() 收集依赖和 watcher.evaluate() 计算求值。

    分析完所有步骤,我们再来总结下整个流程:

    • 当组件初始化的时候,computed 和 data 会分别建立各自的响应系统,Observer遍历 data 中每个属性设置
      get/set 数据拦截
    • 初始化 computed 会调用 initComputed 函数
      • 注册一个 watcher 实例,并在内实例化一个 Dep 消息订阅器用作后续收集依赖(比如渲染函数的 watcher
        或者其他观察该计算属性变化的 watcher )
      • 调用计算属性时会触发其Object.defineProperty的get访问器函数
      • 调用 watcher.depend() 方法向自身的消息订阅器 dep 的 subs 中添加其他属性的 watcher
      • 调用 watcher 的 evaluate 方法(进而调用 watcher 的 get 方法)让自身成为其他 watcher
        的消息订阅器的订阅者,首先将 watcher 赋给 Dep.target,然后执行 getter
        求值函数,当访问求值函数里面的属性(比如来自 data、props 或其他 computed)时,会同样触发它们的 get访问器函数从而将该计算属性的 watcher 添加到求值函数中属性的 watcher 的消息订阅器 dep 中,当这些操作完成,最后关闭Dep.target 赋为 null 并返回求值函数结果。
    • 当某个属性发生变化,触发 set 拦截函数,然后调用自身消息订阅器 dep 的 notify 方法,遍历当前 dep 中保存着所有订阅者wathcer 的 subs 数组,并逐个调用 watcher 的 update 方法,完成响应更新。

    原文链接:https://mp.weixin.qq.com/s/igkif-J_BHd1q5mZ7TewCw

    展开全文
  • 继上一篇:深入理解Vue的watch实现原理及其实现方式 继续讲解Vuecomputed实现相对于watch和data来说比较难以理解,要真正的理解computed的工作方式,你需要深入理解Vue的双向数据绑定原理实现方式。如果你还不是...

    继上一篇:深入理解Vue的watch实现原理及其实现方式 继续讲解

    Vue的computed实现相对于watch和data来说比较难以理解,要真正的理解computed的工作方式,你需要深入理解Vue的双向数据绑定原理和实现方式。

    如果你还不是很理解推荐你先看此文章:

    彻底搞懂Vue针对数组和双向绑定(MVVM)的处理方式


    首先来看一波Vue中computed的使用方式:

    var vm = new Vue({
      data: { a: 1 },
      computed: {
        // 仅读取
        aDouble: function () {
          return this.a * 2
        },
        // 读取和设置
        aPlus: {
          get: function () {
            return this.a + 1
          },
          set: function (v) {
            this.a = v - 1
          }
        }
      }
    })
    vm.aPlus   // => 2
    vm.aPlus = 3
    vm.a       // => 2
    vm.aDouble // => 4复制代码

    计算属性的主要应用场景是代替模板内的表达式,或者data值的任何复杂逻辑都应该使用computed来计算,它有两大优势:

    1、逻辑清晰,方便于管理

    2、计算值会被缓存,依赖的data值改变时才会从新计算

    此文我们需要核心理解的是:

    1、computed是如何初始化,初始化之后干了些什么

    2、为何触发data值改变时computed会从新计算

    3、computed值为什么说是被缓存的呢,如何做的


    如果以上三个问题你都已知,你可以忽略下文了,若未知或一知半解,请抱着学习的态度看看别人的理解。

    备注:以下只是我的个人理解,并不保证绝对的正确性,若有问题欢迎指正

    以下大部分代码摘自Vue源码。


    如果你看到了这里,就当做你已经深入理解了Vue的MVVM原理及其实现方式。相关Vue的MVVM实现直接取自上一篇文章。

    Dep代码的实现:

    //标识当前的Dep id
    let uidep = 0
    class Dep{
    	constructor () {
    		this.id = uidep++
    		// 存放所有的监听watcher
        	this.subs = []
      	}
    
      	//添加一个观察者对象
      	addSub (Watcher) {
        	this.subs.push(Watcher)
      	}
      	//依赖收集
    	depend () {
    		//Dep.target 作用只有需要的才会收集依赖
    	    if (Dep.target) {
    	      Dep.target.addDep(this)
    	    }
    	}
    
    	// 调用依赖收集的Watcher更新
        notify () {
    	    const subs = this.subs.slice()
    	    for (let i = 0, l = subs.length; i < l; i++) {
    	      subs[i].update()
    	    }
      	}
    }
    
    Dep.target = null
    const targetStack = []
    
    // 为Dep.target 赋值
    function pushTarget (Watcher) {
    	if (Dep.target) targetStack.push(Dep.target)
      	Dep.target = Watcher
    }
    function popTarget () {
      Dep.target = targetStack.pop()
    }复制代码


    Watcher代码的实现:

    //去重 防止重复收集
    let uid = 0
    class Watcher{
    	constructor(vm,expOrFn,cb,options){
    		//传进来的对象 例如Vue
    		this.vm = vm
    		if (options) {
    	      this.deep = !!options.deep
    	      this.user = !!options.user
    	      this.lazy = !!options.lazy
    	    }else{
    	    	this.deep = this.user = this.lazy = false
    	    }
    	    this.dirty = this.lazy
    		//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
    		this.cb = cb
    		this.id = ++uid
    		this.deps = []
    	    this.newDeps = []
    	    this.depIds = new Set()
    	    this.newDepIds = new Set()
    		if (typeof expOrFn === 'function') {
    			//data依赖收集走此处
    	      	this.getter = expOrFn
    	    } else {
    	    	//watch依赖走此处
    	      	this.getter = this.parsePath(expOrFn)
    	    }
    		//设置Dep.target的值,依赖收集时的watcher对象
    		this.value = this.lazy ? undefined : this.get()
    	}
    
    	get(){
    		//设置Dep.target值,用以依赖收集
    	    pushTarget(this)
    	    const vm = this.vm
    	    //此处会进行依赖收集 会调用data数据的 get
    	    let value = this.getter.call(vm, vm)
    	    popTarget()
    	    return value
    	}
    
    	//添加依赖
      	addDep (dep) {
      		//去重
      		const id = dep.id
    	    if (!this.newDepIds.has(id)) {
    	      	this.newDepIds.add(id)
    	      	this.newDeps.push(dep)
    	      	if (!this.depIds.has(id)) {
    	      		//收集watcher 每次data数据 set
    	      		//时会遍历收集的watcher依赖进行相应视图更新或执行watch监听函数等操作
    	        	dep.addSub(this)
    	      	}
    	    }
      	}
    
      	//更新
      	update () {
      		if (this.lazy) {
          		this.dirty = true
        	}else{
        		this.run()
        	}
    	}
    
    	//更新视图
    	run(){
    		console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    		const value = this.get()
    		const oldValue = this.value
            this.value = value
    		if (this.user) {
    			//watch 监听走此处
                this.cb.call(this.vm, value, oldValue)
            }else{
            	//data 监听走此处
            	//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
    			this.cb.call(this.vm, value, oldValue)
            }
    	}
    
        //如果计算熟悉依赖的data值发生变化时会调用
        //案例中 当data.name值发生变化时会执行此方法
    	evaluate () {
    	    this.value = this.get()
    	    this.dirty = false
    	}
    	//收集依赖
    	depend () {
    	    let i = this.deps.length
    	    while (i--) {
    	      this.deps[i].depend()
    	    }
    	}
    
    	// 此方法获得每个watch中key在data中对应的value值
    	//使用split('.')是为了得到 像'a.b.c' 这样的监听值
    	parsePath (path){
    		const bailRE = /[^w.$]/
    	  if (bailRE.test(path)) return
    	  	const segments = path.split('.')
    	  	return function (obj) {
    		    for (let i = 0; i < segments.length; i++) {
    		      	if (!obj) return
    		      	//此处为了兼容我的代码做了一点修改	 
    		        //此处使用新获得的值覆盖传入的值 因此能够处理 'a.b.c'这样的监听方式
    		        if(i==0){
    		        	obj = obj.data[segments[i]]
    		        }else{
    		        	obj = obj[segments[i]]
    		        }
    		    }
    		    return obj
    		 }
    	}
    }复制代码

    在Watcher中对于computed来说核心注意点是以下方法:

    //如果计算熟悉依赖的data值发生变化时会调用
    //案例中 当data.name值发生变化时会执行此方法
    evaluate () {
        this.value = this.get()
        this.dirty = false
    }复制代码

    当computed中用到的data值发生变化时,视图更新调用computed值时会从新执行,获得新的计算属性值。


    Observer代码实现

    class Observer{
    	constructor (value) {
    	    this.value = value
    	    // 增加dep属性(处理数组时可以直接调用)
    	    this.dep = new Dep()
    	    //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,
    	    //处理数组是也可直接获取Observer对象
    	    def(value, '__ob__', this)
    	    if (Array.isArray(value)) {
    	    	//这里只测试对象
    	    } else {
    	    	//处理对象
    	      	this.walk(value)
    	    }
    	}
    
    	walk (obj) {
        	const keys = Object.keys(obj)
        	for (let i = 0; i < keys.length; i++) {
        		//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理
        		if(keys[i]=='__ob__') return;
          		defineReactive(obj, keys[i], obj[keys[i]])
        	}
      	}
    }
    //数据重复Observer
    function observe(value){
    	if(typeof(value) != 'object' ) return;
    	let ob = new Observer(value)
      	return ob;
    }
    // 把对象属性改为getter/setter,并收集依赖
    function defineReactive (obj,key,val) {
      	const dep = new Dep()
      	//处理children
      	let childOb = observe(val)
      	Object.defineProperty(obj, key, {
        	enumerable: true,
        	configurable: true,
        	get: function reactiveGetter () {
        		console.log(`调用get获取值,值为${val}`)
          		const value = val
          		if (Dep.target) {
    	        	dep.depend()
    		        if (childOb) {
    		          	childOb.dep.depend()
    		        }
    	      	}
          		return value
    	    },
    	    set: function reactiveSetter (newVal) {
    	    	console.log(`调用了set,值为${newVal}`)
    	      	const value = val
    	       	val = newVal
    	       	//对新值进行observe
    	      	childOb = observe(newVal)
    	      	//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新
    	      	dep.notify()
    	    }
      })
    }
    //辅助方法
    function def (obj, key, val) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: true,
        writable: true,
        configurable: true
      })
    }复制代码


    此文的重点Computed代码实现:

    //空函数
    const noop = ()=>{}
    // computed初始化的Watcher传入lazy: true就会触发Watcher中的dirty值为true
    const computedWatcherOptions = { lazy: true }
    //Object.defineProperty 默认value参数
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    // 初始化computed
    class initComputed {
    	constructor(vm, computed){
    		//新建存储watcher对象,挂载在vm对象执行
    		const watchers = vm._computedWatchers = Object.create(null)
    		//遍历computed
    		for (const key in computed) {
    		    const userDef = computed[key]
    		    //getter值为computed中key的监听函数或对象的get值
    		    let getter = typeof userDef === 'function' ? userDef : userDef.get
    		    //新建computed的 watcher
    		    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
    		    if (!(key in vm)) {
    		      	/*定义计算属性*/
    		      	this.defineComputed(vm, key, userDef)
    		    }
    		}
    	}
        //把计算属性的key挂载到vm对象下,并使用Object.defineProperty进行处理
        //因此调用vm.somecomputed 就会触发get函数
    	defineComputed (target, key, userDef) {
    	  if (typeof userDef === 'function') {
    	    sharedPropertyDefinition.get = this.createComputedGetter(key)
    	    sharedPropertyDefinition.set = noop
    	  } else {
    	    sharedPropertyDefinition.get = userDef.get
    	      ? userDef.cache !== false
    	        ? this.createComputedGetter(key)
    	        : userDef.get
    	      : noop
    	      //如果有设置set方法则直接使用,否则赋值空函数
    	    	sharedPropertyDefinition.set = userDef.set
    	      	? userDef.set
    	      	: noop
    	  }
    	  Object.defineProperty(target, key, sharedPropertyDefinition)
    	}
    
    	//计算属性的getter 获取计算属性的值时会调用
    	createComputedGetter (key) {
    	  return function computedGetter () {
    	  	//获取到相应的watcher
    	    const watcher = this._computedWatchers && this._computedWatchers[key]
    	    if (watcher) {
    	    	//watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次
    	      	if (watcher.dirty) {
    	      		/*每次执行之后watcher.dirty会设置为false,只要依赖的data值改变时才会触发
    	      		watcher.dirty为true,从而获取值时从新计算*/
    	        	watcher.evaluate()
    	      	}
    	      	//获取依赖
    	      	if (Dep.target) {
    	        	watcher.depend()
    	      	}
    	      	//返回计算属性的值
    	      	return watcher.value
    	    }
    	  }
    	}
    }复制代码


    代码已经写完,完整代码如下:

    //标识当前的Dep id
    let uidep = 0
    class Dep{
    	constructor () {
    		this.id = uidep++
    		// 存放所有的监听watcher
        	this.subs = []
      	}
    
      	//添加一个观察者对象
      	addSub (Watcher) {
        	this.subs.push(Watcher)
      	}
      	//依赖收集
    	depend () {
    		//Dep.target 作用只有需要的才会收集依赖
    	    if (Dep.target) {
    	      Dep.target.addDep(this)
    	    }
    	}
    
    	// 调用依赖收集的Watcher更新
        notify () {
    	    const subs = this.subs.slice()
    	    for (let i = 0, l = subs.length; i < l; i++) {
    	      subs[i].update()
    	    }
      	}
    }
    
    Dep.target = null
    const targetStack = []
    
    // 为Dep.target 赋值
    function pushTarget (Watcher) {
    	if (Dep.target) targetStack.push(Dep.target)
      	Dep.target = Watcher
    }
    function popTarget () {
      Dep.target = targetStack.pop()
    }
    /*----------------------------------------Watcher------------------------------------*/
    //去重 防止重复收集
    let uid = 0
    class Watcher{
    	constructor(vm,expOrFn,cb,options){
    		//传进来的对象 例如Vue
    		this.vm = vm
    		if (options) {
    	      this.deep = !!options.deep
    	      this.user = !!options.user
    	      this.lazy = !!options.lazy
    	    }else{
    	    	this.deep = this.user = this.lazy = false
    	    }
    	    this.dirty = this.lazy
    		//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
    		this.cb = cb
    		this.id = ++uid
    		this.deps = []
    	    this.newDeps = []
    	    this.depIds = new Set()
    	    this.newDepIds = new Set()
    		if (typeof expOrFn === 'function') {
    			//data依赖收集走此处
    	      	this.getter = expOrFn
    	    } else {
    	    	//watch依赖走此处
    	      	this.getter = this.parsePath(expOrFn)
    	    }
    		//设置Dep.target的值,依赖收集时的watcher对象
    		this.value = this.lazy ? undefined : this.get()
    	}
    
    	get(){
    		//设置Dep.target值,用以依赖收集
    	    pushTarget(this)
    	    const vm = this.vm
    	    //此处会进行依赖收集 会调用data数据的 get
    	    let value = this.getter.call(vm, vm)
    	    popTarget()
    	    return value
    	}
    
    	//添加依赖
      	addDep (dep) {
      		//去重
      		const id = dep.id
    	    if (!this.newDepIds.has(id)) {
    	      	this.newDepIds.add(id)
    	      	this.newDeps.push(dep)
    	      	if (!this.depIds.has(id)) {
    	      		//收集watcher 每次data数据 set
    	      		//时会遍历收集的watcher依赖进行相应视图更新或执行watch监听函数等操作
    	        	dep.addSub(this)
    	      	}
    	    }
      	}
    
      	//更新
      	update () {
      		if (this.lazy) {
          		this.dirty = true
        	}else{
        		this.run()
        	}
    	}
    
    	//更新视图
    	run(){
    		console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    		const value = this.get()
    		const oldValue = this.value
            this.value = value
    		if (this.user) {
    			//watch 监听走此处
                this.cb.call(this.vm, value, oldValue)
            }else{
            	//data 监听走此处
            	//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
    			this.cb.call(this.vm, value, oldValue)
            }
    	}
    
        //如果计算熟悉依赖的data值发生变化时会调用
        //案例中 当data.name值发生变化时会执行此方法
    	evaluate () {
    	    this.value = this.get()
    	    this.dirty = false
    	}
    	//收集依赖
    	depend () {
    	    let i = this.deps.length
    	    while (i--) {
    	      this.deps[i].depend()
    	    }
    	}
    
    	// 此方法获得每个watch中key在data中对应的value值
    	//使用split('.')是为了得到 像'a.b.c' 这样的监听值
    	parsePath (path){
    		const bailRE = /[^w.$]/
    	  if (bailRE.test(path)) return
    	  	const segments = path.split('.')
    	  	return function (obj) {
    		    for (let i = 0; i < segments.length; i++) {
    		      	if (!obj) return
    		      	//此处为了兼容我的代码做了一点修改	 
    		        //此处使用新获得的值覆盖传入的值 因此能够处理 'a.b.c'这样的监听方式
    		        if(i==0){
    		        	obj = obj.data[segments[i]]
    		        }else{
    		        	obj = obj[segments[i]]
    		        }
    		    }
    		    return obj
    		 }
    	}
    }
    
    /*----------------------------------------Observer------------------------------------*/
    class Observer{
    	constructor (value) {
    	    this.value = value
    	    // 增加dep属性(处理数组时可以直接调用)
    	    this.dep = new Dep()
    	    //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,
    	    //处理数组是也可直接获取Observer对象
    	    def(value, '__ob__', this)
    	    if (Array.isArray(value)) {
    	    	//这里只测试对象
    	    } else {
    	    	//处理对象
    	      	this.walk(value)
    	    }
    	}
    
    	walk (obj) {
        	const keys = Object.keys(obj)
        	for (let i = 0; i < keys.length; i++) {
        		//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理
        		if(keys[i]=='__ob__') return;
          		defineReactive(obj, keys[i], obj[keys[i]])
        	}
      	}
    }
    //数据重复Observer
    function observe(value){
    	if(typeof(value) != 'object' ) return;
    	let ob = new Observer(value)
      	return ob;
    }
    // 把对象属性改为getter/setter,并收集依赖
    function defineReactive (obj,key,val) {
      	const dep = new Dep()
      	//处理children
      	let childOb = observe(val)
      	Object.defineProperty(obj, key, {
        	enumerable: true,
        	configurable: true,
        	get: function reactiveGetter () {
        		console.log(`调用get获取值,值为${val}`)
          		const value = val
          		if (Dep.target) {
    	        	dep.depend()
    		        if (childOb) {
    		          	childOb.dep.depend()
    		        }
    	      	}
          		return value
    	    },
    	    set: function reactiveSetter (newVal) {
    	    	console.log(`调用了set,值为${newVal}`)
    	      	const value = val
    	       	val = newVal
    	       	//对新值进行observe
    	      	childOb = observe(newVal)
    	      	//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新
    	      	dep.notify()
    	    }
      })
    }
    //辅助方法
    function def (obj, key, val) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: true,
        writable: true,
        configurable: true
      })
    }
    /*----------------------------------------初始化watch------------------------------------*/
    //空函数
    const noop = ()=>{}
    // computed初始化的Watcher传入lazy: true就会触发Watcher中的dirty值为true
    const computedWatcherOptions = { lazy: true }
    //Object.defineProperty 默认value参数
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    // 初始化computed
    class initComputed {
    	constructor(vm, computed){
    		//新建存储watcher对象,挂载在vm对象执行
    		const watchers = vm._computedWatchers = Object.create(null)
    		//遍历computed
    		for (const key in computed) {
    		    const userDef = computed[key]
    		    //getter值为computed中key的监听函数或对象的get值
    		    let getter = typeof userDef === 'function' ? userDef : userDef.get
    		    //新建computed的 watcher
    		    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
    		    if (!(key in vm)) {
    		      	/*定义计算属性*/
    		      	this.defineComputed(vm, key, userDef)
    		    }
    		}
    	}
        //把计算属性的key挂载到vm对象下,并使用Object.defineProperty进行处理
        //因此调用vm.somecomputed 就会触发get函数
    	defineComputed (target, key, userDef) {
    	  if (typeof userDef === 'function') {
    	    sharedPropertyDefinition.get = this.createComputedGetter(key)
    	    sharedPropertyDefinition.set = noop
    	  } else {
    	    sharedPropertyDefinition.get = userDef.get
    	      ? userDef.cache !== false
    	        ? this.createComputedGetter(key)
    	        : userDef.get
    	      : noop
    	      //如果有设置set方法则直接使用,否则赋值空函数
    	    	sharedPropertyDefinition.set = userDef.set
    	      	? userDef.set
    	      	: noop
    	  }
    	  Object.defineProperty(target, key, sharedPropertyDefinition)
    	}
    
    	//计算属性的getter 获取计算属性的值时会调用
    	createComputedGetter (key) {
    	  return function computedGetter () {
    	  	//获取到相应的watcher
    	    const watcher = this._computedWatchers && this._computedWatchers[key]
    	    if (watcher) {
    	    	//watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次
    	      	if (watcher.dirty) {
    	      		/*每次执行之后watcher.dirty会设置为false,只要依赖的data值改变时才会触发
    	      		watcher.dirty为true,从而获取值时从新计算*/
    	        	watcher.evaluate()
    	      	}
    	      	//获取依赖
    	      	if (Dep.target) {
    	        	watcher.depend()
    	      	}
    	      	//返回计算属性的值
    	      	return watcher.value
    	    }
    	  }
    	}
    }复制代码


    computed测试:

    //1、首先来创建一个Vue构造函数:
    function Vue(){
    }
    //2、设置data和computed的值:
    let data={
        name:'Hello',
    }
    let computed={
    	getfullname:function(){
    		console.log('-----走了computed 之 getfullname------')
    		console.log('新的值为:'+data.name + ' - world')
    		return data.name + ' - world'
    	}
    }
    //3、实例化Vue并把data挂载到Vue上
    let vue 		= new Vue()
    vue.data 		= data
    //4、创建Watcher对象
    let updateComponent = (vm)=>{
    	// 收集依赖
    	data.name
    	
    }
    let watcher1 = new Watcher(vue,updateComponent,()=>{})
    //5、初始化Data并收集依赖
    observe(data)
    //6、初始化computed
    let watcher2 = new initComputed(vue,computed)复制代码


    在浏览器console中测试:

    //首先获得一次getfullname
    vue.getfullname
    
    //第二次调用getfullname看看会有什么变化呢
    vue.getfullname复制代码

    分析:调用vue.getfullname第一次会打印 '-----走了computed 之 getfullname------',即计算属性第一次计算了值,第二次调用时,不会再打印值

    即直接获取的缓存值,为什么第二次是获得的缓存值呢,因为第二次执行时watcher.dirty=true,就会直接返回watcher.value值。


    //为data.name赋值
    data.name = 'Hi'复制代码

    分析:执行data.name时会触发两个Watcher监听函数(为什么是两个Watcher自己去脑补一下额!),一个是全局的watcher,一个是computed的watcher,第一个Watcher会更新视图,第二个Watcher会触发watcher.dirty=true。


    //name值变更之后再次执行会是什么结果呢
    vue.getfullname
    
    //再执行一次
    vue.getfullname复制代码

    分析:运行vue.getfullname时会执行computedGetter函数,因为watcher.dirty=true因此会从新计算值,因此会打印 '-----走了computed 之 getfullname------',值为'HI world', 再次执行只会获得计算属性的缓存值。

    所有测试代码如下:

    /*----------------------------------------Vue------------------------------------*/
    function Vue(){
    }
    /*----------------------------------------测试代码------------------------------------*/
    // 调用
    let data={
        name:'Hello',
    }
    let computed={
    	getfullname:function(){
    		console.log('-----走了computed 之 getfullname------')
    		console.log('新的值为:'+data.name + ' - world')
    		return data.name + ' - world'
    	}
    }
    let vue 		= new Vue()
    vue.data 		= data
    let updateComponent = (vm)=>{
    	// 收集依赖
    	data.name
    }
    let watcher1 = new Watcher(vue,updateComponent,()=>{})
    observe(data)
    let watvher2 = new initComputed(vue,computed)
    
    //测试 浏览器console中相继运行一下代码测试
    vue.getfullname
    vue.getfullname
    data.name='Hi'
    vue.getfullname
    vue.getfullname复制代码


    若有疑问欢迎交流。


    展开全文
  • Computed 计算属性是 Vue 中常用的一个功能,本篇文章主要介绍了Vue Computed 计算属性原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 概念: computed是一个计算属性,类似于过滤器,对绑定到view的数据...原理: 在initState的时候会初始化initComputed,默认初始化计算属性initComputed的时候会创建一个watcher(new Watcher),这个watcher有一...

    概念:

         computed是一个计算属性,类似于过滤器,对绑定到view的数据进行处理,计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。它用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就可以在页面上进行双向数据绑定展示出结果或者用作其他处理。

          计算属性不是异步更新的。渲染的时候才能取到最新的值。

    原理:

        在initState的时候会初始化initComputed,默认初始化计算属性initComputed的时候会获取用户定义的方法,内部会创建一个watcher(new Watcher),将用户定义的方法传入,这个watcher有一个computedWatcherOptions标识lazy:true,默认创建出来watcher并不会去执行,watcher内部有个属性叫dirty,如果是计算属性默认dirty就是true。watcher的第二次传入的是一个getter用户定义的属性,在watcher对传入的属性方法进行判断,如果是function就把它存到一个getter上,当lazy是true就undefind的什么也不做,false的话就执行this.get()进行依赖追加表示不是计算属性是watch。

           在state文件中有一个defineComputed方法,用来定义计算属性,它的底层也使用的是Object.defineProperty,在这个方法中又定义了一个createComputedGetter方法用来创建getter,在取值的时候会执行createComputedGetter方法返回一个computedGetter函数来,里面会对计算属性的watcher中dirty进行判断,当为true时,watcher才会去执行计算属性进行求值(watcher.evaluate),在evaluate中会执行get方法,get方法就是让当前的getter执行,在求值的过程中进行依赖收集。在取值之前pushTarget(this)就是将watcher放到全局,进行依赖收集,会把当前的计算属性的watcher收集起来,等数据发生变化,就会触发watcher的update执行,在watcher的update中,如果lazy为true是计算属性,就会将dirty改为true在取值时重新进行求值,在求值完成后就会把dirty改为false,如果在取值时dirty为false就会直接将值返回。

          计算属性的核心就是定义了一个dirty,实现了一个缓冲的机制。

    源码:

    1.initComputed

    function initComputed (vm: Component, computed: Object) {
      const watchers = vm._computedWatchers = Object.create(null)
      const isSSR = isServerRendering()
      for (const key in computed) {
        const userDef = computed[key] // 获取用户定义的方法
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (process.env.NODE_ENV !== 'production' && getter == null) {
          warn(
            `Getter is missing for computed property "${key}".`,
            vm
          )
        }
        if (!isSSR) { // 创建一个watcher,把用户的定义的方法传入
          watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions // {lazy:true},目前watcher不会执行
          )
        }
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
          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)
          }
        }
      }
    }

    2.watcher中的部分

        if (options) {
          this.deep = !!options.deep
          this.user = !!options.user
          this.lazy = !!options.lazy
          this.sync = !!options.sync
          this.before = options.before
        } else {
          this.deep = this.user = this.lazy = this.sync = false
        }
        this.cb = cb
        this.id = ++uid // uid for batching
        this.active = true
        this.dirty = this.lazy // lazy默认是true,如果是计算属性默认dirty就是true
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.expression = process.env.NODE_ENV !== 'production'
          ? expOrFn.toString()
          : ''
        if (typeof expOrFn === 'function') { // expOrFn就是传进来的用户定义的计算属性方法
          this.getter = expOrFn //如果expOrFn是个函数就将它复制给getter 
        } else {
          this.getter = parsePath(expOrFn)
          if (!this.getter) {
            this.getter = noop
            process.env.NODE_ENV !== 'production' && warn(
              `Failed watching path: "${expOrFn}" ` +
              'Watcher only accepts simple dot-delimited paths. ' +
              'For full control, use a function instead.',
              vm
            )
          }
        }
        this.value = this.lazy ? undefined : this.get() // lazy为true时就什么也不做

    3.defineComputed

    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      const shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key) // 创建计算属性的getter
          : 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 (process.env.NODE_ENV !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            `Computed property "${key}" was assigned to but it has no setter.`,
            this
          )
        }
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
      // target就是vm,key当前计算属性的key值,sharedPropertyDefinition并不是我们原生的用户定义,而且自己写了一个
    }

    4.createComputedGetter 

    // 定义一个计算属性
    function createComputedGetter (key) {
      return function computedGetter () { // 取值的时候回调用此方法
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) { // 查看当前计算属性的dirty是否为true
            watcher.evaluate() // 为true时就会进行求值
          }
          if (Dep.target) {
            watcher.depend()
          }
          return watcher.value
        }
      }
    }

    5.evaluate 

      evaluate () {
        this.value = this.get() // 求值
        this.dirty = false // 求值完将dirty改为false,如果下次是false就直接返回值不做计算
      }

    6.get()让当前的getter执行

      get () {
        pushTarget(this) // 将watcher放到全局上
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)  // 取值, 会进行依赖收集,getter就是用户传入的方法
        } catch (e) {
          if (this.user) {
            handleError(e, vm, `getter for watcher "${this.expression}"`)
          } else {
            throw e
          }
        } finally {
          if (this.deep) {
            traverse(value)
          }
          popTarget()
          this.cleanupDeps()
        }
        return value
      }

    7.watcher中的update

      update () { // 追加的依赖发生了变化就会执行这
        if (this.lazy) { // 如果是计算属性,将dirty变为true,当再次取值时就会再次求值
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
      // 计算属性优先于queueWatcher(普通watcher)

     

    引申出的面试题:

    1.computed,watch,method的区别?

    (1)method方法只要把方法用到模板上了,当每次一变化就会重新视图渲染,它的性能开销就比较大。computed计算属性也是一个watcher是具备缓存的,只有当依赖的属性发生变化时才会更新视图。

    如果页面中有属性需求区计算不要在页面中这样写{{fn()}},这时候可以写一个计算属性,渲染的时候它依赖的属性没有发生变化,它就不会导致方法重新执行。

    (2)computed和watch的原理它们里面都是一个watcher来实现的,computed是具备缓存的,watch不具备。

    展开全文
  • Vuecomputed原理分析

    2020-09-10 14:40:24
    1.Computed也是响应式的 Computed是响应式的,读取Computed会触发get,设置Computed会触发set 2.Computed如何控制缓存 计算属性是有缓存的,比如某个计算属性C,它依赖data中的A,如果没有缓存的话,每次读取C时,C...
  • Vue Computed 原理

    2020-12-19 09:05:01
    Computed 计算属性是 Vue 中常用的一个功能,在使用中发现他像是介于data和method之间的一个属性。...本片文章就简要分析一下computed原理,在此之间需要了解一下Vue数据响应的原理Object.defineProperty中的...
  • vue中的computed实现原理---------需要建立数据依赖搜集,动态计算实现原理 1)问题:计算属性如何与属性建立依赖关系?属性发生变化又如何通知到计算属性重新计算? 如何建立依赖关系?----------利用 ...
  • 看这篇文章之前最好先对Vue的MVVM实现原理有一定的认识,因为这是Vue的核心概念,其他的工具大部分都是在此之上锦上添花,如果你不是很了解,可以先看看这篇文章: Vue的MVVM原理 实现原理分析 initState 在Vue源码...
  • computed是什么,主要干嘛。有啥特点: computed 是计算属性,可缓存。主要用来替换文本中的模板。插值表达式。 用来包装data数据的复杂逻辑。方便管理。 当依赖的data值发生改变时会自动重新计算。 包含 get 和 ...
  • 浅谈Vue中计算属性computed实现原理

    千次阅读 2018-12-18 21:11:55
    虽然目前的技术栈已由Vue转到了React,但从之前...之前也有断断续续研读过Vue的源码,但一直没有梳理总结,所以在此做一些技术归纳同时也加深自己对Vue的理解,那么今天要写的便是Vue中最常用到的API之一computed...
  • 上一篇: Vue 原理解析(八) 之 ... 对它内部的实现做了分析, 这些首饰篇底层的原理。 下面我们将介绍日常开发中经常使用的API的原理, 进一步丰富vue 的认识, 主要包括如下API: 响应式相关API: this.watch,t...
  • ,我们通过实现一个简单版的和Vuecomputed具有相同功能的函数来了解computed是如何工作的。对Vue.js中的computed工作原理感兴趣的朋友跟随脚本之家小编一起学习吧
  • 1、计算属性默认不执行 Object.defineProperty =>getter 只有在取值的时候才执行 多次取值如果依赖的值不变化就只执行一次,是缓存的原因 依赖的值变化了,需要重新执行...const vm = new Vue({ computed: { ...
  • Vue computed以及watch简单实现

    千次阅读 2019-05-21 20:55:30
    Vue computed以及watch简单实现 这一次就不先上效果图了,这次先说需求: 在vue中,有两个很好用的api,就是computed以及watch,computed为可以自定义计算属性,这个计算属性有哪些优点呢,先说一些相比较于计算...
  • 1.初始化 data 和 computed,分别代理其 set 和 get 方法,对 data 中的所有属性生成唯一的 dep 实例 2.对 computed 中的 属性生成唯一的 watcher,并保存在 vm._computedWatchers 中 3.访问计算属性时,设置 Dep....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,846
精华内容 3,938
关键字:

vue的computed实现原理

vue 订阅