国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術文章
文章詳情頁

詳解Vue數據驅動原理

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

前言

Vue區別于傳統的JS庫,例如JQuery,其中一個最大的特點就是不用手動去操作DOM,只需要對數據進行變更之后,視圖也會隨之更新。 比如你想修改div#app里的內容:

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

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

在代碼層面上的最大區別就是,JQuery直接對DOM進行了操作,而Vue則對數據進行了操作,接下來我們通過分析源碼來進一步分析,Vue是如何做到數據驅動的,而數據驅動主要分成兩個部分依賴收集和派發更新。

數據驅動

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

在Vue初始化會執行_init方法,并調用initState方法. initState相關代碼在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) // 執行 data.call(vm) : data || {} if (!isPlainObject(data)) { data = {} // 這也是為什么 data函數需要返回一個object不然就會報這個警告 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值所組成的數組 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重復 warn( `Method '${key}' has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { // 避免props的key與data的key重復 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 */) // 響應式處理}

其中有兩個重要的函數分別是proxy跟observe,在往下閱讀之前,如果還有不明白Object.defineProperty作用的同學,可以點擊這里進行了解,依賴收集跟派發更新都需要依靠這個函數進行實現。

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函數的邏輯很簡單,就是對vm._data上的數據進行代理,vm._data上保存的就是data數據。通過代理的之后我們就可以直接通過this.xxx訪問到data上的數據,實際上訪問的就是this._data.xxx。

observe

oberse定義在src/core/oberse/index.js下,關于數據驅動的文件都存放在src/core/observe這個目錄中:

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

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

Observer類

export class Observer { value: any; // 觀察的數據 dep: Dep; // dep實例用于 派發更新 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__變成不可枚舉的,因為沒有必要改變watcher本身 def(value, ’__ob__’, this) 會執行 value._ob_ = this(watcher實例)操作 if (Array.isArray(value)) { // 當value是數組 if (hasProto) { protoAugment(value, arrayMethods) // 重寫Array.prototype的相關方法 } else { copyAugment(value, arrayMethods, arrayKeys) // 重寫Array.prototype的相關方法 } this.observeArray(value) } else { this.walk(value) // 當value為對象 } } /** * 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]) // 對數據進行響應式處理 } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) // 遍歷value數組的每一項并調用observe函數,進行響應式處理 } }}

Observe類要做的事情通過查看源碼也是清晰明了,對數據進行響應式處理,并對數組的原型方法進行重寫!defineReactive函數就是實現依賴收集和派發更新的核心函數了,實現代碼如下。

依賴收集

defineReactive

export function defineReactive ( obj: Object, // data數據 key: string, // data中對應的key值 val: any, // 給data[key] 賦值 可選 customSetter?: ?Function, // 自定義setter 可選 shallow?: boolean // 是否對data[key]為對象的值進行observe遞歸 可選) { const dep = new Dep() // Dep實例 **每一個key對應一個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 if ((!getter || setter) && arguments.length === 2) { // 沒有getter或者有setter,并且傳入的參數有兩個 val = obj[key] } let childOb = !shallow && observe(val) // 根據shallow,遞歸遍歷val對象,相當于val當做data傳入 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 當前的全部的Watcher實例 dep.depend() // 把當前的Dep.target加入到dep.subs數組中 if (childOb) { // 如果val是對象, childOb.dep.depend() // 會在value._ob_的dep.subs數組中加入Dep.target, 忘記ob實例屬性的同學可往回翻一番 if (Array.isArray(value)) { dependArray(value) // 定義如下,邏輯也比較簡單 } } } 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是響應式數據,則往e._ob_.dep.subs數組中加入Dep.target if (Array.isArray(e)) { dependArray(e) // 遞歸遍歷 } }}

代碼中多次用到了Dep類和Dep.target,理解清楚了它們的作用,我們就離Vue數據驅動的原理更近一步了,相關的代碼如下:

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++ // 每一個dep都有一個唯一的ID this.subs = [] // 存放watcher實例的數組 } addSub (sub: Watcher) { this.subs.push(sub) // 往this.subs加入watcher } removeSub (sub: Watcher) { remove(this.subs, sub) // 刪除this.subs對應的watcher } depend () { if (Dep.target) { // watcher.addDep(this) actually Dep.target.addDep(this) // 在watcher類中查看 } } 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) // 根據watcher的id進行排序 } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 遍歷subs數組中的每一個watcher執行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 代表當前全局的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就跟捆綁銷售一樣,互相依賴。我們在分析denfineReactive的時候,在對數據進行響應式操作的時候,通過Object.defineProperty重寫了getter函數。

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

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

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) // 會把watcher插入到dep.subs數組中 } } }}

可以通過下圖以便理解data、Dep、Watcher的關系:

詳解Vue數據驅動原理

回到代碼中,其中dep.addSub(this)就是會把當前的wathcer實例插入到dep.subs的數組中,為之后的派發更新做好準備,這樣依賴收集就完成了。但是到現在為止,我們只分析了依賴收集是怎么實現的,但是依賴收集的時機又是在什么時候呢?什么時候會觸發getter函數進而實現依賴收集的?在進行依賴收集的時候,Dep.tagrget對應wathcer又是什么呢?

Watcher大致可以分為三類: * 渲染Watcher: 每一個實例對應唯一的一個(有且只有一個) * computed Watcher: 每一個實例可以有多個,由computed屬性生成的(computed有多少個keyy,實例就有多少個computedWatcher) * user Watcher: 每一個實例可以有多個,由watch屬性生成的(同computed一樣,userWatcher的數量由key數量決定) 為避免混淆,我們接下來說的Watcher都是渲染Watcher。我們知道在Vue初始化的過程中,在執行mountComponent函數的時候,會執行new Watcher(vm, updateComponent, {}, true),這里的Watcher就是渲染Watcher

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對于渲染watcher而言,會直接執行this.get()方法,然后執行pushTarget(this),所以當前的Dep.target為渲染watcher(用于更新視圖)。 而在我們執行this.getter的時候,會調用render函數,此時會讀取vm實例上的data數據,這個時候就觸發了getter函數了,從而進行了依賴收集,這就是依賴收集的時機,比如

{{ message }} // 會讀取vm._data.message, 觸發getters函數

派發更新

我們繼續來看defineReactive函數里

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數組,取出所有的wathcer執行update操作 } })}

