【Vue2.0ソースコード学習】変更検知 - オブジェクト変更検知

1 はじめに

私たちは知っています: データ駆動型ビューの重要なポイントは、データが変更されたことをどのように知るかです. データがいつ変更されたかを知っている限り、問題は解決されます.データの変更。

データがいつ読み取られたのか、いつデータが書き換えられたのかを知ることは実際には難しくありません.データがいつ変更されたのかを簡単に知る方法をJS提供します.Object.defineProperty

2.オブジェクトデータを「観察可能」にする

データのすべての読み取りと書き込みは、私たちが見ることができる、つまりいつデータが読み取られたのか、いつデータが書き換えられたのかを知ることができます.これをデータの「観測可能」と呼びます.

Object.definePropertyデータを「観測可能」にするには、冒頭で述べた方法が必要ですが、この記事では、この方法を使用してデータを「観測可能」にします。

まず、データ オブジェクトを定義しますcar

let car = {
    
    
  'brand':'BMW',
  'price':3000
}

これを定義したcarブランドはbrandBMW、価格priceは 3000 です。car.brandこれで、と を介して、この対応するプロパティ値をcar.price直接読み書きできるようになりました。carただし、carこのプロパティがいつ読み取られたり、変更されたりするかはわかりません。carでは、属性が変更されたことを率先して知らせるにはどうすればよいでしょうか。

次に、Object.defineProperty()上記の例を次のように書き直します。

let car = {
    
    }
let val = 3000
Object.defineProperty(car, 'price', {
    
    
  enumerable: true,
  configurable: true,
  get(){
    
    
    console.log('price属性被读取了')
    return val
  },
  set(newVal){
    
    
    console.log('price属性被修改了')
    val = newVal
  }
})

プロパティはObject.defineProperty()メソッドによって定義され、このプロパティの読み取りと書き込みはそれぞれ使用され傍受されます。プロパティが読み取られるか書き込まれるたびに、 と がトリガーされます以下に示すように:carpriceget()set()get()set()

ここに画像の説明を挿入

car属性の読み取りおよび書き込みステータスをアクティブに通知できることがわかります。これは、このcarデータ オブジェクトが既に「観察可能」であることも意味します。

carオブザーバブルのすべてのプロパティを作成するには、次のコードを記述します。

// 源码位置:src/core/observer/index.js

/**
 * Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
 */
export class Observer {
    
    
  constructor (value) {
    
    
    this.value = value
    // 给value新增一个__ob__属性,值为该value的Observer实例
    // 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
    def(value,'__ob__',this)
    if (Array.isArray(value)) {
    
    
      // 当value为数组时的逻辑
      // ...
    } else {
    
    
      this.walk(value)
    }
  }

  walk (obj: Object) {
    
    
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
    
    
      defineReactive(obj, keys[i])
    }
  }
}
/**
 * 使一个对象转化成可观测对象
 * @param { Object } obj 对象
 * @param { String } key 对象的key
 * @param { Any } val 对象的某个key的值
 */
function defineReactive (obj,key,val) {
    
    
  // 如果只传了obj和key,那么val = obj[key]
  if (arguments.length === 2) {
    
    
    val = obj[key]
  }
  if(typeof val === 'object'){
    
    
      new Observer(val)
  }
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get(){
    
    
      console.log(`${
      
      key}属性被读取了`);
      return val;
    },
    set(newVal){
    
    
      if(val === newVal){
    
    
          return
      }
      console.log(`${
      
      key}属性被修改了`);
      val = newVal;
    }
  })
}

上記のコードでは、法線をオブザーバブルにobserver変換するために使用されるクラスを定義していますobjectobject

value新しい属性を追加します__ob__値はthisvalueのインスタンスですObserverこの操作は、マークを付けることと同じでvalue、レスポンシブ型に変換されたことを示し、操作の繰り返しを回避します。

