亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解Vue數(shù)據(jù)驅(qū)動(dòng)原理

瀏覽:7日期:2022-10-27 08:52:48

前言

Vue區(qū)別于傳統(tǒng)的JS庫(kù),例如JQuery,其中一個(gè)最大的特點(diǎn)就是不用手動(dòng)去操作DOM,只需要對(duì)數(shù)據(jù)進(jìn)行變更之后,視圖也會(huì)隨之更新。 比如你想修改div#app里的內(nèi)容:

/// JQuery<div id='app'></div><script> $(’#app’).text(’lxb’)</script>

<template><div id='app'>{{ message }}</div> <button @click='change'>點(diǎn)擊修改message</button></template><script>export default {data () { return { message: ’lxb’ } }, methods: { change () { this.message = ’lxb1’ // 觸發(fā)視圖更新 }}}</script>

在代碼層面上的最大區(qū)別就是,JQuery直接對(duì)DOM進(jìn)行了操作,而Vue則對(duì)數(shù)據(jù)進(jìn)行了操作,接下來(lái)我們通過(guò)分析源碼來(lái)進(jìn)一步分析,Vue是如何做到數(shù)據(jù)驅(qū)動(dòng)的,而數(shù)據(jù)驅(qū)動(dòng)主要分成兩個(gè)部分依賴(lài)收集和派發(fā)更新。

數(shù)據(jù)驅(qū)動(dòng)

// _init方法中initLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, ’beforeCreate’)initInjections(vm) // resolve injections before data/propsinitState(vm) // 重點(diǎn)分析initProvide(vm) // resolve provide after data/propscallHook(vm, ’created’)

在Vue初始化會(huì)執(zhí)行_init方法,并調(diào)用initState方法. initState相關(guān)代碼在src/core/instance/state.js下

export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) // 初始化Props if (opts.methods) initMethods(vm, opts.methods) // 初始化方法 if (opts.data) { initData(vm) // 初始化data } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) // 初始化computed if (opts.watch && opts.watch !== nativeWatch) { // 初始化watch initWatch(vm, opts.watch) }}

我們具體看看initData是如何定義的。

function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === ’function’ // 把data掛載到了vm._data上 ? getData(data, vm) // 執(zhí)行 data.call(vm) : data || {} if (!isPlainObject(data)) { data = {} // 這也是為什么 data函數(shù)需要返回一個(gè)object不然就會(huì)報(bào)這個(gè)警告 process.env.NODE_ENV !== ’production’ && warn( ’data functions should return an object:n’ + ’https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function’, vm ) } // proxy data on instance const keys = Object.keys(data) // 取到data中所有的key值所組成的數(shù)組 const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== ’production’) { if (methods && hasOwn(methods, key)) { // 避免方法名與data的key重復(fù) warn( `Method '${key}' has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { // 避免props的key與data的key重復(fù) process.env.NODE_ENV !== ’production’ && warn( `The data property '${key}' is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { // 判斷是不是保留字段 proxy(vm, `_data`, key) // 代理 } } // observe data observe(data, true /* asRootData */) // 響應(yīng)式處理}

其中有兩個(gè)重要的函數(shù)分別是proxy跟observe,在往下閱讀之前,如果還有不明白Object.defineProperty作用的同學(xué),可以點(diǎn)擊這里進(jìn)行了解,依賴(lài)收集跟派發(fā)更新都需要依靠這個(gè)函數(shù)進(jìn)行實(shí)現(xiàn)。

proxy

proxy分別傳入vm,’_data’,data中的key值,定義如下:

const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop}export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition)}

proxy函數(shù)的邏輯很簡(jiǎn)單,就是對(duì)vm._data上的數(shù)據(jù)進(jìn)行代理,vm._data上保存的就是data數(shù)據(jù)。通過(guò)代理的之后我們就可以直接通過(guò)this.xxx訪問(wèn)到data上的數(shù)據(jù),實(shí)際上訪問(wèn)的就是this._data.xxx。

observe

oberse定義在src/core/oberse/index.js下,關(guān)于數(shù)據(jù)驅(qū)動(dòng)的文件都存放在src/core/observe這個(gè)目錄中:

