精华内容
下载资源
问答
  • 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 方法,在不同情况下会触发不同的方法,这里我们就不一一解释了。感兴趣的可以自己去看看
    展开全文
  • computed watch原理理解

    2021-03-28 10:57:16
    此时重新执行render函数,视图得以更新(这里的Watcher还不完善,看后面逐步实现完整的Watcher) 二、Computed computed特征是缓存,仅有在数据有变化时才会执行计算取值,否则都是在缓存中取的 初始化渲染时会执行...

    一、new Vue()的执行过程

    1.1 数据劫持和初步渲染
    function Vue (options) {
      // 初始化
      this._init(options)
      // 执行render函数
      this.$mount()
    }
    Vue.prototype._init = function (options) {
      const vm = this
      // 把options挂载到this上
      vm.$options = options
      if (options.data) {
        // 数据响应式
        initState(vm)
      }
      if (options.computed) {
        // 初始化计算属性
        initComputed(vm)
      }
      if (options.watch) {
        // 初始化watch
        initWatch(vm)
      }
    }
    

    initState()中实现数据劫持和代理:

    function initState(vm) {
      // 拿到配置的data属性值
      let data = vm.$options.data;
      // 判断data 是函数还是别的类型
      data = vm._data = typeof data === 'function' ? data.call(vm, vm) : data || {};
      // 数据代理
      const keys = Object.keys(data);
      let i = keys.length;
      while(i--) {
        // 从this上读取的数据全部拦截到this._data到里面读取
        // 例如 this.name 等同于  this._data.name
        proxy(vm, '_data', keys[i]);
      }
      // 数据观察
      observe(data);
    }
    
    // 数据观察函数
    function observe(data) {
      if (typeof data !== 'object' && data != null) {
        return;
      }
      return new Observer(data)
    }
    
    // 从this上读取的数据全部拦截到this._data到里面读取
    // 例如 this.name 等同于  this._data.name
    function proxy(vm, source, key) {
      Object.defineProperty(vm, key, {
        get() {
          return vm[source][key] // this.name 等同于  this._data.name
        },
        set(newValue) {
          return vm[source][key] = newValue
        }
      })
    }
    
    class Observer{
      constructor(value) {
        // 给每一个属性都设置get set
        this.walk(value)
      }
      walk(data) {
        let keys = Object.keys(data);
        for (let i = 0, len = keys.length; i < len; i++) {
          let key = keys[i]
          let value = data[key]
          // 给对象设置get set
          defineReactive(data, key, value)
        }
      }
    }
    
    function defineReactive(data, key, value) {
      Object.defineProperty(data, key, {
        get() {
          return value
        },
        set(newValue) {
          if (newValue == value) return
          observe(newValue) // 给新的值设置响应式
          value = newValue
        }
      })
      // 递归给数据设置get set
      observe(value);
    }
    

    initComputed()以及initWatch()后面再实现,这时候new Vue()中的this._init(options)执行完成,接着执行this.$mount()

    // 挂载方法
    Vue.prototype.$mount = function () {
      const vm = this
      new Watcher(vm, vm.$options.render, () => {}, true)  // 这里的参数true表示是渲染Watcher
    }
    

    在这里可以看到new了一个Watcher,此处的Watcher就是渲染Watcher,先看看Watcher的初步实现:

    let wid = 0
    class Watcher {
      constructor(vm, exprOrFn, cb, options) {
        this.vm = vm // 把vm挂载到当前的this上
        if (typeof exprOrFn === 'function') {
          this.getter = exprOrFn // 把exprOrFn挂载到当前的this上,这里exprOrFn 等于 vm.$options.render
        }
        this.cb = cb // 把cb挂载到当前的this上
        this.options = options // 把options挂载到当前的this上
        this.id = wid++  // 每个Watcher都有其id
        this.value = this.get() // 相当于运行 vm.$options.render()
      }
      get() {
        const vm = this.vm
        let value = this.getter.call(vm, vm) // 把this指向到vm,此时的getter即是render函数
        return value
      }
    }
    

    可以看到,当在this.$mount()new Watcher()渲染Watcher)时,会立刻执行this.get()方法去获取数据渲染,至此,首页渲染就完成了

    1.2 Dep Watcher

    在首页渲染的过程中,是data订阅的时期,以实现数据修改时能够修改视图

    • Dep类的实现:
    // 依赖收集
    let dId = 0
    class Dep{
      constructor() {
        this.id = dId++ // 每次实例化都生成一个id
        this.subs = [] // 让这个dep实例收集watcher
      }
      depend() {
        // Dep.target 就是当前的watcher
        if (Dep.target) {
          Dep.target.addDep(this) // 让watcher,去存放dep,然后里面dep存放对应的watcher,两个是多对多的关系
        }
      }
      notify() {
        // 触发更新
        this.subs.forEach(watcher => watcher.update())
      }
      addSub(watcher) {
        this.subs.push(watcher)
      }
    }
    
    let stack = []
    // push当前watcher到stack 中,并记录当前watcer
    function pushTarget(watcher) {
      Dep.target = watcher
      stack.push(watcher)
    }
    // 运行完之后清空当前的watcher
    function popTarget() {
      stack.pop()
      Dep.target = stack[stack.length - 1]
    }
    

    发现Dep中有一个depend()实例方法,该方法核心代码为Dep.target.addDep(this),即调用当前栈顶WatcheraddDep(),目的是实现每一个Watcher记录所有它订阅的dep以及每一个dep记录它所有收集的Watcher的双向保存,看看Watcher的改进实现:

    class Watcher {
      constructor(vm, exprOrFn, cb, options) {
        this.vm = vm
        if (typeof exprOrFn === 'function') {
          this.getter = exprOrFn
        }
        this.cb = cb
        this.options = options
        this.id = wid++
    +    this.deps = []
    +    this.depsId = new Set() // dep 已经收集过相同的watcher 就不要重复收集了
        this.value = this.get()
      }
      get() {
        const vm = this.vm
    +   pushTarget(this)
        // 执行函数
        let value = this.getter.call(vm, vm)
    +   popTarget()
        return value
      }
    +  addDep(dep) {
    +    let id = dep.id
    +    if (!this.depsId.has(id)) {  // 当watcher有dep时,也意味着dep中已有该watcher,就不再双向保存了,保证了首页渲染后数据修改,触发执行渲染watcher时,不会再添加渲染watcher到属性的dep中
    +      this.depsId.add(id)
    +      this.deps.push(dep)  // 当前watcher保存dep
    +      dep.addSub(this)  // dep保存当前watcher
    +    }
    +  }
    +  update(){
    +    this.get()
    +  }
    }
    

    DepWatcher都有了,接下来就是watcher什么时候订阅:

    function defineReactive(data, key, value) {
      let dep = new Dep()
      Object.defineProperty(data, key, {
        get() {
    +      if (Dep.target) { // 如果取值时有watcher
    +        dep.depend() // 让watcher保存dep,并且让dep 保存watcher,双向保存
    +      }
          return value
        },
        set(newValue) {
          if (newValue == value) return
          observe(newValue) // 给新的值设置响应式
          value = newValue
    +      dep.notify() // 通知渲染watcher去更新
        }
      })
      // 递归给数据设置get set
      observe(value);
    }
    
    • initState()时,每一个属性都创建了自己的dep
    • 在首页渲染时,创建渲染Watcher,执行get()pushTarget(this), 此时栈顶是渲染Watcher
    • let value = this.getter.call(vm, vm),即执行render函数,会执行每个属性的get()方法,这个时候每个属性把渲染Watcher都添加到自己的dep中,渲染Watcher中也添加了每一个自己订阅的依赖器dep
    • 当数据变化时,变化属性执行dep.notify(),变化属性的dep中有渲染Watcher,因此执行渲染Watcherupdate(),即get(),此时重新执行render函数,视图得以更新(这里的Watcher还不完善,看后面逐步实现完整的Watcher

    二、Computed

    • computed特征是缓存,仅有在数据有变化时才会执行计算取值,否则都是在缓存中取的
    • 初始化渲染时会执行一次

    执行代码:

    const root = document.querySelector('#root')
    var vue = new Vue({
      data() {
        return {
          name: '张三',
          age: 10
        }
      },
      computed: {
        info() {
          return this.name + this.age
        }
      },
      render() {
        root.innerHTML = `${this.name}----${this.age}----${this.info}`
      }
    })
    function changeData() {
      vue.name = '李四'
      vue.age = 20
    }
    

    执行initComputed

    // 初始化computed
    function initComputed(vm) {
      // 拿到computed配置
      const computed = vm.$options.computed
      // 给当前的vm挂载_computedWatchers属性,后面会用到
      const watchers = vm._computedWatchers = Object.create(null)
      // 循环computed每个属性
      for (const key in computed) {
        const userDef = computed[key]
        // 判断是函数还是对象
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // 给每一个computed创建一个computed watcher 注意{ lazy: true }
        // 然后挂载到vm._computedWatchers对象上
        watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true })
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)  // 在vm上定义key属性,如果key已在vm中,则会报错警告
        }
      }
    }
    

    computed是有缓存的,所以创建watcher的时候,会传一个配置{ lazy: true },同时也可以区分这是computed watcher,然后到watcer里面接收到这个对象,我们继续看watcher的实现:

    class Watcher {
      constructor(vm, exprOrFn, cb, options) {
        this.vm = vm
        if (typeof exprOrFn === 'function') {
          this.getter = exprOrFn
        }
    +    if (options) {
    +      this.lazy = !!options.lazy // 为computed 设计的
    +    } else {
    +      this.lazy = false
    +    }
    +    this.dirty = this.lazy
        this.cb = cb
        this.options = options
        this.id = wId++
        this.deps = []
        this.depsId = new Set()
    +    this.value = this.lazy ? undefined : this.get()
      }
      // 省略很多代码
    }
    

    从上面这句this.value = this.lazy ? undefined : this.get()代码可以看到,computed创建watcher的时候是不会指向this.get的,第一次执行get是在render即初次渲染时获取计算属性时
    computed watcher创建好了之后,把计算属性挂载到vm实例上,即执行defineComputed(vm, key, userDef)

    // 设置comoputed的 set个set
    function defineComputed(vm, key, userDef) {
      let getter = null
      // 判断是函数还是对象
      if (typeof userDef === 'function') {
        getter = createComputedGetter(key)
      } else {
        getter = userDef.get
      }
      Object.defineProperty(vm, key, {   // 劫持计算属性
        enumerable: true,
        configurable: true,
        get: getter,
        set: function() {} // 又偷懒,先不考虑set情况哈,自己去看源码实现一番也是可以的
      })
    }
    // 创建computed函数
    function createComputedGetter(key) {
      return function computedGetter() {
        const watcher = this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) {// 给computed的属性添加订阅watchers
            watcher.evaluate()
          }
          // 把渲染watcher 添加到属性的订阅里面去,这很关键
          if (Dep.target) {
            watcher.depend()
          }
          return watcher.value
        }
      }
    }
    

    可以看到,读取计算属性时主要执行了createComputedGetter(key)(这里只考虑了getter是函数的情况),核心代码是watcher.evaluate()watcher.depend(),接下来在watch中实现:

    class Watcher {
      constructor(vm, exprOrFn, cb, options) {
        this.vm = vm
        if (typeof exprOrFn === 'function') {
          this.getter = exprOrFn
        }
        if (options) {
          this.lazy = !!options.lazy // 为computed 设计的
        } else {
          this.lazy = false
        }
        this.dirty = this.lazy
        this.cb = cb
        this.options = options
        this.id = wId++
        this.deps = []
        this.depsId = new Set() // dep 已经收集过相同的watcher 就不要重复收集了
        this.value = this.lazy ? undefined : this.get()
      }
      get() {
        const vm = this.vm
        pushTarget(this)
        // 执行函数
        let value = this.getter.call(vm, vm)
        popTarget()
        return value
      }
      addDep(dep) {
        let id = dep.id
        if (!this.depsId.has(id)) {
          this.depsId.add(id)
          this.deps.push(dep)
          dep.addSub(this);
        }
      }
      update(){
        if (this.lazy) {
          this.dirty = true
        } else {
          this.get()
        }
      }
      // 执行get,并且 this.dirty = false
    +  evaluate() {
    +    this.value = this.get()
    +    this.dirty = false
    +  }
      // 所有的属性收集当前的watcer
    +  depend() {
    +    let i = this.deps.length
    +    while(i--) {
    +      this.deps[i].depend()
    +    }
    +  }
    }
    

    执行流程:

    • 1、首先在render函数里面会读取this.info,这个会触发createComputedGetter(key)中的computedGetter(key)
    • 2、然后会判断watcher.dirty,执行watcher.evaluate()
    • 3、进到watcher.evaluate(),执行this.get方法,这时候会执行pushTarget(this)把当前的computed watcher pushstack里面去,并且把Dep.target 设置成当前的computed watcher
    • 4、然后运行this.getter.call(vm, vm) 相当于运行computedinfo: function() { return this.name + this.age },这个方法;
    • 5、info函数里面会读取到this.name,这时候就会触发数据响应式this.nameObject.defineProperty.get的方法,这里name会进行依赖收集,把watcher收集到对应的dep上面,computed watcher此时也把namedep记录在了自己的deps中;并且返回name = '张三'的值,age收集同理;
    • 6、依赖收集完毕之后执行popTarget(),把当前的computed watcher从栈清除,返回计算后的值('张三+10'),并且this.dirty = false
    • 7、watcher.evaluate()执行完毕之后,就会判断Dep.target是不是true,如果有就代表还有渲染watcher,就执行watcher.depend(),然后让watcher里面的deps都收集渲染watcher,这就是双向保存的优势。
    • 8、此时name都收集了computed watcher渲染watcher。那么设置name的时候都会去更新执行watcher.update()
    • 9、首页渲染后,数据更改,执行watcher,update(),如果是computed watcher的话不会重新执行一遍,只会把this.dirty设置成true,等渲染的时候需要用到对应计算属性再执行watcher.evaluate()进行info更新。没有变化的话this.dirty就是false,不会执行watcher.evaluate()方法。这就是computed缓存机制
    三、watch

    执行代码:

    const root = document.querySelector('#root')
    var vue = new Vue({
      data() {
        return {
          name: '张三',
          age: 10
        }
      },
      computed: {
        info() {
          return this.name + this.age
        }
      },
      watch: {
        name(newValue, oldValue) {
          console.log(oldValue, newValue)
        }
      },
      render() {
        root.innerHTML = `${this.name}----${this.age}----${this.info}`
      }
    })
    function changeData() {
      vue.name = '李四'
      vue.age = 20
    }
    

    initWatch()

    function initWatch(vm) {
      let watch = vm.$options.watch
      for (let key in watch) {
        const handler = watch[key]
        new Watcher(vm, key, handler, { user: true })
      }
    }
    

    修改watcher

    let wId = 0
    class Watcher {
      constructor(vm, exprOrFn, cb, options) {
        this.vm = vm
        if (typeof exprOrFn === 'function') {
          this.getter = exprOrFn
        } else {
    +      this.getter = parsePath(exprOrFn) // user watcher 
        }
        if (options) {
          this.lazy = !!options.lazy // 为computed watcher设计的
    +      this.user = !!options.user // 为user watcher设计的
        } else {
    +      this.user = this.lazy = false
        }
        this.dirty = this.lazy
        this.cb = cb
        this.options = options
        this.id = wId++
        this.deps = []
        this.depsId = new Set() // dep 已经收集过相同的watcher 就不要重复收集了
        this.value = this.lazy ? undefined : this.get()
      }
      get() {
        const vm = this.vm
        pushTarget(this)
        // 执行函数
        let value = this.getter.call(vm, vm)  // 如果是watch的话getter函数的作用就是获取值而已,在获取值时就在对应属性dep上添加了该watch watcher
        popTarget()
        return value
      }
      addDep(dep) {
        let id = dep.id
        if (!this.depsId.has(id)) {
          this.depsId.add(id)
          this.deps.push(dep)
          dep.addSub(this);
        }
      }
      update(){
        if (this.lazy) {
          this.dirty = true
        } else {
    +      this.run()   // 除了computed都执行这个
        }
      }
      // 执行get,并且 this.dirty = false
      evaluate() {
        this.value = this.get()
        this.dirty = false
      }
      // 所有的属性收集当前的watcer
      depend() {
        let i = this.deps.length
        while(i--) {
          this.deps[i].depend()
        }
      }
    +  run () {
    +    const value = this.get()
    +    const oldValue = this.value
    +    this.value = value
        // 执行cb
    +    if (this.user) {  // watch执行回调
    +      try{
    +        this.cb.call(this.vm, value, oldValue)
    +      } catch(error) {
    +        console.error(error)
    +      }
    +    } else {
    +      this.cb && this.cb.call(this.vm, oldValue, value)
    +    }
    +  }
    }
    function parsePath (path) {
      const segments = path.split('.')
      return function (obj) {  // 如name.age,则name和name.age的dep里都有了watch watcher,但如果修改的是name.age.sex,则不会触发watch watcher了,需要使用deep:true选项
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return
          obj = obj[segments[i]]
        }
        return obj
      }
    }
    
    • watch默认特征是初始渲染时会获取值,执行get()方法以给检测属性dep绑定上watch watcher,但不会执行回调cb,而且不会执行深检测
    • 深检测:配置deep: true选项,原理就是遍历值继续绑定,大致代码
    // 由于配置选项时,handler不是函数,因此得考虑对象写法,就不详细写了,只写出大致思路,这里deepHas = !deep
    class Watcher {
      get() {
        const vm = this.vm
        pushTarget(this)
        // 执行函数
        let value = this.getter.call(vm, vm)  // 如果是watch的话getter函数的作用就是获取值而已,在获取值时就在对应属性dep上添加了该watch watcher
        if(!deepHas) {
        	this.deepBind(value)
        	deepHas = true
        }
        popTarget()
        return value
      }
      deepBind (obj) {
      	if(typeof obj === 'object') {
      		for(let key in obj) {
      			this.deepBind(obj[key])  // obj[key]去读取值,此时就绑定上了
      		}
      	}
      }
    }
    
    • 立即执行,配置immediate: true选项
    // 由于配置选项时,handler不是函数,因此得考虑对象写法,就不详细写了,只写出大致思路,这里immediateHas = !immediate
    class Watcher {
      get() {
        const vm = this.vm
        pushTarget(this)
        // 执行函数
        let value = this.getter.call(vm, vm)  // 如果是watch的话getter函数的作用就是获取值而已,在获取值时就在对应属性dep上添加了该watch watcher
        if(!immediateHas) {
        	this.run()
        	immediateHas = true
        }
        popTarget()
        return value
      }
    }
    

    总结一下执行流程:

    • 执行new Vue()
      1. 执行this._init(options)
        • 执行initState()
          • proxy
          • observe
            • 创建属性自身的dep
        • 执行initComputed()
          • 创建computed watcher
            • 不会执行watcher中的get()方法,在这一步仅仅创建了computed watcher,即new Watcher
          • defineComputed()
            • 这一步通过Object.defineProperty将计算属性劫持,设置获取计算属性的get()方法为createComputedGetter(),即
            Object.defineProperty(vm, key, {   // 劫持计算属性
                enumerable: true,
                configurable: true,
                get: createComputedGetter(key)
            }
            
        • 执行initWatch()
          • 创建use watcher
            • 执行watcher中的get()方法,栈顶此时是use watcher,对应属性dep就和use watcher实现了双向保存
      2. 执行this.$mount()
        • 创建渲染 watcher,执行watcher中的get()方法,栈顶此时是渲染watcher,执行render函数取值渲染,渲染过程中取值会引起对应属性dep添加watcher以及watcher添加对应属性dep的双向保存
        • 取计算属性时:会执行createComputedGetter(),最终即执行
        if (watcher) {
              if (watcher.dirty) {// 给computed的属性添加订阅watchers
                watcher.evaluate()
              }
              // 把渲染watcher 添加到属性的订阅里面去,这很关键
              if (Dep.target) {
                watcher.depend()
              }
              return watcher.value
        }
        
        根据watcher.dirty决定是取缓存还是计算取值,因为是第一次渲染,所以是计算取值,取值过程中就实现了computed watcher与对应属性dep的双向保存。继续判断Dep.target,此时是渲染 watcher,因此执行watcher.depend(),这时对应属性的dep上就都挂载了渲染watcher。在这里对应属性的dep上就都挂载了渲染watchercomputed watcher
        1. 取普通属性时,此时栈顶时渲染watcher,因此对应属性dep渲染watcher实现了双向保存
        2. 渲染完毕,渲染watcher出栈,此时栈为空
    • 属性变动时,就会通知dep中的watcher去更新,根据入dep的顺序以及调用的顺序,user watcher先执行,然后是computed watcher,最后是渲染 watcher,这样,所有数值更新后,最后再进行页面渲染,页面就是最新的
      参考:https://segmentfault.com/a/1190000023196603
    展开全文
  • vue中的computed的实现原理---------需要建立数据依赖搜集,动态计算实现原理 1)问题:计算属性如何与属性建立依赖关系?属性发生变化又如何通知到计算属性重新计算? 如何建立依赖关系?----------利用 ...

    vue中的computed的实现原理---------需要建立数据依赖搜集,动态计算实现原理

    1)问题:计算属性如何与属性建立依赖关系?属性发生变化又如何通知到计算属性重新计算?

    如何建立依赖关系?----------利用 JavaScript 单线程原理和 Vue 的 Getter 设计,通过一个简单的发布订阅,就可以在一次计算属性求值的过程中收集到相关依赖

    2)data 属性初始化 getter setter:通过Object.defineProperty()给data设置setter  getter

    // src/observer/index.js
     
    // 这里开始转换 data 的 getter setter,原始值已存入到 __ob__ 属性中
    Object.defineProperty(obj, key, {
     enumerable: true,
     configurable: true,
     get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // 判断是否处于依赖收集状态
      if (Dep.target) {
       // 建立依赖关系
       dep.depend()
       ...
      }
      return value
     },
     set: function reactiveSetter (newVal) {
      ...
      // 依赖发生变化,通知到计算属性重新计算
      dep.notify()
     }
    })

    3)computed属性初始化

    // src/core/instance/state.js
     
    // 初始化计算属性
    function initComputed (vm: Component, computed: Object) {
     ...
     // 遍历 computed 计算属性
     for (const key in computed) {
      ...
      // 创建 Watcher 实例
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
     
      // 创建属性 vm.reversedMessage,并将提供的函数将用作属性 vm.reversedMessage 的 getter,
      // 最终 computed 与 data 会一起混合到 vm 下,所以当 computed 与 data 存在重名属性时会抛出警告
      defineComputed(vm, key, userDef)
      ...
     }
    }
     
    export function defineComputed (target: any, key: string, userDef: Object | Function) {
     ...
     // 创建 get set 方法
     sharedPropertyDefinition.get = createComputedGetter(key)
     sharedPropertyDefinition.set = noop
     ...
     // 创建属性 vm.reversedMessage,并初始化 getter setter
     Object.defineProperty(target, key, sharedPropertyDefinition)
    }
     
    function createComputedGetter (key) {
     return function computedGetter () {
      const watcher = this._computedWatchers && this._computedWatchers[key]
      if (watcher) {
       if (watcher.dirty) {
        // watcher 暴露 evaluate 方法用于取值操作
        watcher.evaluate()
       }
       // 同第1步,判断是否处于依赖收集状态
       if (Dep.target) {
        watcher.depend()
       }
       return watcher.value
      }
     }
    }

    4)无论是属性还是计算属性,都会生成一个对应的 watcher 实例

    // src/core/observer/watcher.js
     
    // 当通过 vm.reversedMessage 获取计算属性时,就会进到这个 getter 方法
    get () {
     // this 指的是 watcher 实例
     // 将当前 watcher 实例暂存到 Dep.target,这就表示开启了依赖收集任务
     pushTarget(this)
     let value
     const vm = this.vm
     try {
      // 在执行 vm.reversedMessage 的函调函数时,会触发属性(步骤1)和计算属性(步骤2)的 getter
      // 在这个执行过程中,就可以收集到 vm.reversedMessage 的依赖了
      value = this.getter.call(vm, vm)
     } 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
    }

    5)Dep 的代码短小精悍,但却承担着非常重要的依赖收集环节

    // src/core/observer/dep.js
     
    export default class Dep {
     static target: ?Watcher;
     id: number;
     subs: Array<Watcher>;
     
     constructor () {
      this.id = uid++
      this.subs = []
     }
     
     addSub (sub: Watcher) {
      this.subs.push(sub)
     }
     
     removeSub (sub: Watcher) {
      remove(this.subs, 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++) {
       // 更新 watcher 的值,与 watcher.evaluate() 类似,
       // 但 update 是给依赖变化时使用的,包含对 watch 的处理
       subs[i].update()
      }
     }
    }
     
    // 当首次计算 computed 属性的值时,Dep 将会在计算期间对依赖进行收集
    Dep.target = null
    const targetStack = []
     
    export function pushTarget (_target: Watcher) {
     // 在一次依赖收集期间,如果有其他依赖收集任务开始(比如:当前 computed 计算属性嵌套其他 computed 计算属性),
     // 那么将会把当前 target 暂存到 targetStack,先进行其他 target 的依赖收集,
     if (Dep.target) targetStack.push(Dep.target)
     Dep.target = _target
    }
     
    export function popTarget () {
     // 当嵌套的依赖收集任务完成后,将 target 恢复为上一层的 Watcher,并继续做依赖收集
     Dep.target = targetStack.pop()
    }

    总结一下依赖收集、动态计算的流程:

    1. data 属性初始化 getter setter

    2. computed 计算属性初始化,提供的函数将用作属性 vm.reversedMessage 的 getter

    3. 当首次获取 reversedMessage 计算属性的值时,Dep 开始依赖收集

    4. 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定 message 为 reversedMessage 的依赖,并建立依赖关系

    5. 当 message 发生变化时,根据依赖关系,触发 reverseMessage 的重新计算

     

    vue中响应式属性有哪些?

    在data中声明的属性为响应式属性,还有哪些呢????

     

     

    观察属性watch和计算属性computed、methods的区别

    定义:

    1)computed---计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例

    类型:{ [key: string]: Function | { get: Function, set: Function } }

    计算属性computed的结果会被缓存,只要依赖的响应式属性发生变化的时候,才会重新计算,否则计算属性不会被更新----主要当做属性来使用

    2)methods---methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例----主要书写业务逻辑

    类型:{ [key: string]: Function }
    
    

    3)watch---一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实化时例调用 $watch(),遍历 watch 对象的每一个属性------主要是监听某些特定数据的变化,从而进行某些特定的业务逻辑操作,可看做computed和methods的结合

    类型:{ [key: string]: string | Function | Object | Array }
    
    

    触发的时机

    1)computed是在HTML DOM加载后马上执行的,如赋值

    2)而methods则必须要有一定的触发条件才能执行,如点击事件;

    3)watch用于观察Vue实例上的数据变动。对应一个对象,键是观察表达式,值是对应回调。值也可以是方法名,或者是对象,包含选项。

    执行顺序

    默认加载的时候computedwatch,不执行methods

    等触发某一事件后,则是:先methodswatch
     

    watch支持同步、异步请求;而computed只支持同步请求

    转载地址   https://blog.csdn.net/smartdt/article/details/75557369

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 前面的话 Vue每日一练,巩固基础,不打烊!!! 解答 如有错误欢迎指出,感谢...想了解computed原理,你需要了解Vue的响应式原理,了解computed其实就是一个惰性watcher。 下面一一解答这几个问题。 Watcher 的实...

    前面的话

    前端日问,巩固基础,不打烊!!!

    解答

    如有错误欢迎指出,感谢!!!

    提出问题

    提出几个问题:

    • computed 是如何初始化的?
    • 为何data值的变化computed会重新计算?
    • 为什么computed值是缓存的呢?

    想了解computed的原理,你需要了解Vue的响应式原理,了解computed其实就是一个惰性watcher

    下面一一解答这几个问题。

    Watcher 的实现

    给出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
    		 }
    	}
    }
    
    computed 的实现

    在Vue响应式原理这篇文章已经重点提了data数据的初始化(即initDate),computed的初始化是在其后面定义的:(为什么一定要在data数据的后面初始化,这也是有原因的,下面这接着说)

    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函数的定义:

    //空函数
    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
    	    }
    	  }
    	}
    }
    

    开头的参数很重要。

    初始化大致流程:

    • 新建存储watcher对象的数组,并挂载在vm_computedWatchers属性上。
    • 遍历整个computed上的属性key
      • 获取key上的监听函数(赋给getter)

      • 创建watcher (即每一个computed属性都是一个watcher)

      • 调用defineComputed函数 :将每一个计算属性key挂载到vm上,并使用Object.defineProperty进行处理。

        具体看一看defineComputed函数做了什么:

        既然要是用Object.defineProperty函数将每个计算属性key挂载到vm上,需要三个值:挂载目标 vm挂载对象 key设置key的value对象。前面两个已经具备,只差value对象了。还记得开头的参数么?这个value对象就是开头设置的sharedPropertyDefinition对象

        sharedPropertyDefinition对象的get属性刚开始是空函数,在这里我们调用createComputedGetter函数来设置get属性。

        具体看一看createComputedGetter 函数做了什么:

        它返回了一个函数给sharedPropertyDefinition.get. 其内部:根据计算属性key找到对应的watcher。(因为计算属性watcher创建时,会传入computedWatcherOptions 对象,这个对象里面定义了lazy: true,导致了计算属性watcher的lazy为true,dirty值初始时为lazy的值。)

        找到对应的watcher后,会执行这段代码:

        	if (watcher.dirty) {
        	      		/*每次执行之后watcher.dirty会设置为false,
        	      		只要依赖的data值改变时才会触发
        	      		watcher.dirty为true,从而获取值时从新计算*/
        	        	watcher.evaluate()
        	    }
        

        watcher.evaluate()就是本文的核心: 初始时,就会执行这个函数

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

        了解响应式原理的应该知道wacther.get()函数的用途:就是绑定Dep.target 为当前的watcher。并且进行data数据的依赖收集。

        看代码: 调用watcher.get会触发data数据的getter,进行依赖收集,收集这个watcher。前面讲过data里面的数据会比computed里面的数据先初始化,就是这个原因。

        get(){
        		//设置Dep.target值,用以依赖收集
        	    pushTarget(this)
        	    const vm = this.vm
        	    //此处会进行依赖收集 会调用data数据的 get
        	    let value = this.getter.call(vm, vm)
        	    popTarget()
        	    return value
        	}
        

    到此为止,上面的问题应该有答案了吧! 计算属性的值,是依赖于其他data属性的,而计算属性本质是计算watcher, 它所依赖的data属性的dep会将这个watcher加入到subs数组中,当其变化时,就会通知这个watcher改变,所以说计算属性时缓存的。

    总结
    • 初始化一个computed,会为每个属性key,创建相应的watcher(将key的监听函数传入,作为watcher的getter)。

    • 挂载属性key到vm上,并用Object.defineProperty为其添加getter。

    • 第一次调用属性key时,触发watcher.evaluate --> 触发watcher.get --> watcher.getter(就是key的监听函数) --> 触发属性key所依赖的data属性的getter,进而收集这个watcher。

    • 当data属性变化时,触发其setter,通知watcher更新,执行watcher.run,进而触发diff,更新视图。

      在这里插入图片描述

    通过一个computed实例来走一下上面的流程

    computed:{
      sayHello() {
        return this.hello + ' ' + this.world;
      }
    },
    

    sayHello计算属性依赖两个属性:this.hellothis.world

    • 在初始化时会创建一个sayHello对应的计算属性watcher,watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)

    • 将sayHello计算属性挂载到vm上,用Object.defineProperty定义其get属性。

      初次获取sayHello属性,触发监听函数 :

      • 获取this.hello -->触发hello的getter --> hello的dep.depend收集收集sayHello的watcher.

      • 获取this.world时,触发world的getter,dep.depend收集sayHello的watcher.

    • 所以sayhello的watcher最后,会有两个dep收集它。hello与world其中任意一个更新,都会触发更新sayhello watcher的run函数

    • 最后触发diff算法,更新虚拟dom,进而更新界面

    computed 与watch的区别

    • computed属性不是data中的属性值,是一个新值,初始化时使用Object.defineProperty方法挂载到vm上;而watch是监听已经存在于data中的属性
    • computed本质是一个惰性的观察者,具有缓存性,之后依赖的data值变化时,才会变化;watch没有缓存性,数据变化就更新
    • computed适合与一个数据被多个数据影响;而watch适用于一个数据影响多个数据。

    参考

    展开全文
  • 浅谈 Vue 中 computed 实现原理

    千次阅读 2018-10-10 19:05:26
    data、props) computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数 从使用场景上说,computed 适用一个...
  • 继上一篇:深入理解Vue的watch实现原理及其实现方式 继续讲解Vue的computed实现相对于watch和data来说比较难以理解,要真正的理解computed的工作方式,你需要深入理解Vue的双向数据绑定原理和实现方式。如果你还不是...
  • 1.初始化 data 和 computed,分别代理其 set 和 get 方法,对 data 中的所有属性生成唯一的 dep 实例 2.对 computed 中的 属性生成唯一的 watcher,并保存在 vm._computedWatchers 中 3.访问计算属性时,设置 Dep....
  • Vue中computed原理分析

    2020-09-10 14:40:24
    2.Computed如何控制缓存 计算属性是有缓存的,比如某个计算属性C,它依赖data中的A,如果没有缓存的话,每次读取C时,C都回去读取A,从而触发A的get。多次触发A的get有时候是一个非常消耗性能的操作。所以Computed...
  • computed的实现原理

    2020-05-05 16:34:29
    写起来像一个方法,用起来像一个属性,基于它的依赖进行缓存的 只要跟计算属性相关的数据发生了改变,计算属性就会重新计算,不相关的值发生变化,不会重新计算计算属性 根据已知data中的值,生成一个额外的新值,且...
  • 文章目录前言实现原理分析initStateinitComputeddefineComputedWatcher对计算属性的处理总结 前言 看这篇文章之前最好先对Vue的MVVM实现原理有一定的认识,因为这是Vue的核心概念,其他的工具大部分都是在此之上...
  • 阅读本文前题,已经了解template和data数据绑定机制中的的observer和watcher原理,在此基础上,本文进一步讲解一下computed与template之间绑定关联的原理 computed原理解析图如下: 首先在vue的src/core/instance/...
  • Computed 计算属性是 Vue 中常用的一个功能,本篇文章主要介绍了Vue Computed 计算属性原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数 从使用场景上说, computed 适用一个数据被多个数据...
  • 在这之前,希望你能够对响应式原理有一些理解,因为 computed 是基于响应式原理进行工作。如果你对响应式原理还不是很了解,可以阅读我的上一篇文章:手摸手带你理解Vue响应式原理 computed 用法 想要理解原理,最...
  • 一、 什么是computed定义: 当其依赖的属性的值发生变化的时,计算属性会重新计算。反之则使用缓存中的属性值, 其设计的目的就是为了解决模板中放入太多的逻辑而导致模板过重且难以维护的...
  • 一、springboot与缓存介绍&使用缓存 1、JSR107 JAVA Cahing定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry、Expiry。 CachingProvider:定义创建、配置、获取、管理、和控制多个...
  • 浅谈Vue中计算属性computed的实现原理

    千次阅读 2018-12-18 21:11:55
    虽然目前的技术栈已由Vue转到了React,但从之前...之前也有断断续续研读过Vue的源码,但一直没有梳理总结,所以在此做一些技术归纳同时也加深自己对Vue的理解,那么今天要写的便是Vue中最常用到的API之一computed...
  • 上一篇: Vue 原理解析(八) 之 diff 算法 之前的章节, 我们按照流程介绍了vue的初始化, 虚拟Dom生成, 虚拟Dom转为真实Dom, 深入理解响应式以及diff 算法等这些核心概念。 对它内部的实现做了分析, 这些首饰篇...
  • 多次取值如果依赖的值不变化就只执行一次,是缓存的原因 依赖的值变化了,需要重新执行 dirty 表示这个值是不是脏的,默认是true (取值后变为false),依赖的值变化了。 dirty:true 2、只能有get和set,或者只有get...
  • computed

    千次阅读 2020-09-21 12:00:19
    区别 计算属性有缓存,计算属性会把函数执行一次,把结果存起来,依赖...3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算
  • computed是一个计算属性,类似于过滤器,对绑定到view的数据进行处理,计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。它用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义...
  • Vue实现computed计算属性(Vue原理之步骤六) 基于上一篇(Vue中实现数组) 根据计算属性几个特点设计思路: 1.他的值是一个函数的运行结果; 2.函数里用到的所有属性都会引起计算属性的变化; 计算属性仍然属于Vue响应式...
  • 之前的文章详细的介绍了vue3.0 相应式原理,知道了用proxy代替Object.defineProperty 的利与弊,了解了依赖收集和派发更新的大致流程,知道了vue3.0响应式原理,这节我们一起研究vue3.0中的 watch 有那些变化。...
  • 【【Vue原理】月老Computed - 白话版computed 也是响应式的简单地说Computed 如何控制缓存TIP:computed 计算就是调用 你设置的 get 函数,然后得到返回值依赖的data变化,computed如何更新简述Vue响应式原理场景设置...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,913
精华内容 2,365
关键字:

computed缓存原理