レスポンシブシステムとウォッチャーの実装のスケジューリング

背景

vue3はまだ正式にリリースされておらず、vue2の実装のアイデアの一部は参考として非常に価値があるため、この理論的な説明はまだvue2です。

応答性の高いシステムの実装

Vue.jsはMVVMフレームワークです。データモデルは通常のJavaScriptオブジェクトですが、これらのオブジェクトが操作されると、対応するビューに影響を与える可能性があります。そのコア実装は「レスポンシブシステム」です。

vue 2.0では、Object.defineProperty「レスポンシブシステム」の実装に基づいてい  ます。Vue3はProxy/Reflect 実現に基づいており  、vue3の詳細な分析には書く時間があり、この記事はvue2の実現についてです。

主に属性が含まれます:

  • 列挙可能、プロパティを列挙できるかどうか、デフォルトはfalse。

  • 構成可能。プロパティーを変更または削除できるかどうかにかかわらず、デフォルトはfalseです。

  • get、属性を取得するメソッド。

  • set、プロパティの設定方法。

応答性の基本原則は、Vueコンストラクターでオプションのデータを処理することです。つまり、vueインスタンスが初期化されると、データ、プロパティ、その他のオブジェクトの各プロパティは、Object.definePropertyによって一度定義されます。データが設定されたら、いくつかの操作を実行して、対応するビューを変更します。

class Vue {
    /* Vue构造类 */
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
function observer (value) {
    if (!value || (typeof value !== 'object')) {
        return;
    }
    
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}
function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,       /* 属性可枚举 */
        configurable: true,     /* 属性可被修改或删除 */
        get: function reactiveGetter () {
            return val;
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

実際のアプリケーションでは、さまざまなシステムが非常に複雑です。これでグローバルオブジェクトがあると仮定します。これを複数のVueオブジェクトで使用して表示できます。あるいは、dataに書き込まれたデータがビューに適用されておらず、この時点でビューを更新する必要がない場合もあります。これは収集プロセスによって異なります。

依存コレクション

いわゆる依存収集とは、データが使用される場所を収集することであり、このデータが変更されると、各場所に対応する操作を行うよう通知します。VUEの「サブスクライバー」の基本モードは次のとおりです。

exportdefaultclass 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) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

サブスクライバーについて、Watcherの実装を見てみましょう。ソースコードウォッチャーはより論理的であり、簡略化されたモデルは次のとおりです。

class Watcher{
    constructor(vm,expOrFn,cb,options){
        //传进来的对象 例如Vue
        this.vm = vm
        //在Vue中cb是更新视图的核心,调用diff并更新视图的过程
        this.cb = cb
        //收集Deps,用于移除监听
        this.newDeps = []
        this.getter = expOrFn
        //设置Dep.target的值,依赖收集时的watcher对象
        this.value =this.get()
    }

    get(){
        //设置Dep.target值,用以依赖收集
        pushTarget(this)
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        return value
    }

    //添加依赖
      addDep (dep) {
          // 这里简单处理,在Vue中做了重复筛选,即依赖只收集一次,不重复收集依赖
        this.newDeps.push(dep)
        dep.addSub(this)
      }

      //更新
      update () {
        this.run()
    }

    //更新视图
    run(){
        //这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
        console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    }
}

defineReactive詳細ロジック

exportfunction defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new 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) {
    val = obj[key]
  }

  let childOb = !shallow && 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)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* 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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

所以响应式原理就是,我们通过递归遍历,把vue实例中data里面定义的数据,用defineReactive(Object.defineProperty)重新定义。每个数据内新建一个Dep实例,闭包中包含了这个 Dep 类的实例,用来收集 Watcher 对象。在对象被「读」的时候,会触发 reactiveGetter 函数把当前的 Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。之后如果当该对象被「写」的时候,则会触发 reactiveSetter 方法,通知 Dep 类调用 notify 来触发所有 Watcher 对象的 update 方法更新对应视图。

ウォッチャーの誕生

Vueでは、Watcherを生成する4つの状況があります。

  1. Vueインスタンスオブジェクトのウォッチャー、ルートデータを観察し、変更が発生したときにコンポーネントを再レンダリングしますupdateComponent =()=> {vm._update(vm._render()、hydrating)} vm._watcher = new Watcher(vm、updateComponent、noop)

  2. ユーザーがvueオブジェクトのwatch属性を使用して作成したウォッチャー

  3. ユーザーがVueオブジェクトに作成した計算属性は、基本的にウォッチャーです

  4. ユーザーがvmを使用して作成したWatcher $ Watch

ワッサーは増減し、レンダリング時に追加される場合があります。したがって、ウォッチャーをスケジュールするスケジュールが必要です。主なコードは次のとおりです。

 queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    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
      }
    }
  }

スケジュールの役割:

  1. 重複排除、各Watcherには一意のIDがあります。まず、IDがすでにキューにある場合は、スキップして実行を繰り返す必要はありません。IDがキューにない場合は、キューが実行されているかどうかによって異なります。実行されていない場合、キューは次のタイムスライスで実行されるため、キューは常に非同期で実行されます。

  2. 並べ替えは、解析とレンダリングの順序で実行されます。つまり、最初にWatcherが実行されます。WatcherのIDは自動的に増加し、最初に作成されたIDは後で作成されたIDよりも小さくなります。したがって、次のルールがあります。

2.1。コンポーネントはネストすることができ、分析では最初に親コンポーネントを解析し、次に子コンポーネントを解析する必要があります。したがって、親コンポーネントのidは子コンポーネントよりも小さいです。

2.2。ユーザーが作成したウォッチャーは、レンダリング時に作成されるウォッチャーの前に解析されます。したがって、ユーザーが作成したWatcherのIDは、レンダリング時に作成されたIDよりも小さくなります。

  1. ウォッチャーの削除。コンポーネントのウォッチャーがキューにあり、その親コン​​ポーネントが削除されている場合、このウォッチャーもこの時点で削除する必要があります。

  2. キューの実行中に、各ウォッチャーの実行回数を含むオブジェクトサーキュラーが保存されます。いずれかのウォッチャーがMAX_UPDATE_COUNTで定義された回数を超えて実行される場合、無限ループと見なされ、実行されなくなります。デフォルトは100回です。

总之,调用的作用就是管理 Watcher。

補足

Object.definePropertyを使用してVUE2の配列オブジェクトを再定義する方法?データ内のアイテムを直接変更しても、ビューが応答して変化しないのはなぜですか(arr [3] = 4)。

答えは、アレイの応答性は完全ではなく、VUEは限られたメソッドのみを書き直したことです。書き換えロジックは次のとおりです。

const arrayProto = Array.prototype
exportconst arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original 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)
    // notify change
    ob.dep.notify()
    return result
  })
})
117件の元の記事を公開しました 69件の賞賛 10,000以上のビュー

おすすめ

転載: blog.csdn.net/zsd0819qwq/article/details/105356741