首頁 > web前端 > js教程 > 主體

如何使用Vue實作Observer

php中世界最好的语言
發布: 2018-05-26 10:50:45
原創
1684 人瀏覽過

這次帶給大家如何使用Vue實作Observer,使用Vue實作Observer的注意事項有哪些,以下就是實戰案例,一起來看一下。

導語:

本文是對Vue 官方文件深入響應式原則(https://cn.vuejs.org/v2/guide /reactivity.html)的理解,並透過原始碼還原來實現過程。

響應式原理可分為兩步,依賴收集的過程與觸發-重新渲染的過程。依賴收集的過程,有三個很重要的類,分別是 Watcher、Dep、Observer。本文主要解讀 Observer 。

這篇文章講解上篇文章沒有覆蓋到的Observer 部分的內容,還是先看官網這張圖:

Observer 最主要的作用就是實作了上圖中touch -Data(getter) - Collect as Dependency這段過程,也就是依賴收集的過程。

還是以下面的程式碼為範例進行梳理:

(註:左右滑動即可查看完整程式碼,下同)

varvm = newVue({
el: '#demo',
data: {
firstName: 'Hello',
fullName: ''
},
watch: {
firstName(val) {
this.fullName = val + 'TalkingData';
},
}
})
登入後複製

在原始碼中,透過還原Vue進行實例化的過程,從開始一步一步到Observer 類別的源碼依次為(省略了很多不在本篇文章討論的程式碼):

// src/core/instance/index.js
functionVue(options) {
if(process.env.NODE_ENV !== 'production'&&
!(thisinstanceofVue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
constvm: Component = this
// ...
initState(vm)
// ...
}
// src/core/instance/state.js
exportfunctioninitState(vm: Component) {
// ...
constopts = vm.$options
if(opts.data) {
initData(vm)
}
// ...
}
functioninitData(vm: Component) {
letdata = vm.$options.data
data = vm._data = typeofdata === 'function'
? getData(data, vm)
: data || {}
// ...
// observe data
observe(data, true/* asRootData */)
}
登入後複製

在initData 方法中,開始了對data 項中的數據進行“觀察”,會將所有資料的變成observable 的。接下來看observe 方法的程式碼:

// src/core/observer/index.js
functionobserve(value: any, asRootData: ?boolean): Observer| void{
// 如果不是对象,直接返回
if(!isObject(value) || value instanceofVNode) {
return
}
letob: Observer | void
if(hasOwn(value, 'ob') && value.ob instanceofObserver) {
// 如果有实例则返回实例
ob = value.ob
} elseif(
// 确保value是单纯的对象,而不是函数或者是Regexp等情况
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 实例化一个 Observer
ob = newObserver(value)
}
if(asRootData && ob) {
ob.vmCount++
}
returnob
}
登入後複製

observe 方法的作用是給data 建立一個Observer 實例並傳回,如果data 有ob屬性了,表示已經有Observer 實例了,則傳回現有的實例。 Vue 的響應式資料都會有一個ob的屬性,裡面存放了該屬性的Observer 實例,防止重複綁定。再來看new Observer(value) 過程中發生了什麼事:

exportclassObserver{
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value
this.dep = newDep()
this.vmCount = 0
def(value, 'ob', this)
if(Array.isArray(value)) {
// ...
this.observeArray(value)
} else{
this.walk(value)
}
}
walk (obj: Object) {
constkeys = Object.keys(obj)
for(leti = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray (items: Array<any>) {
for(leti = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
登入後複製

透過原始碼可以看到,實例化Observer 過程中主要是做了兩個判斷。如果是數組,則對數組裡面的每一項再次調用oberser 方法進行觀察;如果是非數組的對象,遍歷對象的每一個屬性,對其調用defineReactive 方法。這裡的defineReactive 方法就是核心!透過使用Object.defineProperty 方法對每個需要被觀察的屬性加入get/set,完成依賴收集。依賴收集過後,每個屬性都會有一個Dep 來保存所有Watcher 物件。按照文章最開始的例子來講,就是對firstName和fullName分別添加了get/set,並且它們各自有一個Dep 實例來保存各自觀察它們的所有Watcher 物件。以下是defineReactive 的原始碼:

exportfunctiondefineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
constdep = newDep()
// 获取属性的自身描述符
constproperty = Object.getOwnPropertyDeor(obj, key)
if(property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 检查属性之前是否设置了 getter/setter
// 如果设置了,则在之后的 get/set 方法中执行设置了的 getter/setter
constgetter = property && property.get
constsetter = property && property.set
// 通过对属性再次调用 observe 方法来判断是否有子对象
// 如果有子对象,对子对象也进行依赖搜集
letchildOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: functionreactiveGetter() {
// 如果属性原本拥有getter方法则执行
constvalue = getter ? getter.call(obj) : val
if(Dep.target) {
// 进行依赖收集
dep.depend()
if(childOb) {
// 如果有子对象,对子对象也进行依赖搜集
childOb.dep.depend()
// 如果属性是数组,则对每一个项都进行依赖收集
// 如果某一项还是数组,则递归
if(Array.isArray(value)) {
dependArray(value)
}
}
}
returnvalue
},
set: functionreactiveSetter(newVal) {
// 如果属性原本拥有getter方法则执行
// 通过getter方法获取当前值,与新值进行比较
// 如果新旧值一样则不需要执行下面的操作
constvalue = getter ? getter.call(obj) : 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(setter) {
// 如果属性原本拥有setter方法则执行
setter.call(obj, newVal)
} else{
// 如果原本没有setter则直接赋新值
val = newVal
}
// 判断新的值是否有子对象,有的话继续观察子对象
childOb = !shallow && observe(newVal)
// 通知所有的观察者,更新状态
dep.notify()
}
})
}
登入後複製

依照原始碼中的中文註釋,應該可以明白defineReactive 執行的過程中做了哪些工作。其實整個過程就是遞歸,為每個屬性加上getter/setter。對於getter/setter,同樣也需要對每一個屬性進行遞歸(判斷子物件)的完成觀察者模式。對於getter,用來完成依賴收集,即原始碼中的dep.depend()。對於setter,一旦一個資料觸發其set方法,就會發布更新訊息,通知這個資料的所有觀察者也要改變。即原始碼中的dep.notify()。

相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

推薦閱讀:

怎麼使用vue檔案樹元件

JavaScript EventEmitter底層邏輯剖析

###################################################################################################### ###

以上是如何使用Vue實作Observer的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板