以前看了不少文章经过源码分析computed的原理,借此机会把本身对源码所理解的,因此这篇记录大多数都是对源码批注的形式写下来的,当作本身的学习笔记。写的很差还请担待。vue
vue中有个经常使用的方法Computed就是计算属性
平时咱们在获取后台返回的数据后可能会对某些数据进行加工直接放在模板中太多的逻辑会让模板太重且难以维护。例如:git
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
复制代码
在这个地方,模板再也不是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中屡次引用此处的翻转字符串时,就会更加难以处理。github
因此,对于任何复杂逻辑,你均可以使用计算属性。缓存
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
复制代码
在vue中的computed声明了计算属性,初始化vue对象的时候会直接挂载到vue实例上,能够用this.访问,同时计算属性有一个特色,他会缓存数据。当计算属性中所依赖的数据没有变化的时候计算属性不会从新计算return的值。当计算属性中依赖的数据项有某一项变化的时候他会从新计算并返回新值。bash
因为计算属性会缓存因此调用computed不会重复的get数据而且计算,可是调用methods中的方法,每调用一次就会从新执行一次,因此使用computed的性能会高于methods函数
刚刚简单的介绍了computed,那么他是如何实现的?咱们经过源码来探究一下。 (若是不了解vue响应式原理的,请移步这里) vue初始化的时候分别对data,computed,watcher 执行相应的方法。源码地址oop
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
---省去其余无用代码---
//这里对computed执行了initComputed();
if (opts.computed) initComputed(vm, opts.computed)
}
const computedWatcherOptions = { lazy: true }
下面是initComputed方法===============
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
-----省略无用代码
for (const key in computed) {
//遍历了computed对每个都进行了处理
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
//这里new Watcher,调用属性的时候,会调用get,dep回收集相应的订阅者
watchers[key] = new Watcher( vm, getter || noop, noop,computedWatcherOptions)
}
//判断声明的key不在vue实例中存在
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
-----省略无用代码
}
}
}
复制代码
这里初始化computed执行了initComputed方法,遍历computed 对每一个key执行了defineComputed()生成了订阅者Watcher 同时传入了四个参数,咱们看下getter,和computeeWatcherOptions,当computed里面写的是函数的时候,这个getter就是声明的函数,若是是对象,那么就取get方法,源码分析
computed:{
sum(){
return this.a+this.b;
},
add:{
get:function(){
return:this.a+this.b;
}
}
}
复制代码
computedWatcherOptions是个对象{lazy:true},这个参数会就是控制计算属性懒加载的。咱们接下来去看下 defineComputed(vm, key, userDef)这个方法作了什么。 defineComputed源码以下:post
//这里声明了一个属性描述符,
//这里会用到sharedPropertyDefinition: Object.defineProperty(target, key, sharedPropertyDefinition)
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
//这里判断是否是服务端渲染,一般不是ssr
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
//不是ssr执行了createComputedGetter(key),建立了新的get方法赋值给了sharedPropertyDefinition.get
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
//这里也执行了createComputedGetter()建立了个getter方法。
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
----删除无用的------------------
Object.defineProperty(target, key, sharedPropertyDefinition)
//最后对computed的key添加属性描述符,重写了get和set
//当咱们调用computed的时候调用了get方法收集了相应的依赖
}
==========createComputedGetter源码============
//createComputedGetter会返回一个新get的函数
//当再代码中调用computed的时候就会调用get,
//Object.defineProperty(target, key, sharedPropertyDefinition)这里sharedPropertyDefinition的get就是这个函数返回的computedGetter
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
//当在代码中使用computed后这里判断是否是初始化时候添加过的watcher
if (watcher) {
//当watcher.dirty是true的时候调用了evaluate(),咱们去下面看下evaluate
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
//收集使用此计算属性的订阅者
watcher.depend()
}
//这里返回计算后的值。
return watcher.value
}
}
}
//上面说到初始化computed的时候会new Watcher()还传入了{lazy:true}
//因此当前这个computed的watcher.dirty=lazy=true
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
vm._watchers.push(this)
// options
if (options) {
this.lazy = !!options.lazy
}
this.getter = expOrFn
//这里this.dirty=true
this.dirty = this.lazy // for lazy watchers
evaluate () {
//调用了evaluate()方法。调用了get方法,缓存了本身
//watcher实例中的value就是计算后的值,而后把dirty变成false.
//因此这个计算属性实例访问计算后得值就是watcher.value
this.value = this.get()
this.dirty = false
}
//get方法缓存了本身,调用了传过来的getter方法返回了计算后的值
//这个getter方法就是在计算属性中声明的函数
<!--computed:{-->
<!-- sum(){-->
<!-- return this.a+this.b-->
<!-- }-->
<!-- }-->
//这个sum函数使用了 data中声明的a变量,和b变量这里就就会调用这两个变量的属性描述符中的get方法,这两个变量就会把
当前的计算属性这个订阅者收集起来。而后返回了计算后的值,这个watcher实例中的value就是计算后的值
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
复制代码
当计算属性中依赖的数据变化的时候就会触发set方法,通知更新,update中会把this.dirty设置为true,当再次调用computed的时候就会从新计算返回新的值.性能
//当计算属性依赖的数据变化的时候就会触发set方法通知订阅者更新,就会调用update
//由于this.lazy是初始化计算属性才传入进来的
//这里判断this.lazy是true改变this.dirty=true;
//必须是计算属性中用到的数据变化得时候才会改变this.dirty=true;
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
//因此当从新触发计算属性的get方法后
还会执行这里
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
//当在代码中使用computed后这里判断是否是初始化时候添加过的watcher
if (watcher) {
//当watcher.dirty是true的时候才去计算
//为false的时候就直接返回原来的值
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
//收集使用此计算属性的订阅者
watcher.depend()
}
//这里返回计算后的值。
return watcher.value
}
}
复制代码
咱们在computed里声明了多个函数或者是对象,若是是函数则取自己做为getter函数,若是是对象则取对象中的get函数做为getter
computed:{
sum(){
return this.a+this.b
},
add(){
return this.aaa+this.bbb
},
name:{
get:function(){
return this.aa+this.bb
}
}
}
复制代码
初始化computed的时候遍历全部的computed,对每一个key都建立了个watcher,传入了一个特殊的参数lazy:true,而后调用了本身的getter方法这样就收集了这个计算属性依赖的全部data;那么所依赖的data会收集这个订阅者(当前计算属性) 初始化的同时会针对computed中的key添加属性描述符建立了独有的get方法,当调用计算属性的时候,这个get判断dirty是否为真,为真的时候表明这个计算属性须要从新计算,若是为false则直接返回value。当依赖的data 变化的时候回触发数据的set方法调用update()通知更新,会把dirty设置成true,因此computed 就会从新计算这个值。
写的很差、欢迎指正