资讯专栏INFORMATION COLUMN

vue源码-对于「计算属性」的理解

xiaochao / 763人阅读

摘要:源码对于计算属性的理解这是我最近学习源码的一个个人总结和理解,所以可能并不适合每一位读者本文的整体脉络如下,首先尽可能去掉细节,对计算属性源码的大致实现有一个了解,然后举一例子,分别谈谈计算属性依赖收集和派发更新的流程。

vue源码-对于「计算属性」的理解

这是我最近学习vue源码的一个个人总结和理解,所以可能并不适合每一位读者

本文的整体脉络如下,首先尽可能去掉细节,对计算属性源码的大致实现有一个了解,然后举一例子,分别谈谈计算属性依赖收集和派发更新的流程。

计算属性的源码实现

举例来说,谈谈页面初次渲染时,整个依赖收集的过程

举例来说,计算属性的依赖被修改时,派发更新的过程

另外推荐2个开源的vue源码分析集合

https://ustbhuangyi.github.io...

http://hcysun.me/vue-design/a...

计算属性的源码实现

_init() --> initState() --> initComputed()

1.遍历computed选项,2.实例化computed watcher 3.defineComputed()

defineComputed()核心就是把计算属性用Object.defineProperty包装成响应式对象,而getter就是把用户传入的函数作为getter

但是准确的说,是用户传递的fn的返回值是作为计算属性getter的return值,但是除此之外计算属性在getter时还做了一些其他的操作

1是watch.depend() 2.return watch.evaluate()。 也就是1.收集依赖2.把值返回

this._init() : 重点关注重点init方法中initState
    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")
initState() 重点关注这一句 if (opts.computed) initComputed(vm, opts.computed)
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 */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
initComputed()  核心就是遍历computed,每次循环都实例化一个computed watch,并且用defineComputed把计算属性包装成响应式对象
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)
      }
    }
  }
}
defineComputed()  核心就是Object.defineProperty ,大段代码都是在给它设置get和set
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)
}
createComputedGetter  : 计算属性的getter就是这个computedGetter,做了2件事情,1.收集render watcher作为自己的依赖,2.调用用户的那个函数作为返回值
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      watcher.depend()
      return watcher.evaluate()
    }
  }
}

到目前为止就结束了,也就是说初始化computed watcher都没有求值

直到render时,才会触发computed watcher的getter

举例来说,谈谈页面初次渲染时,整个依赖收集的过程

比如我们有一个计算属性,并且fullName是渲染在模板中的。

computed: {
    fullName(){
        return this.firstName + this.lastName
    }
}

那么页面初次渲染时,整个依赖收集的过程如下

render函数执行时,会读取计算属性fullName,那么会触发fullName的getter,那么就会执行到watch.depend()和return watch.evaluate()

这个computed watcher的depend()会把render watcher作为依赖收集到它的subs里。

这个computed watcher的evaluate()主要是把调用了用户给的那个函数,求值并返回

最后值得注意的是,调用用户的函数,也就是执行了this.firstName + this.lastName ,那么也会触发他们的getter,所以他们也会把computed watcher作为依赖,收集到subs里,将来如果被修改的话,用通知subs里的watch调用update,也就是去派发更新

举例来说,计算属性的依赖被修改时,派发更新的过程

当this.firstName或者this.lastName被修改时,会触发他们的setter,setter就干两个事情。1是赋值 2是派发更新

所以会通知他们的subs里的watch去调用自己的update方法。其中也包括computed watcher,

那么computed watcher在update方法跟普通的user watcher的update存在区别,computed watcher并不是直接推入异步更新队列,而是 this.dep.notify()发出之前计算属性所收集的依赖去派发更新,其中就包括render watcher,调用render watcher的update就会实现视图更新了

注意this.getAndInvoke的作用,就是如果this.lastName和this.firstName变化了,但是经过计算之后,计算属性的值不变,那么也不会触发notify的,也就不会更新视图

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)
  }
}

这也是为什么,我们说计算属性的依赖属性不被修改的话,计算属性就不会变化。因为getter就是你那个函数嘛,而且其实就算依赖变化了,只要计算之后的计算属性变,也不会触发视图更新。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99264.html

相关文章

  • [Vue.js进阶]从源码角度剖析计算属性原理

    摘要:前言最近在学习计算属性的源码,发现和普通的响应式变量内部的实现还有一些不同,特地写了这篇博客,记录下自己学习的成果文中的源码截图只保留核心逻辑完整源码地址可能需要了解一些响应式的原理版本计算属性的概念一般的计算属性值是一个函数,这个函数showImg(https://user-gold-cdn.xitu.io/2019/5/6/16a8b98f1361f6f6); 前言 最近在学习Vue计...

    melody_lql 评论0 收藏0
  • Vue 数据响应式原理

    摘要:接下来,我们就一起深入了解的数据响应式原理,搞清楚响应式的实现机制。回调函数只是打印出新的得到的新的值,由执行后生成。及异步更新相信读过前文,你应该对响应式原理有基本的认识。 前言 Vue.js 的核心包括一套响应式系统。 响应式,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。 举个简单的例子,对于模板: {{ name ...

    Mike617 评论0 收藏0
  • Vue源码详细解析:transclude,compile,link,依赖,批处理...一网打尽,全解

    摘要:先说遍历,很简单,如下行左右代码就足够遍历一个对象了遇到普通数据属性,直接处理,遇到对象,遍历属性之后递归进去处理属性,遇到数组,递归进去处理数组元素。这样改进之后我就不需要对数组元素进行响应式处理,只是遇到数组的时候把数组的方法变异即可。 用了Vue很久了,最近决定系统性的看看Vue的源码,相信看源码的同学不在少数,但是看的时候却发现挺有难度,Vue虽然足够精简,但是怎么说现在也有1...

    yy736044583 评论0 收藏0
  • vue源码分析系列之响应式数据(三)

    摘要:并在内执行了函数,在函数内部,访问了。至此知道了它依赖于。需要根据最新的计算。本例中收集到了依赖并且也被告知观察了他们。文章链接源码分析系列源码分析系列之环境搭建源码分析系列之入口文件分析源码分析系列之响应式数据一源码分析系列之响应式数据二 前言 上一节着重讲述了initData中的代码,以及数据是如何从data中到视图层的,以及data修改后如何作用于视图。这一节主要记录initCo...

    shenhualong 评论0 收藏0
  • 自己实现MVVM(Vue源码解析)

    摘要:无论是双向绑定还是单向绑定,都是符合思想的。看了的源码后不难发现的双向绑定的实现也就是在表单元素上添加了事件,可以说双向绑定是单向绑定的一个语法糖。 前言 本文会带大家手动实现一个双向绑定过程(仅仅涵盖一些简单的指令解析,如:v-text,v-model,插值),当然借鉴的是Vue1的源码,相信大家在阅读完本文后对Vue1会有一个更好的理解,源代码放到了github,由于本人水平有限,...

    ?xiaoxiao, 评论0 收藏0

发表评论

0条评论

xiaochao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<