當我們修改數據的時候,會觸發setter函數,這個時候會執行dep.notify,dep.subs中所有的watcher都會執行update方法,對于渲染Watcher而言,就是執行this.get()方法,及更新視圖。這樣一來,就實現了數據驅動。 到這里,Vue的數據驅動原理我們就分析完了,如果還對這個流程不大清楚的,可以結合參考官方給的圖解:

詳解Vue數據驅動原理

總結

通過Object.defineProperty函數改寫了數據的getter和setter函數,來實現依賴收集和派發更新。 一個key值對應一個Dep實例,一個Dep實例可以包含多個Watcher,一個Wathcer也可以包含多個Dep。 Dep用于依賴的收集與管理,并通知對應的Watcher執行相應的操作。 依賴收集的時機是在執行render方法的時候,讀取vm上的數據,觸發getter函數。而派發更新即在變更數據的時候,觸發setter函數,通過dep.notify(),通知到所收集的watcher,執行相應操作。

以上就是詳解Vue數據驅動原理的詳細內容,更多關于Vue數據驅動原理的資料請關注好吧啦網其它相關文章!

標簽: Vue
相關文章:
主站蜘蛛池模板: 欧美一区二区精品系列在线观看 | 99精品福利视频 | 手机看片日韩日韩国产在线看 | 国产亚洲精品一区二区三区 | 国产资源精品一区二区免费 | 精品视频一区二区 | 欧美成人三级 | 欧美日韩一区二区三区在线播放 | 成人区精品一区二区毛片不卡 | 欧美成人看片黄a免费看 | 精品久久久久久久 | 亚洲www视频| 国产亚洲精品成人一区看片 | 国产cao | 久久一本精品 | 57pao强力打造手机版 | 精品一区二区三区在线视频 | 亚洲成人在线免费观看 | 德国女人一级毛片免费 | 久久99免费视频 | 高清国产精品久久久久 | 免费国产在线观看 | 成人免费的性色视频 | 91成人影院 | 日本免费人成黄页网观看视频 | 久久99久久成人免费播放 | 欧洲欧美成人免费大片 | 熟女毛片| 国产精品v一区二区三区 | 国内免费视频成人精品 | 久久久一级| a级高清 | 国产精品激情丝袜美女 | 国产成人精品免费视频网页大全 | 国产一区二区三区免费在线视频 | a国产成人免费视频 | 一级毛片免费视频观看 | 亚洲第一页在线播放 | 国产成人久久精品二区三区 | 日本亚欧乱色视频在线观看 | 国产一级精品高清一级毛片 |