系列文章目錄
vue原始碼學習——初始化data
vue原始碼學習——回應式資料
文章目錄
- 系列文章目錄
- 前言
- 一、observe()
- 二、Observer類
- 三、Dep類
- 三、Watcher
- 總結
前言
在《vue原始碼學習——初始化data》一文中,知道了在new Vue()時做了一系列初始化操作,其中在初始化data資料時,利用observe(data,true)方法,對資料屬性進行了觀察,下面來具體看下是如何對data進行的觀察,從而實作資料驅動~
vue版本:v2.5.21

一、observe()
代碼如下(src/core/observer/index.js):
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void // 定義observer實體 并最后回傳
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() && // 判斷是否服務端渲染,node環境
(Array.isArray(value) || isPlainObject(value)) && // 只有當資料物件是陣列或純物件的時候
Object.isExtensible(value) && // 物件可擴展 即不是凍結物件等等
!value._isVue //不是Vue實體
) {
ob = new Observer(value) // 創建一個 Observer 實體
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
- 本函式,接收2個引數:
value【純物件資料】、asRootData【是否是根級資料,用來統計vm數量ob.vmCount++】 - 經過一系列入參判斷,最侄訓傳一個ob物件,ob為一個Observer實體物件:
ob = new Observer(value)
二、Observer類
代碼如下(src/core/observer/index.js):
export class Observer {
value: any; // 純物件
dep: Dep;
vmCount: number; // number of vms that have this object as root $data,vm數量
constructor (value: any) {
this.value = value // 純物件,observe函式已經判定過
this.dep = new Dep() // 依賴
this.vmCount = 0 // vm數量,observe函式賦值過此值
def(value, '__ob__', this) // 給資料定義了__ob__屬性,值為當前的Observe實體
if (Array.isArray(value)) { // 若此物件是個陣列
if (hasProto) { // [util/env.js] : hasProto = '__proto__' in {}
protoAugment(value, arrayMethods) // [./array.js] value.__proto__ = arrayMethods , 重寫了陣列的7個方法,
} else {
copyAugment(value, arrayMethods, arrayKeys) //
}
this.observeArray(value) // 觀察陣列,遍歷陣列,依次觀察各項:observe(items[i])
} else { // 純物件,呼叫walk,繼續走流程
this.walk(value)
}
}
- 在實體初始化constructor時,初始化了3個自身屬性:
value【觀察的物件】、dep【依賴收集容器】、vmCount【vm個數】 def(value, '__ob__', this),value物件下,掛載__ob__屬性,對應的值為實體本身(this),也就是在vue中所有data下都掛載一個__ob__屬性,為觀察資料添加觀察者參考,如下:
const data = {
a: 1,
__ob__: {
value: data, // value 屬性指向 data 資料物件本身,這是一個回圈參考
dep: dep實體物件, // new Dep()
vmCount: 0
}
}
Array.isArray(value)判定是否是陣列物件,這里對陣列物件做了特殊處理,重寫了陣列的7個方法(在observer/array.js),observeArray(value)遍歷陣列各項進行觀察observe(items[i]),又回到了最初流程,- 若value不是陣列物件,walk(value)繼續走流程:
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 遍歷可列舉屬性,為每個屬性 defineReactive
}
}
defineReactive(obj, keys[i])為每個物件屬性進行getter,setter設定,把每個屬性資料變成了回應式
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { // 當且僅當指定物件的屬性描述可以被改變或者屬性可被洗掉時,configurable為true,
return
}
// cater for pre-defined getter/setters
const getter = property && property.get // 獲取該屬性的訪問器函式(getter)
const setter = property && property.set // 獲取該屬性的設定器函式(setter), 如果沒有設定器, 該值為undefined
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// !shallow時,observe(val)遞回呼叫深度監聽
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可列舉
configurable: true, // 可修改
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val // 有 `getter ` 就是自定義的`get`函式,如果沒有就是取`val`的值,
if (Dep.target) { // Dep.target 全域變數指向的就是當前正在決議指令的Complie生成的 Watcher
dep.depend() // 添加依賴
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val // 首先是判斷了是使用有 `getter ` 就是自定義的`get`函式,如果沒有就是取`val`的值,
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return // 有getter,沒setter時,為只讀狀態,不可修改,繼而無需觀察
if (setter) { // 如果有`setter`就呼叫`setter`處理,否則直接復制給`val`,
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal) // `shallow `為false的話,就重新監聽`newVal`的值,
dep.notify() // 呼叫 Dep 實體的 notify 方法, 更新
}
})
}
- 通過
Object.getOwnPropertyDescriptor(obj, key)獲取該欄位可能已有的屬性描述物件,屬性key對應的屬性描述符,若屬性存在于obj上,回傳其屬性描述符物件(property descriptor),否則返 undefined, Object.defineProperty是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因,property.get、property.set獲取該屬性的訪問器函式(getter),設定器函式(setter),若無回傳undefinedgetter中,Dep.target【實則是一個Watcher】存在時,在依賴容器dep中添加依賴dep.depend()(下面講到)setter中,更新完最新值后,呼叫了dep.notify()發出了DOM更新通知- 這里多個邏輯涉及到了dep,下面看下Dep類
三、Dep類
代碼如下(src/core/observer/dep.js):
let uid = 0 // 全域變數,唯一標識
export default class Dep {
static target: ?Watcher; // 當前正在計算的一個watcher物件,
id: number; // 每個Dep實體都有唯一的ID
subs: Array<Watcher>; // subs用于存放依賴
constructor () {
this.id = uid++ // 保證每個Dep實體都有唯一的ID
this.subs = []
}
// 添加依賴
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除依賴
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) { // 對應的就是下面的全域變數Dep.target對應的watcher
Dep.target.addDep(this) // 呼叫當前watcher的appDep(this)
}
}
// 通知更新,defineReactive dep=new Dep() ==>setter dep.notify() =>watcher update() ==> ...更新UI
notify () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知所有系結 Watcher,呼叫watcher的update()
}
}
}
// 這是全域變數,因為任何時候都可能只有一個watcher正在評估,
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]
}
- Dep是資料訂閱和通知watcher的事件處理方
- 在
Observer實體中,物件屬性getter中呼叫Dep實體的dep.depend(),最終呼叫的是Dep.target.addDep(this),Dep.target全域變數存盤的是當前的Watcher實體,也就是呼叫的watcher的addDep()方法(下面提到) - 在
Observer實體中,物件屬性setter中呼叫Dep實體的dep.notify(),最終呼叫的是subs[i].update(),subs存盤的所有的watchers,陣列遍歷呼叫watcher的update()方法,也就是說當observer對應setter觸發時,會向對應的dep中全部watchers發送更新訊息 Dep.target代表著當前正在計算的watcher實體,通過在watcher中呼叫Dep的pushTarget()、popTarget()更改Dep.target
三、Watcher
代碼如下(src/core/observer/watcher.js):
export default class Watcher {
//...
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
//...
}
每個組件實體都對應一個 watcher 實體,它會在組件渲染的程序中把“接觸”過的資料 property 記錄為依賴,之后當依賴項的setter 觸發時,會通知 watcher,從而使它關聯的組件重新渲染,
- 訂閱者,通過Dep,在觀察資料執行getter時添加訂閱關系,
- watcher的
addDep()方法,繞了一圈,又呼叫的是dep.addSub(this),Dep實體的addSub()方法就是在this.subs依賴陣列里面增加一個依賴, - watcher的
notify()方法,會觸發組件重新渲染,
總結
大體流程:

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/258164.html
標籤:其他
下一篇:vsomeipUserGuide
