深さ解析Vue応答式の原理
27541 ワード
深さ解析Vue応答式の原理
この記事の内容は、チームのオープンソースプロジェクトInterviewMapから抜粋されています.プロジェクトの現在の内容はJS、ネットワーク、ブラウザ関連、性能最適化、セキュリティ、フレームワーク、Git、データ構造、アルゴリズムなどの内容が含まれており、基礎的にも進級的にも、ソースコードの解読にも、本スペクトルで満足のいく答えを得ることができ、この面接スペクトルが面接の準備に役立つことを望んでいます.
Vue初期化
Vueの初期化では、propsとdataが先に初期化されます
次にpropsとdataを初期化する方法を見てみましょう
Object.defineProperty
オブジェクトであれ配列であれ、双方向バインドが必要な場合は最終的にこの関数が実行され、
依存収集
依存収集は
Watcherには、WatcherをレンダリングするWatcherと、ユーザーが書いたWatcherの2種類があります.レンダリングWatcherは初期化でインスタンス化されます.
次にWatcherの部分実装を見てみましょう
以上が収集に依存する全過程である.コアフローは、
更新の配布
オブジェクトのデータを変更すると、配布更新がトリガーされ、
以上が配布更新の全過程である.コア・プロセスは、オブジェクトに値を割り当て、
Object.definePropertyの欠陥
以上Vueの応答式原理を解析したが,次に
配列データを下付きで変更したり、オブジェクトに新しい属性を追加したりしても、
最初の問題に対して、VueはAPIの解決を提供した.
配列の場合、Vue内部では次の関数を書き換えて配布更新を実現します.
転載先:https://www.cnblogs.com/zhangycun/p/9403268.html
この記事の内容は、チームのオープンソースプロジェクトInterviewMapから抜粋されています.プロジェクトの現在の内容はJS、ネットワーク、ブラウザ関連、性能最適化、セキュリティ、フレームワーク、Git、データ構造、アルゴリズムなどの内容が含まれており、基礎的にも進級的にも、ソースコードの解読にも、本スペクトルで満足のいく答えを得ることができ、この面接スペクトルが面接の準備に役立つことを望んでいます.
Vue初期化
Vueの初期化では、propsとdataが先に初期化されます
Vue.prototype._init = function(options?: Object) {
// ...
// props data initState(vm) initProvide(vm) callHook(vm, 'created') if (vm.$options.el) { // vm.$mount(vm.$options.el) } }
次にpropsとdataを初期化する方法を見てみましょう
export function initState (vm: Component) { // props if (opts.props) initProps(vm, opts.props) if (opts.data) { // data initData(vm) } } function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // key const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // props if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) // prop const value = validateProp(key, propsOptions, propsData, vm) // defineProperty defineReactive(props, key, value) // vm._props.x vm.x if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true) } function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:
' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (props && hasOwn(props, key)) { } else if (!isReserved(key)) { // vm._data.x vm.x proxy(vm, `_data`, key) } } // data observe(data, true /* asRootData */) } export function observe (value: any, asRootData: ?boolean): Observer | void { // value VNode 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 } 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 // defineProperty __ob__ , // __ob__ def(value, '__ob__', this) // , if (Array.isArray(value)) { // // , Object.defineProperty // , const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } // , defineProperty walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } // , observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
Object.defineProperty
オブジェクトであれ配列であれ、双方向バインドが必要な場合は最終的にこの関数が実行され、
set
およびget
のイベントをリスニングできます.export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // , // set get const dep = new Dep() // const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // getter setter const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // val let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // getter, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // // Watcher // get if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, // setter, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val // if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } // childOb = !shallow && observe(newVal) // dep.notify() } }) }
Object.defineProperty
でget
およびset
関数をカスタマイズし、get
で依存収集を行い、set
で更新を配布する.次に依存収集の方法を見てみましょう.依存収集
依存収集は
Dep
によって達成されるが,Watcherにも密接に関係している.export default class Dep { static target: ?Watcher; id: number; subs: Array; constructor () { this.id = uid++ this.subs = [] } // addSub (sub: Watcher) { this.subs.push(sub) } // removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) {、 // Watcher addDep Dep.target.addDep(this) } } // notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // , Dep.target = null
Watcherには、WatcherをレンダリングするWatcherと、ユーザーが書いたWatcherの2種類があります.レンダリングWatcherは初期化でインスタンス化されます.
export function mountComponent( vm: Component, el: ?Element, hydrating?: boolean ): Component { // ... let updateComponent if (process.env.NODE_ENV !== 'production' && config.performance && mark) {} else { // , updateComponent = () => { vm._update(vm._render(), hydrating) } } // Watcher new Watcher( vm, updateComponent, noop, { before() { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */ ) return vm }
次にWatcherの部分実装を見てみましょう
export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { // ... if (this.computed) { this.value = undefined this.dep = new Dep() } else { this.value = this.get() } } get () { // Watcher // , Watcher pushTarget(this) let value const vm = this.vm try { // , updateComponent // , 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) } // Watcher popTarget() // , , // this.cleanupDeps() } return value } // 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 // Watcher push dep.addSub(this) } } } } export function pushTarget (_target: ?Watcher) { // target if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
以上が収集に依存する全過程である.コアフローは、
Obeject.defineProperty()
およびset
関数をブロックするために、構成内のpropsおよびdataの各値に対してget
を呼び出し、テンプレート内で双方向にバインドする必要があるオブジェクトにレンダリングWatcherでアクセスする値が依存収集をトリガーする.更新の配布
オブジェクトのデータを変更すると、配布更新がトリガーされ、
Dep
のnotify
関数が呼び出されます.notify () {
// Watcher update
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } update () { if (this.computed) { // ... } else if (this.sync) { // ... } else { // queueWatcher(this) } } export function queueWatcher(watcher: Watcher) { // id const id = watcher.id // Watcher push // , Watch if (has[id] == null) { has[id] = true if (!flushing) { // queue.push(watcher) } else { // flushSchedulerQueue , // watcher let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // if (!waiting) { waiting = true // Watcher nextTick // nextTick(flushSchedulerQueue) } } } function flushSchedulerQueue() { flushing = true let watcher, id // id watch, // 1. // 2. Watch Watch // 3. watch run , Watch queue.sort((a, b) => a.id - b.id) // , for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { // beforeUpdate watcher.before() } id = watcher.id has[id] = null // Watch watcher.run() // // watch , if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + (watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.`), watcher.vm ) break } } } // ... }
以上が配布更新の全過程である.コア・プロセスは、オブジェクトに値を割り当て、
set
の配布更新関数をトリガーします.すべてのWatcherをnextTick
に入れて更新し、nextTick
コールバックでユーザWatchのコールバック関数を実行し、コンポーネントをレンダリングする.Object.definePropertyの欠陥
以上Vueの応答式原理を解析したが,次に
Object.defineProperty
の欠陥について述べる.配列データを下付きで変更したり、オブジェクトに新しい属性を追加したりしても、
Object.defineProperty
がこれらの操作をブロックできないため、コンポーネントの再レンダリングはトリガーされません.より正確には、配列にとって、ほとんどの操作はブロックできませんが、Vue内部では関数を書き換えることでこの問題が解決されます.最初の問題に対して、VueはAPIの解決を提供した.
export function set (target: Array | Object, key: any, val: any): any { // if (Array.isArray(target) && isValidArrayIndex(key)) { // splice // target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } // key if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // , if (!ob) { target[key] = val return val } // defineReactive(ob.value, key, val) // ob.dep.notify() return val }
配列の場合、Vue内部では次の関数を書き換えて配布更新を実現します.
//
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) // const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // const original = arrayProto[method] // def(arrayMethods, method, function mutator (...args) { // const result = original.apply(this, args) const ob = this.__ob__ let inserted // , switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // ob.dep.notify() return result }) })
転載先:https://www.cnblogs.com/zhangycun/p/9403268.html