前言
在Observer原始碼部分主要有三個角色:Observer、Dep、Watcher。
Observer
看這一部分,可以帶著以下幾個問題來看:
1、滿足什麼條件可以將其變成響應式的
2、Observer是如何去分別處理傳入的陣列或者物件的?
3、有兩處new Dep,作用分別是什麼?
4、核心程式碼defineReactive幹了些什麼?
5、Dep.target是什麼?defineReactive中get時,為什麼要判斷Dep.target?
滿足什麼條件可以將其變成響應式的
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
複製程式碼
其實上述程式碼中有很多判斷,我們可以得出以下結論:
(1)必須是一個物件,且不能是vnode的型別的。
Observer是如何去分別處理傳入的陣列或者物件的
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
複製程式碼
這段程式碼就是判斷傳入的值是不是陣列,如果是陣列,走observeArray方法,如果不是陣列,那麼走walk方法。
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
複製程式碼
walk這個方法就是遍歷物件,然後給物件中的屬性值變成響應式的,遍歷完之後,整個物件就是響應式物件了。 observeArray這個方法是遍歷陣列,然後對陣列中每一個元素在走一遍響應式流程。
有兩處new Dep,作用分別是什麼
第一處:
class Observer {
constructor (value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
複製程式碼
第二處:
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
const dep = new Dep()
// 省略中間程式碼
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()
}
})
}
複製程式碼
第二處dep我們很好理解,是給get和set服務的,進行依賴收集和派發更新。
第一處dep,我們可以考慮一下,他是整個物件的一個屬性,那麼他何時進行依賴收集和派發更新。
我們可以全域性搜一下dep.depend。發現會有三處。有兩處是對ob屬性進行操作的,也就是對整個物件進行依賴收集。
在全域性搜一下dep.notify。發現有四處。有三處是對ob屬性進行操作的。分別是set和del,陣列的一個方法。
核心程式碼defineReactive幹了些什麼
這一部分程式碼可以分為三部分看:定義一些變數、Object.defineProperty、對childOb操作。
// 生成一個新的dep。
const dep = new Dep()
// 判斷這個物件這個屬性是否可以修改
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 定義getter和setter方法
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
複製程式碼
get: function reactiveGetter () {
// 獲取值
const value = getter ? getter.call(obj) : val
// 當存在Dep.target的時候進行依賴收集
if (Dep.target) {
dep.depend()
// 省略中間程式碼
}
// 返回獲取到的值
return value
},
set: function reactiveSetter (newVal) {
// 獲取原來的值
const value = getter ? getter.call(obj) : val
// 將新的值和原值進行對比,如果沒有發生改變,就直接返回
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 該屬性不能set的情況也直接返回
if (getter && !setter) return
// 給該屬性賦值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 重新對這個值進行監聽
childOb = !shallow && observe(newVal)
// 更新dep中的watcher
dep.notify()
}
複製程式碼
// 嘗試將值轉化成響應式物件
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const 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) {
// 省略中間程式碼
//因為值發生了變動,所以再一次嘗試將其變成一個響應式物件
childOb = !shallow && observe(newVal)
}
})
複製程式碼
Dep.target是什麼
其實Dep.target是一個全域性變數,更是一個wathcer。在dep檔案中的原始碼如下:
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
複製程式碼
defineReactive中get時,為什麼要判斷Dep.target
因為所有定義在data中的值,都會被變成響應式物件,但是每一個不一定有watcher。watcher分為三種:render中生成的watcher、使用者自定義的watcher、computed。
上述流程中,當生成一個新的renderWatcher的時候,便會走get流程,然後進行依賴收集,如果沒有Dep.target,說明這個值並沒有對應的watcher,所以不需要進行依賴收集。 當更新的是時候,又回進行一次依賴收集。