export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { // 判斷是否是對(duì)象或者是VNode return } let ob: Observer | void // 是否擁有__ob__屬性 有的話(huà)證明已經(jīng)監(jiān)聽(tīng)過(guò)了,直接返回該屬性 if (hasOwn(value, ’__ob__’) && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && // 能否被觀察 !isServerRendering() && // 是否是服務(wù)端渲染 (Array.isArray(value) || isPlainObject(value)) && // 是否是數(shù)組、對(duì)象、能否被擴(kuò)展、是否是Vue函數(shù) Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) // 對(duì)value進(jìn)行觀察 } if (asRootData && ob) { ob.vmCount++ } return ob}

observe函數(shù)會(huì)對(duì)傳入的value進(jìn)行判斷,在我們初始化過(guò)程會(huì)走到new Observer(value),其他情況可以看上面的注釋。

Observer類(lèi)

export class Observer { value: any; // 觀察的數(shù)據(jù) dep: Dep; // dep實(shí)例用于 派發(fā)更新 vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 把__ob__變成不可枚舉的,因?yàn)闆](méi)有必要改變watcher本身 def(value, ’__ob__’, this) 會(huì)執(zhí)行 value._ob_ = this(watcher實(shí)例)操作 if (Array.isArray(value)) { // 當(dāng)value是數(shù)組 if (hasProto) { protoAugment(value, arrayMethods) // 重寫(xiě)Array.prototype的相關(guān)方法 } else { copyAugment(value, arrayMethods, arrayKeys) // 重寫(xiě)Array.prototype的相關(guān)方法 } this.observeArray(value) } else { this.walk(value) // 當(dāng)value為對(duì)象 } } /** * Walk through all properties 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]) // 對(duì)數(shù)據(jù)進(jìn)行響應(yīng)式處理 } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) // 遍歷value數(shù)組的每一項(xiàng)并調(diào)用observe函數(shù),進(jìn)行響應(yīng)式處理 } }}

Observe類(lèi)要做的事情通過(guò)查看源碼也是清晰明了,對(duì)數(shù)據(jù)進(jìn)行響應(yīng)式處理,并對(duì)數(shù)組的原型方法進(jìn)行重寫(xiě)!defineReactive函數(shù)就是實(shí)現(xiàn)依賴(lài)收集和派發(fā)更新的核心函數(shù)了,實(shí)現(xiàn)代碼如下。

依賴(lài)收集

defineReactive

export function defineReactive ( obj: Object, // data數(shù)據(jù) key: string, // data中對(duì)應(yīng)的key值 val: any, // 給data[key] 賦值 可選 customSetter?: ?Function, // 自定義setter 可選 shallow?: boolean // 是否對(duì)data[key]為對(duì)象的值進(jìn)行observe遞歸 可選) { const dep = new Dep() // Dep實(shí)例 **每一個(gè)key對(duì)應(yīng)一個(gè)Dep實(shí)例** const property = Object.getOwnPropertyDescriptor(obj, key) // 拿到對(duì)象的屬性描述 if (property && property.configurable === false) { // 判斷對(duì)象是否可配置 return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { // 沒(méi)有g(shù)etter或者有setter,并且傳入的參數(shù)有兩個(gè) val = obj[key] } let childOb = !shallow && observe(val) // 根據(jù)shallow,遞歸遍歷val對(duì)象,相當(dāng)于val當(dāng)做data傳入 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 當(dāng)前的全部的Watcher實(shí)例 dep.depend() // 把當(dāng)前的Dep.target加入到dep.subs數(shù)組中 if (childOb) { // 如果val是對(duì)象, childOb.dep.depend() // 會(huì)在value._ob_的dep.subs數(shù)組中加入Dep.target, 忘記ob實(shí)例屬性的同學(xué)可往回翻一番 if (Array.isArray(value)) { dependArray(value) // 定義如下,邏輯也比較簡(jiǎn)單 } } } return value }, set: function reactiveSetter (newVal) { // .... } })}function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() // 如果e是響應(yīng)式數(shù)據(jù),則往e._ob_.dep.subs數(shù)組中加入Dep.target if (Array.isArray(e)) { dependArray(e) // 遞歸遍歷 } }}

代碼中多次用到了Dep類(lèi)和Dep.target,理解清楚了它們的作用,我們就離Vue數(shù)據(jù)驅(qū)動(dòng)的原理更近一步了,相關(guān)的代碼如下:

Dep

let uid = 0/** * A dep is an observable that can have multiple * directives subscribing to it. */export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ // 每一個(gè)dep都有一個(gè)唯一的ID this.subs = [] // 存放watcher實(shí)例的數(shù)組 } addSub (sub: Watcher) { this.subs.push(sub) // 往this.subs加入watcher } removeSub (sub: Watcher) { remove(this.subs, sub) // 刪除this.subs對(duì)應(yīng)的watcher } depend () { if (Dep.target) { // watcher.addDep(this) actually Dep.target.addDep(this) // 在watcher類(lèi)中查看 } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== ’production’ && !config.async) { // subs aren’t sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) // 根據(jù)watcher的id進(jìn)行排序 } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 遍歷subs數(shù)組中的每一個(gè)watcher執(zhí)行update方法 } }}// The current target watcher being evaluated.// This is globally unique because only one watcher// can be evaluated at a time.Dep.target = null // Dep.target 代表當(dāng)前全局的watcherconst targetStack = []export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target // 賦值}export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] // 賦值}

Dep的定義還是非常清晰的,代碼注釋如上,很明顯Dep跟Watcher就跟捆綁銷(xiāo)售一樣,互相依賴(lài)。我們?cè)诜治鰀enfineReactive的時(shí)候,在對(duì)數(shù)據(jù)進(jìn)行響應(yīng)式操作的時(shí)候,通過(guò)Object.defineProperty重寫(xiě)了getter函數(shù)。

Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 當(dāng)前的全部的Watcher實(shí)例 dep.depend() // 把當(dāng)前的Dep.target加入到dep.subs數(shù)組中 // .. } return value },

其中的dep.depend()實(shí)際上就是執(zhí)行了Dep.target.addDep(this),this指向Dep實(shí)例,而Dep.target是一個(gè)Watcher實(shí)例,即執(zhí)行watcher.addDep(this)函數(shù)。我們接下來(lái)在看看這個(gè)函數(shù)做了什么:

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)) { dep.addSub(this) // 會(huì)把watcher插入到dep.subs數(shù)組中 } } }}

可以通過(guò)下圖以便理解data、Dep、Watcher的關(guān)系:

詳解Vue數(shù)據(jù)驅(qū)動(dòng)原理

回到代碼中,其中dep.addSub(this)就是會(huì)把當(dāng)前的wathcer實(shí)例插入到dep.subs的數(shù)組中,為之后的派發(fā)更新做好準(zhǔn)備,這樣依賴(lài)收集就完成了。但是到現(xiàn)在為止,我們只分析了依賴(lài)收集是怎么實(shí)現(xiàn)的,但是依賴(lài)收集的時(shí)機(jī)又是在什么時(shí)候呢?什么時(shí)候會(huì)觸發(fā)getter函數(shù)進(jìn)而實(shí)現(xiàn)依賴(lài)收集的?在進(jìn)行依賴(lài)收集的時(shí)候,Dep.tagrget對(duì)應(yīng)wathcer又是什么呢?

Watcher大致可以分為三類(lèi): * 渲染W(wǎng)atcher: 每一個(gè)實(shí)例對(duì)應(yīng)唯一的一個(gè)(有且只有一個(gè)) * computed Watcher: 每一個(gè)實(shí)例可以有多個(gè),由computed屬性生成的(computed有多少個(gè)keyy,實(shí)例就有多少個(gè)computedWatcher) * user Watcher: 每一個(gè)實(shí)例可以有多個(gè),由watch屬性生成的(同computed一樣,userWatcher的數(shù)量由key數(shù)量決定) 為避免混淆,我們接下來(lái)說(shuō)的Watcher都是渲染W(wǎng)atcher。我們知道在Vue初始化的過(guò)程中,在執(zhí)行mountComponent函數(shù)的時(shí)候,會(huì)執(zhí)行new Watcher(vm, updateComponent, {}, true),這里的Watcher就是渲染W(wǎng)atcher

class Wachter {get () { pushTarget(this) // Dep.target = this let value const vm = this.vm try { value = this.getter.call(vm, vm) // 更新視圖 } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher '${this.expression}'`) } else { throw e } } finally { // 'touch' every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }}

new Watcher對(duì)于渲染watcher而言,會(huì)直接執(zhí)行this.get()方法,然后執(zhí)行pushTarget(this),所以當(dāng)前的Dep.target為渲染watcher(用于更新視圖)。 而在我們執(zhí)行this.getter的時(shí)候,會(huì)調(diào)用render函數(shù),此時(shí)會(huì)讀取vm實(shí)例上的data數(shù)據(jù),這個(gè)時(shí)候就觸發(fā)了getter函數(shù)了,從而進(jìn)行了依賴(lài)收集,這就是依賴(lài)收集的時(shí)機(jī),比如

{{ message }} // 會(huì)讀取vm._data.message, 觸發(fā)getters函數(shù)

派發(fā)更新

我們繼續(xù)來(lái)看defineReactive函數(shù)里

export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { const dep = new Dep()// .. Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // .. }, set: function reactiveSetter (newVal) { /* 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() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal)https://cn.vuejs.org//images/data.png } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() // 遍歷dep.subs數(shù)組,取出所有的wathcer執(zhí)行update操作 } })}

當(dāng)我們修改數(shù)據(jù)的時(shí)候,會(huì)觸發(fā)setter函數(shù),這個(gè)時(shí)候會(huì)執(zhí)行dep.notify,dep.subs中所有的watcher都會(huì)執(zhí)行update方法,對(duì)于渲染W(wǎng)atcher而言,就是執(zhí)行this.get()方法,及更新視圖。這樣一來(lái),就實(shí)現(xiàn)了數(shù)據(jù)驅(qū)動(dòng)。 到這里,Vue的數(shù)據(jù)驅(qū)動(dòng)原理我們就分析完了,如果還對(duì)這個(gè)流程不大清楚的,可以結(jié)合參考官方給的圖解:

詳解Vue數(shù)據(jù)驅(qū)動(dòng)原理

總結(jié)

通過(guò)Object.defineProperty函數(shù)改寫(xiě)了數(shù)據(jù)的getter和setter函數(shù),來(lái)實(shí)現(xiàn)依賴(lài)收集和派發(fā)更新。 一個(gè)key值對(duì)應(yīng)一個(gè)Dep實(shí)例,一個(gè)Dep實(shí)例可以包含多個(gè)Watcher,一個(gè)Wathcer也可以包含多個(gè)Dep。 Dep用于依賴(lài)的收集與管理,并通知對(duì)應(yīng)的Watcher執(zhí)行相應(yīng)的操作。 依賴(lài)收集的時(shí)機(jī)是在執(zhí)行render方法的時(shí)候,讀取vm上的數(shù)據(jù),觸發(fā)getter函數(shù)。而派發(fā)更新即在變更數(shù)據(jù)的時(shí)候,觸發(fā)setter函數(shù),通過(guò)dep.notify(),通知到所收集的watcher,執(zhí)行相應(yīng)操作。

以上就是詳解Vue數(shù)據(jù)驅(qū)動(dòng)原理的詳細(xì)內(nèi)容,更多關(guān)于Vue數(shù)據(jù)驅(qū)動(dòng)原理的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Vue
相關(guān)文章:
主站蜘蛛池模板: 久久精品国产免费一区 | 欧美日韩无 | 国产视频第二页 | 国产高清在线a视频大全凹凸 | 日本三级3本三级带黄 | 四川丰满护士毛茸茸 | 国产精品密蕾丝视频 | 91精品国产91久久久久久最新 | 国产精品欧美一区二区三区不卡 | 夜色资源在线观看免费 | 国产视频一区在线观看 | 久久精品无遮挡一级毛片 | 久久美女网 | 欧美日韩无线在码不卡一区二区三区 | 一本到不卡| 黄色影院在线 | 国产交换精品一区二区三区 | 免费又色又爽1000禁片 | 国产麻豆自拍 | 国产玖玖玖精品视频 | 国产黄色的视频 | 久久精品首页 | 中日韩中文字幕 | 久久国产欧美 | 欧美成人精品福利在线视频 | 黄色录像一级片 | 久久精品30 | 末成年一级在线看片 | 日韩中字在线 | 一区二区三区高清视频在线观看 | 国产欧美日韩视频在线观看一区二区 | 一区免费视频 | 香蕉97超级碰碰碰免费公 | 国产专区91 | 欧美成人观看视频在线 | 国产精品免费看久久久 | a级黄色网| 国产免费全部免费观看 | 日韩美女一级片 | 色妇影院 | 国产人成精品午夜在线观看 |