次に、データのタイプを決定します。objectデータのタイプのみがwalk、各属性が変換されたフォームを呼び出してgetter/setter変更を検出します。defineReactive最後に、渡されたプロパティ値がまだ one の場合にinを再帰的にサブプロパティにobject使用して、変更を検出するためにすべてのプロパティ (サブプロパティを含む)new observer(val)を変換できるようにします。つまり、 1 つを に渡す限り、これは観察可能で応答性が高くなりますobjgetter/seterobjectobserverobjectobject

observerクラスはソース コードにありますsrc/core/observer/index.js

したがって、次のように定義できますcar

let car = new Observer({
    
    
  'brand':'BMW',
  'price':3000
})

このように、carの両方のプロパティが観測可能です。

3.依存コレクション

3.1 依存コレクションとは

前の章では、データを観察可能にするという最初のステップを踏みましたobject観測可能になった後、データがいつ変更されたかを知ることができるため、データが変更されたときにビューに通知して更新することができます。ここでまた問題が発生します。ビューが非常に大きいため、誰に変更を通知する必要がありますか? データの一部が変更された後にビュー全体を更新するのは明らかに不合理です。この時点で、ビューでこのデータを使用する人は誰でも誰でも更新すると間違いなく考えるでしょう。右!そうです、それだけです。

ビューでこのデータを使用する人は、それを使用する人を更新します. 別の言い方をすると: 「このデータを使用する人」を「このデータに依存する人」と呼び、各データの依存関係配列を作成します (データはこのデータに依存する人 (つまり、このデータを使用する人) は誰でもこの依存関係配列に入れ、このデータが変更されると、対応する依存関係配列に移動します。彼ら:「依存しているデータが変更されました。更新する必要があります!」. このプロセスは依存コレクションです。

3.2 依存関係をいつ収集するか? 依存関係の更新はいつ通知されますか?

依存関係の収集とは何かを理解した後、いつ依存関係を収集する必要がありますか? 依存関係の更新はいつ通知されますか?

実際、この質問は前のセクションで回答済みですが、このデータを使用する人は誰でも、データが変更されたときに通知されると述べました。いわゆる誰がこのデータを使用したかは、実際には誰がこのデータを取得したかであり、観測可能なデータが取得されるとgetter属性がトリガーされるため、getterこの依存関係を で収集できます。同様に、このデータが変更されると、setterプロパティがトリガーされるため、setter依存関係の更新を で通知できます。

要約すると、1 つの文は次のとおりです。ゲッターで依存関係を収集し、セッターで依存関係の更新を通知します

3.3 依存関係を収集する場所

依存関係の収集とは何か、収集して通知するタイミングを理解したら、依存関係をどこで収集する必要がありますか?

セクション 3.1 で説明したように、データごとに依存関係配列を作成し、依存関係配列に入れられたデータに依存する人は誰でも作成します。依存関係を格納するために配列のみを使用する場合、関数が少し不足しているように見え、コードが結合されすぎています。依存関係配列の機能を拡張する必要があります. より良いアプローチは、データごとに依存関係マネージャーを構築して、このデータのすべての依存関係を管理することです. OK、ここで、依存関係マネージャーDepクラスが作成されました。コードは次のとおりです。

// 源码位置:src/core/observer/dep.js
export default class Dep {
    
    
  constructor () {
    
    
    this.subs = []
  }

  addSub (sub) {
    
    
    this.subs.push(sub)
  }
  // 删除一个依赖
  removeSub (sub) {
    
    
    remove(this.subs, sub)
  }
  // 添加一个依赖
  depend () {
    
    
    if (window.target) {
    
    
      this.addSub(window.target)
    }
  }
  // 通知所有依赖更新
  notify () {
    
    
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      subs[i].update()
    }
  }
}

/**
 * Remove an item from an array
 */
export function remove (arr, item) {
    
    
  if (arr.length) {
    
    
    const index = arr.indexOf(item)
    if (index > -1) {
    
    
      return arr.splice(index, 1)
    }
  }
}

