vue中的數(shù)據(jù)綁定原理的實(shí)現(xiàn)
本文主要介紹了vue中的數(shù)據(jù)綁定原理的實(shí)現(xiàn),分享給大家,也給自己留個(gè)筆記,具體如下:
vue中的響應(yīng)式數(shù)據(jù)綁定是通過數(shù)據(jù)劫持和觀察者模式來實(shí)現(xiàn)的。當(dāng)前學(xué)習(xí)源碼為vue2.0
源碼關(guān)鍵目錄
src |---core | |---instance | |---init.js | |---state.js | |---observer | |---dep.js | |---watcher.js
當(dāng)我們實(shí)例化一個(gè)vue應(yīng)用的時(shí)候,會(huì)伴隨著各種的初始化工作,相關(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) }
在這里可以看到對(duì)state的初始化工作initState()
// src/core/instance/state.js export function initState (vm: Component) { vm._watchers = [] initProps(vm) initData(vm) initComputed(vm) initMethods(vm) initWatch(vm) }
可以看到這里有對(duì)各種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++ }
這里做了一點(diǎn)判斷,判斷data方法是否返回的是一個(gè)對(duì)象,以及props中是否有與data中重名的屬性,最后會(huì)調(diào)用observe對(duì)data進(jìn)行監(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 }
可已看到這里也是做了一點(diǎn)判斷,如果有__ob__屬性的話就用它,或者如果data是數(shù)組或?qū)ο蠡蚩蓴U(kuò)展對(duì)象的話,就為它新建一個(gè)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ù)組就對(duì)數(shù)組元素再去調(diào)用observe方法做同樣的處理,如果不是,就調(diào)用walk去劫持該數(shù)據(jù),對(duì)數(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中的每一個(gè)成員都有一個(gè)對(duì)應(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,當(dāng)頁面上對(duì)應(yīng)狀態(tài)被首次渲染的時(shí)候,會(huì)為頁面上每一個(gè)使用到data的地方新建一個(gè)watcher,并將當(dāng)前watcher保存到全局變量Dep.target中,在對(duì)應(yīng)data的getter中就會(huì)調(diào)用Dep.depend方法,將當(dāng)前的watcher添加到當(dāng)前的Dep中,一個(gè)Dep對(duì)應(yīng)一個(gè)或多個(gè)watcher,著取決于,此狀態(tài)被使用的數(shù)量。當(dāng)data被修改時(shí),對(duì)應(yīng)的setter就會(huì)被觸發(fā),會(huì)調(diào)用對(duì)應(yīng)的Dep中的notify方法,通知所有觀察者,進(jìn)行更新。
這里出現(xiàn)了兩個(gè)定的類: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)用當(dāng)前target,也就是正在處理的watcher的addDep方法,并把此Dep傳進(jìn)去 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)) { // 將當(dāng)前watcher添加到當(dāng)前的Dep中 dep.addSub(this) } } } ... }
總結(jié)
vue的響應(yīng)式數(shù)據(jù)綁定主要依賴Object.defineProperty和觀察者模式。
- 在我們新建一個(gè)vue實(shí)例的時(shí)候,做一系列的初始化工作,這部分的邏輯集中在src文件夾下的core文件夾下的instance和observer文件夾內(nèi)
- 響應(yīng)式數(shù)據(jù)綁定是在狀態(tài)的初始化階段完成的,在initState方法中的initData中進(jìn)行data的數(shù)據(jù)綁定。
- 在initData中調(diào)用observe方法,為該data新建一個(gè)Observer類,然后最終調(diào)用為data中的每一個(gè)成員調(diào)用walk方法,在walk中通過defineReactive方法劫持當(dāng)前數(shù)據(jù)
- 在defineReactive中通過Object.defineProperty去修改數(shù)據(jù)的getter和setter
- 在頁面渲染的時(shí)候,頁面上每一個(gè)用到data的地方都會(huì)生成一個(gè)watcher,并將它保存到全局變量Dep.target中,watcher改變每一個(gè)觀察者,Dep用來管理觀察者。
- 然后在data的getter中將調(diào)用Dep的depend方法,將Dep.target中的watcher添加到此data對(duì)應(yīng)的Dep中,完成依賴收集
- 在data被修改的時(shí)候,對(duì)應(yīng)data的setter方法就會(huì)被出動(dòng),會(huì)調(diào)用Dep.notify()方法發(fā)布通知,調(diào)用每個(gè)watcher的uptade方法進(jìn)行更新。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue3 開始時(shí)間與結(jié)束時(shí)間比較驗(yàn)證(結(jié)束時(shí)間需要大于開始時(shí)間)
本文通過示例代碼介紹了vue3 開始時(shí)間與結(jié)束時(shí)間比較驗(yàn)證(結(jié)束時(shí)間需要大于開始時(shí)間)的相關(guān)操作,代碼簡(jiǎn)單易懂,感興趣的朋友跟隨小編一起看看吧2024-07-07vue3實(shí)現(xiàn)圖片縮放拖拽功能的示例代碼
v3-drag-zoom 是基于 vue3 開發(fā)的一個(gè)縮放拖拽組件,方便開發(fā)者快速實(shí)現(xiàn)縮放拖拽功能,效果類似地圖的縮放與拖拽,本文給大家介紹了vue3如何快速實(shí)現(xiàn)圖片縮放拖拽功能,感興趣的朋友可以參考下2024-04-04前端構(gòu)建工具Webpack、Vite區(qū)別有哪些
Webpack和Vite是兩種主流的前端構(gòu)建工具,它們?cè)诠δ?、性能和使用?chǎng)景上有所不同,Webpack提供豐富的功能和配置,適合大型復(fù)雜項(xiàng)目,但可能導(dǎo)致啟動(dòng)和構(gòu)建速度較慢,Vite基于ES模塊,支持快速的熱替換,適合小型或中等項(xiàng)目,需要的朋友可以參考下2024-10-10Vue學(xué)習(xí)筆記進(jìn)階篇之函數(shù)化組件解析
本篇文章主要介紹了Vue學(xué)習(xí)筆記進(jìn)階篇之函數(shù)化組件探究,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07如何使用Vue3+elementPlus的Tree組件實(shí)現(xiàn)一個(gè)拖拽文件夾管理
最近在做一個(gè)文件夾管理的功能,要實(shí)現(xiàn)一個(gè)樹狀的拖拽文件夾面板,里面包含兩種元素,文件夾以及文件,這篇文章主要介紹了使用Vue3+elementPlus的Tree組件實(shí)現(xiàn)一個(gè)拖拽文件夾管理?,需要的朋友可以參考下2023-09-09vue el-table實(shí)現(xiàn)行內(nèi)編輯功能
這篇文章主要為大家詳細(xì)介紹了vue el-table實(shí)現(xiàn)行內(nèi)編輯功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12