繼上一篇:深刻理解Vue的watch實現原理及其實現方式 繼續講解vue
Vue的computed實現相對於watch和data來講比較難以理解,要真正的理解computed的工做方式,你須要深刻理解Vue的雙向數據綁定原理和實現方式。react
若是你還不是很理解推薦你先看此文章:數組
完全搞懂Vue針對數組和雙向綁定(MVVM)的處理方式
瀏覽器
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值改變時纔會重新計算bash
一、computed是如何初始化,初始化以後幹了些什麼ide
二、爲什麼觸發data值改變時computed會重新計算函數
三、computed值爲何說是被緩存的呢,如何作的oop
如下大部分代碼摘自Vue源碼。學習
若是你看到了這裏,就當作你已經深刻理解了Vue的MVVM原理及其實現方式。相關Vue的MVVM實現直接取自上一篇文章。
//標識當前的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()
}複製代碼
//去重 防止重複收集
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值時會重新執行,得到新的計算屬性值。
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
})
}複製代碼
//空函數
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
}
}
}
}複製代碼
//一、首先來建立一個Vue構造函數:
function Vue(){
}
//二、設置data和computed的值:
let data={
name:'Hello',
}
let computed={
getfullname:function(){
console.log('-----走了computed 之 getfullname------')
console.log('新的值爲:'+data.name + ' - world')
return data.name + ' - world'
}
}
//三、實例化Vue並把data掛載到Vue上
let vue = new Vue()
vue.data = data
//四、建立Watcher對象
let updateComponent = (vm)=>{
// 收集依賴
data.name
}
let watcher1 = new Watcher(vue,updateComponent,()=>{})
//五、初始化Data並收集依賴
observe(data)
//六、初始化computed
let watcher2 = new initComputed(vue,computed)複製代碼
//首先得到一次getfullname
vue.getfullname
//第二次調用getfullname看看會有什麼變化呢
vue.getfullname複製代碼
//爲data.name賦值
data.name = 'Hi'複製代碼
//name值變動以後再次執行會是什麼結果呢
vue.getfullname
//再執行一次
vue.getfullname複製代碼
/*----------------------------------------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複製代碼
如有疑問歡迎交流。