深さ解析Vue応答式の原理

27541 ワード

深さ解析Vue応答式の原理
この記事の内容は、チームのオープンソースプロジェクト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.definePropertygetおよび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でアクセスする値が依存収集をトリガーする.
更新の配布
オブジェクトのデータを変更すると、配布更新がトリガーされ、Depnotify関数が呼び出されます.
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