以前看了不少文章經過源碼分析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 就會從新計算這個值。
寫的很差、歡迎指正