vue中的數(shù)據(jù)綁定原理的實現(xiàn)
本文主要介紹了vue中的數(shù)據(jù)綁定原理的實現(xiàn),分享給大家,也給自己留個筆記,具體如下:

vue中的響應(yīng)式數(shù)據(jù)綁定是通過數(shù)據(jù)劫持和觀察者模式來實現(xiàn)的。當前學習源碼為vue2.0
源碼關(guān)鍵目錄
src |---core | |---instance | |---init.js | |---state.js | |---observer | |---dep.js | |---watcher.js
當我們實例化一個vue應(yīng)用的時候,會伴隨著各種的初始化工作,相關(guān)的初始化工作代碼在init.js文件中
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
...
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)
}
在這里可以看到對state的初始化工作initState()
// src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
initProps(vm)
initData(vm)
initComputed(vm)
initMethods(vm)
initWatch(vm)
}
可以看到這里有對各種sate的初始化工作,我們看initData()
// src/core/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])
}
}
// observe data
observe(data)
data.__ob__ && data.__ob__.vmCount++
}
這里做了一點判斷,判斷data方法是否返回的是一個對象,以及props中是否有與data中重名的屬性,最后會調(diào)用observe對data進行監(jiān)聽,看一下observe
// src/core/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__
} else if (
observerState.shouldConvert &&
!config._isServer &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
return ob
}
可已看到這里也是做了一點判斷,如果有__ob__屬性的話就用它,或者如果data是數(shù)組或?qū)ο蠡蚩蓴U展對象的話,就為它新建一個Observer,看一下Observer
// src/core/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)
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]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
判斷data是不是數(shù)組,如果是數(shù)組就對數(shù)組元素再去調(diào)用observe方法做同樣的處理,如果不是,就調(diào)用walk去劫持該數(shù)據(jù),對數(shù)據(jù)的劫持主要再defineReactive方法中,正如函數(shù)名,讓數(shù)據(jù)變得響應(yīng)式??匆幌耫efineReactive方法
// src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
const dep = new Dep()
// data中的每一個成員都有一個對應(yīng)的Dep,在此閉包創(chuàng)建。
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = 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)) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify() // 發(fā)布通知
}
})
}
遍歷狀態(tài),修改狀態(tài)的getter和setter,當頁面上對應(yīng)狀態(tài)被首次渲染的時候,會為頁面上每一個使用到data的地方新建一個watcher,并將當前watcher保存到全局變量Dep.target中,在對應(yīng)data的getter中就會調(diào)用Dep.depend方法,將當前的watcher添加到當前的Dep中,一個Dep對應(yīng)一個或多個watcher,著取決于,此狀態(tài)被使用的數(shù)量。當data被修改時,對應(yīng)的setter就會被觸發(fā),會調(diào)用對應(yīng)的Dep中的notify方法,通知所有觀察者,進行更新。
這里出現(xiàn)了兩個定的類:Dep和Watcher,其中Dep管理觀察者,Wathcer代表觀察者
先看一下Dep
// src/core/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) {
// 調(diào)用當前target,也就是正在處理的watcher的addDep方法,并把此Dep傳進去
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.js
// 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)) {
// 將當前watcher添加到當前的Dep中
dep.addSub(this)
}
}
}
...
}
總結(jié)
vue的響應(yīng)式數(shù)據(jù)綁定主要依賴Object.defineProperty和觀察者模式。
- 在我們新建一個vue實例的時候,做一系列的初始化工作,這部分的邏輯集中在src文件夾下的core文件夾下的instance和observer文件夾內(nèi)
- 響應(yīng)式數(shù)據(jù)綁定是在狀態(tài)的初始化階段完成的,在initState方法中的initData中進行data的數(shù)據(jù)綁定。
- 在initData中調(diào)用observe方法,為該data新建一個Observer類,然后最終調(diào)用為data中的每一個成員調(diào)用walk方法,在walk中通過defineReactive方法劫持當前數(shù)據(jù)
- 在defineReactive中通過Object.defineProperty去修改數(shù)據(jù)的getter和setter
- 在頁面渲染的時候,頁面上每一個用到data的地方都會生成一個watcher,并將它保存到全局變量Dep.target中,watcher改變每一個觀察者,Dep用來管理觀察者。
- 然后在data的getter中將調(diào)用Dep的depend方法,將Dep.target中的watcher添加到此data對應(yīng)的Dep中,完成依賴收集
- 在data被修改的時候,對應(yīng)data的setter方法就會被出動,會調(diào)用Dep.notify()方法發(fā)布通知,調(diào)用每個watcher的uptade方法進行更新。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue+vuex+json-seiver實現(xiàn)數(shù)據(jù)展示+分頁功能
這篇文章主要介紹了vue+vuex+json-seiver實現(xiàn)數(shù)據(jù)展示+分頁功能,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-04-04
ElementUI動態(tài)渲染el-table的實現(xiàn)過程
在前端開發(fā)中,表格是不可或缺的一部分,無論是數(shù)據(jù)展示、數(shù)據(jù)錄入,還是數(shù)據(jù)分析,表格都扮演著重要的角色,而在Vue.js生態(tài)系統(tǒng)中,ElementUI提供了一個強大且靈活的表格組件——el-table,本文將帶你深入了解如何使用ElementUI動態(tài)渲染el-table,并詳細探討其原理及實現(xiàn)過程2024-08-08
vue.js2.0 實現(xiàn)better-scroll的滾動效果實例詳解
better-scroll 是一個移動端滾動的解決方案,它是基于 iscroll 的重寫。better-scroll 也很強大,不僅可以做普通的滾動列表,還可以做輪播圖、picker 等等,下面通過本文給大家介紹vue.js2.0 實現(xiàn)better-scroll的滾動效果,感興趣的朋友一起看看吧2018-08-08
Vue tagsview實現(xiàn)多頁簽導航功能流程詳解
基本上后臺管理系統(tǒng)都需要有多頁簽的功能,但是因為一些腳手架項目基本都把這個功能給集成好了,導致在學習或者修改的時候不知道該如何下手。今天這篇文章就來聊一聊,vue-element-admin項目是如何實現(xiàn)多頁簽功能的2022-09-09