上記の依存関係マネージャーDepクラスでは、最初にsubs依存関係を格納する配列を初期化し、依存関係に対する追加、削除、通知、およびその他の操作を行ういくつかのインスタンス メソッドを定義しました。

依存関係マネージャーを配置すると、getter で依存関係を収集し、setter で依存関係の更新を通知できます。コードは次のとおりです。

function defineReactive (obj,key,val) {
    
    
  if (arguments.length === 2) {
    
    
    val = obj[key]
  }
  if(typeof val === 'object'){
    
    
    new Observer(val)
  }
  const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get(){
    
    
      dep.depend()    // 在getter中收集依赖
      return val;
    },
    set(newVal){
    
    
      if(val === newVal){
    
    
          return
      }
      val = newVal;
      dep.notify()   // 在setter中通知依赖更新
    }
  })
}

上記のコードでは、メソッドgetterを呼び出してdep.depend()依存関係を収集し、メソッドをsetter呼び出してすべての依存関係の更新を通知しました。dep.notify()

4.誰に依存するか

前の章を通じて、依存関係とは何かを理解しましたか? 依存関係はいつ収集されますか? 収集された依存関係はどこに保存されますか? では、私たちが収集する依存関係は誰でしょうか?

「誰がこのデータを利用するかは依存している」と言い続けてきましたが、これは口語レベルでしかないので、この「誰」をコードでどのように記述すればよいでしょうか。

実際、Vueと呼ばれるクラスは にも実装されておりWatcherWatcherクラスのインスタンスは前述の「誰」です。つまり、データを使用する人は誰でも依存しており、Watcherその人のインスタンスを作成します。将来データが変更された場合、依存関係の更新を直接通知するのではなく、Watch依存関係の対応するインスタンスに通知し、Watcherインスタンスは実際のビューに通知します。

Watcherクラスの具体的な実装は次のとおりです。

export default class Watcher {
    
    
  constructor (vm,expOrFn,cb) {
    
    
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn)
    this.value = this.get()
  }
  get () {
    
    
    window.target = this;
    const vm = this.vm
    let value = this.getter.call(vm, vm)
    window.target = undefined;
    return value
  }
  update () {
    
    
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}

/**
 * Parse simple path.
 * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
 * 例如:
 * data = {a:{b:{c:2}}}
 * parsePath('a.b.c')(data)  // 2
 */
