
Paste_Image.png
Vue 是通過劫持對象的 setter 和 getter 方法來做數(shù)據(jù)綁定的,核心的方法是Object.defineProperty()
要實現(xiàn) MVVM 的雙向綁定,需要實現(xiàn)以下幾點:
- Observer: 為所有數(shù)據(jù)添加監(jiān)聽器 Dep
- Dep (監(jiān)聽器), data 中每個對象(和子對象)都有持有一個該對象, 當所綁定的數(shù)據(jù)有變更時, dep.notify() 方法被調用, 通知 Watcher
- Compile (HTML指令解析器),對每個元素節(jié)點的指令進行掃描和解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應的更新函數(shù)
- Watcher,作為連接 Observer 和 Compile 的橋梁,當 Compile 解析指令時會生成一個 Watcher 并給它綁定一個 update 方法 , 并添加到當前正在解析的指令所依賴的對象的 Dep 對象上
這樣形成的對象結構圖就成了下面這樣:

Paste_Image.png

Paste_Image.png
源碼分析
Vue 對象實例化后會調用到 initData 方法
源碼路徑: src/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else {
proxy(vm, keys[i]) //把 data 的屬性代理到 vm 實例上
}
}
// observe data
observe(data) // 觀察 data 對象
data.__ob__ && data.__ob__.vmCount++
}
源碼路徑: src/observer/index.js
export function observe (value: any): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__ // 如果參數(shù) value 對象中已經(jīng)存在有效的 __ob__ 對象就直接賦值
} else if ( observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue) {
ob = new Observer(value) // 新建一個 Observer 對象
}
return ob
}
源碼路徑: src/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) // value.__ob__ = this; 將本對象( Observer)賦值給 value 的 __ob__ 屬性, 所以 Vue.$data 中每個對象都 __ob__ 屬性,包括 Vue.$data 對象本身
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]) // 給對象中每個屬性定義 setter 和 getter 方法
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 如果是列表繼續(xù)執(zhí)行 observe 方法, 其中會繼續(xù)新建 Observer 對象, 直到窮舉完畢執(zhí)行 walk 方法
}
}
}
/**
* Define a reactive property on an Object.
*/
export function defineReactive (obj: Object, key: string, val: any, customSetter?: Function) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key) //
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get // 獲取已經(jīng)實現(xiàn)的 getter 方法
const setter = property && property.set // 獲取已經(jīng)實現(xiàn)的 setter 方法
let childOb = observe(val)
Object.defineProperty(obj, key, { // 給 obj 對象中的 key 屬性定義 setter 和 getter 方法
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // Dep.target 全局變量指向的就是當前正在解析指令的 Complier 生成的 Watcher
dep.depend() // 會執(zhí)行到 dep.addSub(Dep.target), 將 Watcher 添加到 Dep 對象的 Watcher 列表中
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
//....
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify() // 如果數(shù)據(jù)被重新賦值了, 調用 Dep 的 notify 方法, 通知所有的 Watcher
}
})
}
源碼路徑: src/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stablize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知所有綁定的 Watcher
}
}
}
源碼路徑: src/observer/watcher.js
export default class Watcher {
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) // 大部分情況會執(zhí)行該方法, 加入到一個等待隊列, 在下一次刷新觸發(fā)的時候一起執(zhí)行
}
}
}
參考資料
剖析Vue原理&實現(xiàn)雙向綁定MVVM
Vue 雙向數(shù)據(jù)綁定原理分析