vue的響應式原理,是發佈-訂閱模式的應用。學習vue響應式原理前,先來了解下,到底什麼是發佈-訂閱模式。vue
有一天去商城買鞋子,結果沒貨了。店員拿出一個本對你說,能夠先登記電話,到貨再通知你。react
這個例子中,店員是消息的發佈者,店員有個小本本,只要貨到了,就會通知小本本上的客戶。客戶是消息的訂閱者。能夠在將來某個時候接收信息發佈者(售貨員)的消息,而不用一直輪訓消息的變化。用代碼實現這個例子。git
const sales = {
//售貨員的小本本
noteBook :[],
//在本上登記電話
add(customer){
let isExist = this.noteBook.find((item) =>{
return item.phone == customer.phone
})
if(!isExist){
this.noteBook.push(customer)
}
},
//刪除小本本上的記錄
delete(customer){
let getIndex = this.noteBook.findIndex((v) =>{
return v.phone == customer.phone
})
if(getIndex!==-1){
this.noteBook.splice(getIndex,1)
}
},
//通知顧客
notify(){
this.noteBook.forEach((item) =>{
item.upDate();
})
}
}
const customer1 = {
phone:'12345678',
upDate(){
console.log(`customer1電話號碼${this.phone}`)
},
}
const customer2 = {
phone:'87654321',
upDate(){
console.log(`customer2電話號碼${this.phone}`)
},
}
//記錄客戶1信息
sales.add(customer1)
//記錄客戶2信息
sales.add(customer2)
//到貨了,通知客戶
sales.notify()
複製代碼
Object.defineProperty 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。 get 是一個給屬性提供的 getter 方法,當咱們訪問了該屬性的時候會觸發 getter 方法;set 是一個給屬性提供的 setter 方法,當咱們對該屬性作修改的時候會觸發 setter 方法。github
一旦對象擁有了 getter 和 setter,咱們能夠簡單地把這個對象稱爲響應式對象編程
vue在何時,把哪些對象建立爲響應對象呢。設計模式
initState 在 Vue 的初始化階段,_init 方法執行的時候,會執行 initState(vm) 方法(src/core/instance/state.js)數組
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
複製代碼
initDatabash
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
.......
// observe data
observe(data, true /* asRootData */)
}
複製代碼
observe閉包
observe 的功能就是用來監測數據的變化。若是沒有_ob_屬性,而且不是Observer的實例,就爲數據添加一個Observer類。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
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
}
複製代碼
Observer類
Observer類 主要作了三件事。1.爲當前值添加_ob_屬性。2.當前值爲數組調用observeArray方法,循環調用observe方法,給裏面的每一個值變爲響應對象。3.當前值是對象,調用walk方法
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
...
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函數裏,調用defineReactive方法,給響應式對象動態添加 getter 和 setter
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
複製代碼
defineReactive.
對數據進行響應式化,爲數據添加getter/setter,爲每一個數據添加一個訂閱者列表的過程,。這個列表是getter閉包中的屬性,會記錄依賴這個數據的組件。 響應式化後的數據至關於消息的發佈者。
new Dep()建立一個數組,裏面保存全部對這個數據依賴的訂閱者
export function defineReactive (
....
) {
const dep = new Dep()
....
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true
//依賴收集
get: function reactiveGetter () {
....
},
// 派發更新
set: function reactiveSetter (newVal) {
.....
}
})
}
複製代碼
每一個組件都對應一個Watcher訂閱者。當每一個組件的渲染函數被執行時,都會將本組件的Watcher放到本身所依賴的響應式數據的訂閱者列表裏,這就至關於完成了訂閱。
當對數據對象的訪問會觸發他們的 getter 方法,那麼這些對象何時被訪問呢?
在Vue 的 mount 過程是經過 mountComponent 函數。(core/instance/lifecycle.js)。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
複製代碼
當實例化一個渲染Watcher時,.首先進入 watcher 的構造函數邏輯,而後會執行它的 this.get() 方法(src/core/observer/watcher.js).而後調用pushTarget
pushTarget(this)
複製代碼
pushTarget(src/core/observer/dep.js )把當的渲染watcher賦值給 Dep.target
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
複製代碼
在warcher構造函數裏,繼續執行this.getter,對應就是 updateComponent 函數,這實際上就是在執行:
vm._update(vm._render(), hydrating)
複製代碼
vm._render(),執行這個方法會對vm上的數據訪問,這時就觸發了數據對象的getter.
src/core/observer/index.js
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
},
複製代碼
觸發getter,調用dep.depend,也就是調用 Dep.target.addDep,這時Dep.target已經被賦值爲當前的渲染watcher。
src/core/observer/watcher.js
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)
}
}
}
複製代碼
執行dep.addSub,那麼就會執行 this.subs.push(sub),也就是說把當前的 watcher 訂閱到這個數據持有的 dep 的 subs 中,這個目的是爲後續數據變化時候能通知到哪些 subs 作準備。
當響應式數據發生變化的時候,也就是觸發了setter時,setter會負責通知該數據的訂閱者列表裏的Watcher,Watcher會觸發組件從新渲染來更新視圖