作者:大廷705 | 来源:互联网 | 2023-10-10 10:33
vue的相應式數據綁定原文地點:https:github.comHolyZheng…癥結詞:Object.defineProperty、觀察者形式vue中的相應式數據綁定是經由過程
vue的相應式數據綁定
原文地點:
https://github.com/HolyZheng/…
癥結詞:Object.defineProperty、觀察者形式
vue中的相應式數據綁定是經由過程數據挾制和觀察者形式來完成的。當前進修源碼為vue2.0
源碼癥結目次
src
|---core
| |---instance
| |---init.js
| |---state.js
| |---observer
| |---dep.js
| |---watcher.js
當我們實例化一個vue運用的時刻,會伴隨着種種的初始化事情,相干的初始化事情代碼在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中重名的屬性,末了會挪用observe對data舉行監聽,看一下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是數組或對象或可擴大對象的話,就為它新建一個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 defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array) {
for (let i = 0, l = items.length; i observe(items[i])
}
}
}
推斷data是不是是數組,假如是數組就對數組元素再去挪用observe要領做一樣的處置懲罰,假如不是,就挪用walk去挾制該數據,對數據的挾制重要再defineReactive要領中,正如函數名,讓數據變得相應式。看一下defineReactive要領
// src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
const dep = new Dep()
// data中的每一個成員都有一個對應的Dep,在此閉包建立。
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 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() // 宣布關照
}
})
}
遍歷狀況,修正狀況的getter和setter,當頁面上對應狀況被初次襯着的時刻,會為頁面上每一個運用到data的處所新建一個watcher,並將當前watcher保存到全局變量Dep.target中,在對應data的getter中就會挪用Dep.depend要領,將當前的watcher增加到當前的Dep中,一個Dep對應一個或多個watcher,着取決於,此狀況被運用的數目。當data被修正時,對應的setter就會被觸發,會挪用對應的Dep中的notify要領,關照一切觀察者,舉行更新。
這裏湧現了兩個定的類:Dep和Watcher,个中Dep治理觀察者,Wathcer代表觀察者
先看一下Dep
// src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
// 挪用當前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 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)
}
}
}
...
}
總結
vue的相應式數據綁定重要依靠Object.defineProperty和觀察者形式。
- 在我們新建一個vue實例的時刻,做一系列的初始化事情,這部份的邏輯集合在
src
文件夾下的core
文件夾下的instance
和observer
文件夾內 - 相應式數據綁定是在狀況的初始化階段完成的,在initState要領中的initData中舉行data的數據綁定。
- 在initData中挪用observe要領,為該data新建一個Observer類,然後終究挪用為data中的每一個成員挪用walk要領,在walk中經由過程defineReactive要領挾制當前數據
- 在defineReactive中經由過程Object.defineProperty去修正數據的getter和setter
- 在頁面襯着的時刻,頁面上每一個用到data的處所都邑天生一個watcher,並將它保存到全局變量Dep.target中,watcher轉變每一個觀察者,Dep用來治理觀察者。
- 然後在data的getter中將挪用Dep的depend要領,將Dep.target中的watcher增加到此data對應的Dep中,完成依靠網絡
- 在data被修正的時刻,對應data的setter要領就會被出動,會挪用Dep.notify()要領宣布關照,挪用每一個watcher的uptade要領舉行更新。