const bailRE = /[^\w.$]/
export function parsePath (path) {
    
    
  if (bailRE.test(path)) {
    
    
    return
  }
  const segments = path.split('.')
  return function (obj) {
    
    
    for (let i = 0; i < segments.length; i++) {
    
    
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

Watcherデータを使用する人は誰でも依存関係であり、その人のインスタンスを作成しますWatcher. インスタンスの作成プロセス中に、このデータに対応する依存関係マネージャーに自分自身を自動的に追加します. 将来、このWatcherインスタンスはこの依存関係を表します. . データが変更されると、Watcherインスタンスに通知し、Watcherインスタンスは実際の依存関係を通知します。

では、Watcherインスタンスの作成プロセス中に、このデータに対応する依存関係マネージャーにどのように追加するのでしょうか?

Watcherクラスのコード実装ロジックを分析しましょう。

  1. Watcherクラスをインスタンス化するとき、そのコンストラクターが最初に実行されます。
  2. インスタンス メソッドはコンストラクターで呼び出されますthis.get()
  3. このメソッドではget()、まずwindow.target = thisインスタンス自体をグローバルで一意のオブジェクトwindow.targetに割り当て、次にlet value = this.getter.call(vm, vm)依存データを取得します. 依存データを取得する目的は、上記のデータをトリガーすることですgetter. 上で述べたように、依存関係を収集するためにgetter呼び出してdep.depend()dep.depend()取得します.window.targetマウントの値を依存関係配列に格納し、get()メソッドの最後でwindow.target解放します。
  4. When the data changes, the data will be trigger setter, and the method setteris called in the method.メソッドでは、すべての依存関係 (つまり、ウォッチャー インスタンス) が走査され、依存するメソッド (つまり、インスタンス メソッド)が実行されます。クラス、およびデータの変更がメソッドで呼び出されます ビューを更新するコールバック関数を更新します。dep.notify()dep.notify()update()Watcherupdate()update()

簡単な要約は次のとおりです。Watcherまず、グローバルに一意の指定された位置 ( window.target) に自分自身を設定し、次にデータを読み取ります。データが読み込まれるため、このデータがトリガーされますgetter次に、 in はgetterグローバルに一意な場所から現在読み取られているデータを読み取りWatcher、これを in にwatcher収集しますDepデータが収集されると、データが変更されたときにDepそれぞれに通知が送信されますWatcherこのようにして、Watcherデータの変更を積極的にサブスクライブできます。理解を容易にするために、以下に示すように、その関係のフローチャートを描きました。

ここに画像の説明を挿入

Object以上で、データの検出、依存関係の収集、依存関係の更新などのすべての操作が完全に完了します。

5.弱点

この方法でデータの可観測性をObject.defineProperty実現しましたが、この方法ではデータの値と設定値しか観測できず、データに新しいペアを追加したり、既存のペアを削除したりすると、観測できませんでした。データに値を追加または削除すると、依存関係を通知できず、ビューを駆動してレスポンシブ更新を実行できません。objectobjectobjectkey/valuekey/valueobject

もちろん、Vueこれにも気付きました.この問題を解決するために、Vueの2つのグローバルAPIが追加されました.この2つのAPIの実装原理については、グローバルAPIを学ぶときに後述します.Vue.setVue.delete

6. まとめ

まず、メソッドによってデータの可観測性をObject.defineProperty実現し、クラスをカプセル化して、データ内のすべての属性 (サブ属性を含む) を簡単に形式に変換して変化を検出できるようにしました。objectObserverobjectgetter/seter

次に、依存関係のコレクションとは何かを学びました。また、getterで依存関係を収集し、setterで依存関係の更新を通知し、Dep収集した依存関係を格納するために依存関係マネージャーをカプセル化する方法を知っています。

最後に、依存関係ごとにインスタンスを作成しますWatcher. データが変更されると、Watcherインスタンスに通知され、Watcherインスタンスが実際の更新操作を実行します.

全体のプロセスはおおよそ次のとおりです。

  1. Data変更は、フォームにobserver変換することによって追跡されます。getter/setter
  2. 外の世界がWatcherデータを読み取ると、それがトリガーされgetterWatcher依存関係に追加されます。
  3. データが変更されると、依存関係 (つまり、ウォッチャー)setterに通知を送信するようにトリガーされます。依存コレクションとは何ですか? また、で依存関係を収集し、で依存関係の更新を通知し、収集した依存関係を格納するために依存関係マネージャーをカプセル化する方法を知っています。Dep
    gettersetterDep

最後に、依存関係ごとにインスタンスを作成しますWatcher. データが変更されると、Watcherインスタンスに通知され、Watcherインスタンスが実際の更新操作を実行します.

全体のプロセスはおおよそ次のとおりです。

  1. Data変更は、フォームにobserver変換することによって追跡されます。getter/setter
  2. 外の世界がWatcherデータを読み取ると、それがトリガーされgetterWatcher依存関係に追加されます。
  3. データが変更されると、依存関係 (つまり、ウォッチャー)setterに通知を送信するようにトリガーされます。Dep
  4. Watcher通知を受け取った後、外界に通知を送信します. 変更通知が外界に到達した後、ビューの更新をトリガーするか、ユーザーのコールバック関数がトリガーされる場合があります.

おすすめ

転載: blog.csdn.net/weixin_46862327/article/details/130452991