Vueソースコード分析:双方向データバインディングの原則(レスポンシブ)

vueの双方向データバインディング(レスポンシブ)の原則は何ですか?これはまだvueのソースコードから開始する必要があります:

vueの主な機能は数据驱动视图、データ駆動型ビューの6つの単語を理解する方法であることを私たちは知っていますか?

データ:状態;

ドライブ:レンダリング機能;

ビュー:ページのUI。

このようにして、実際には、次の式を取得できます。UI= render(state)、状態の変化はUIの変化に直接つながり、定数はrenderであり、vueはrenderの役割を果たします。では、vueはどのようにして状態の変化を知るのでしょうか?变化侦测現在の主流のテクノロジースタック、vue、react、angularで言及されているため、と呼ばれる単語があり状态追宗ます。つまり、データが変更されたら、ビューを更新します。オブジェクトの変化検出から始めましょう。

いわゆる変更検出とは、データがいつ読み取られたか、いつ書き換えられたかを知ることができることを意味します。

データ検出

1.オブジェクト検出の実現(観察可能)

物体検出はObject.defineProperty()この方法によるものです。

まず、オブジェクト動物を定義します。


let animal = {
    name: 'dog',
    age: 3

では、この動物オブジェクトのプロパティがいつ変更されたかをどのようにして知ることができますか?次の操作を見てみましょう。


let name = 'cat'
Object.defineProperty(animal, 'name', {
    configrable: true,    //    描述属性是否配置,以及可否删除
    enumerable: true,     //    描述属性是否会出现在for in 或者 Object.keys()的遍历中
    writable: true,       //    属性的值是否可以被重写
    get () {
        //    这里读取了name的值
        return name
    },
    set (value) {
        //    这里设置了name的值
        name = value
    }

このように、nameの値がcatに変更されると、それは観察可能および検出可能になります。ただし、これはオブジェクトの特定の属性が検出される場合にのみ可能ですが、オブジェクトの属性は1つではないことがよくあります。複数の属性がある場合はどうすればよいですか?実際、それは非常に単純です、はい、はい、それはあなたが話したい再帰です。コードに直接:

//    源码位置:src/core/observer/index.js
export class Observer {
    constructor (value) {
        this.value = 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])
        }
    }
}
function defineReactive (obj,key,val) {
      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クラスを宣言し、__ ob__属性と値を持つObserverのインスタンスを各検出可能オブジェクトに追加する必要があります。これはYuがフラグを設定したのと同じです。各値。これは、値がレスポンシブであることを意味します。次に、値がオブジェクトの場合、すべてのキーを取得し、Object.defineProperty()を呼び出すメソッドをループし、get / setメソッドを呼び出して検出します。着信オブジェクトがオブジェクトであることが判明した場合は、次のように渡します。再帰的に、オブジェクトはObserverクラスを介して再度インスタンス化されます。このようにして、物体検出を実現しました。

2.アレイ検出の実現(観察可能)

上記でオブジェクト検出について説明しましたが、次に配列検出について説明します。

配列はObject.defineProperty()で監視できないため、配列のobservableはオブジェクトのobservableとは異なります。では、配列を観察可能にする方法、実際には同じですが、配列は拦截器実行されます。それでは、神聖なインターセプターとは何ですか?

JavaScriptで配列を操作するためのネイティブメソッド、push / pop / shift / unshift / splice / sort / reverseがいくつかあることはわかっています。これらのメソッドはすべてを介してアクセスされるArray.prototypeため、Array.prototypeのメソッドを再定義して、pushと同じ効果を実現するために、newPushなどのネイティブメソッドを実装する場合、それは完璧ですか?ネイティブメソッドは変更されず、ネイティブメソッドを呼び出す前に対応する操作を実行できます。

let arr = [1,2,3]
//    arr.push(4) 原生方法调用
Array.prototype.newPush = function(val){
      console.log('arr被修改了')
      this.push(val)
}
arr.newPush(4)

だから、数组的拦截器就是在数组实例和Array.prototype之间重写了操作数组的方法このように、処理されたメソッドが呼び出されると、ネイティブメソッドの代わりに書き換えられたメソッドが呼び出されます。インターセプターの実装を見てください。

// 源码位置:/src/core/observer/array.js
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]
  Object.defineProperty(arrayMethods, method, {
    enumerable: false,
    configurable: true,
    writable: true,
    value:function mutator(...args){
      const result = original.apply(this, args)
      return result
    }
  })
})

これがインターセプターの実装です。配列のメソッドが呼び出されると、実際に呼び出されるのはmutator函数であるため、この関数でいくつかの操作を実行できます。しかし、これは十分ではありません只有将拦截器挂载在数组实例和Array.prototype之间才能生效,也就是将数据的__proto__属性设置为拦截器arrayMethods即可

augment(value arrayMethods、、 arrayKeys)

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}
// 能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}
 
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
 
/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object, keys: any) {
  target.__proto__ = src
}
 
/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
 

インターセプターが有効になると、アレイが変更された場合にそれを検出できます。

上記は、オブジェクトと配列の変更を検出可能にする方法です。検出されるだけでは十分ではありません。コレクションに依存することについて話しましょう。

依存するデータ収集

1.オブジェクトの依存関係のコレクション

依存コレクション:ビューで使用されるデータ、ビューはデータに依存し、変更されたデータが収集されます。このプロセスは依存コレクションです。

依存関係を収集する場所:監視可能なデータgetterはで取得されるため、getterは依存関係を収集する場所です。

依存更新を通知するタイミング:データの変更setterは、当然、通知依存更新はセッターで確認できます。

依存関係はどこで収集する必要がありますか?そして見下ろしてください:

更新されたデータのサイズを制御できないため、依存関係を収集するための依存関係マネージャーが必要です。

// 源码位置: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)
       }
   }
}

依存関係マネージャーを使用すると、ゲッターで依存関係を収集し、セッターで依存関係の更新を通知できます。

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中通知依赖更新
     }
  })
}

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

実際、Vueには、変更ごとにデータに依存するウォッチャークラスが実装されています。後でデータが変更された場合、依存する更新を直接通知するのではなく、依存するWatchインスタンスに通知し、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
  }
}
 

分析:

  • ウォッチがインスタンス化されると、コンストラクターが最初に実行されます。
  • コンストラクターでthis.get()インスタンスメソッドを呼び出します。
  • get()メソッドでは、最初にインスタンス自体をグローバル一意オブジェクトwindow.targetにwindow.target = thisで割り当て、次にlet value = this.getter.call(vm、vm)Dataで依存オブジェクトを取得します。依存データを取得する目的は、データに対してゲッターをトリガーすることです。前述のように、dep.depend()は、ゲッター内の依存関係を収集するために呼び出され、window.targetはdep.depend()から取得されます。依存関係配列、およびget()メソッドの最後でwindow.targetを解放します。
  • データが変更されると、データセッターがトリガーされます。dep.notify()メソッドがセッターで呼び出されます。dep.notify()メソッドでは、すべての依存関係(つまり、ウォッチャーインスタンス)がトラバースされ、依存するupdate()メソッドが呼び出されます。つまり、Watcherクラスのupdate()インスタンスメソッドは、update()メソッドのデータ変更のupdateコールバック関数を呼び出してビューを更新します。

簡単な要約は次のとおりです。

Watcherは、最初にグローバルに一意の指定された場所(window.target)に自分自身を設定し、次にデータを読み取ります。データが読み取られるため、このデータのゲッターがトリガーされます。次に、ゲッターで、現在データを読み取っているウォッチャーがグローバルに一意の位置から読み取られ、このウォッチャーがDepに収集されます。収集後、データが変更されると、Depの各ウォッチャーに通知が送信されます。このようにして、Watcherはデータの変更を積極的にサブスクライブできます。理解を容易にするために、以下に示すような関係フローチャートを作成しました。

ここに画像の説明を挿入
以上で、オブジェクトデータの検出、依存関係の収集、依存関係の更新などのすべての操作が完全に完了しました。

2.配列の依存性コレクション

実際、配列の場合、依存コレクション内のオブジェクトと同様のメソッドがあります。

配列の依存関係のコレクションは、オブザーバークラスにあります。

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    this.dep = new Dep()    // 实例化一个依赖管理器,用来收集数组依赖
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}

このようにして、オブザーバーに依存関係マネージャーも実装しました。

したがって、配列の依存関係がどのように収集されるかを確認し、コードに直接移動できます。

function defineReactive (obj,key,val) {
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get(){
      if (childOb) {
        childOb.dep.depend()
      }
      return val;
    },
    set(newVal){
      if(val === newVal){
        return
      }
      val = newVal;
      dep.notify()   // 在setter中通知依赖更新
    }
  })
}
 
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为value创建一个0bserver实例,如果创建成功,直接返回新创建的Observer实例。
 * 如果 Value 已经存在一个Observer实例,则直接返回它
 */
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

オブザーブ関数内で、最初に現在の受信データに__ob__属性があるかどうかを判断します。これは、前の記事で説明したように、データに__ob__属性がある場合、それはリアクティブタイプに変換されたことを意味するためです。データがまだ応答していないことを意味します。次に、new Observer(value)を呼び出して応答に変換し、データに対応するObserverインスタンスを返します。defineReactive関数で、最初にデータに対応するObserverインスタンスchildObを取得し、次にゲッターのObserverインスタンスで依存関係マネージャーを呼び出して依存関係を収集します。

これにより、通知に依存して更新に依存することが非常に簡単になります。

methodsToPatch.forEach(function (method) {
 const original = arrayProto[method]
 def(arrayMethods, method, function mutator (...args) {
   const result = original.apply(this, args)
   const ob = this.__ob__
   // notify change
   ob.dep.notify()
   return result
 })
})

この時点で、配列の依存関係の収集は基本的に達成されていますが、これはまだ終わっていません。アレイは1つのレイヤーではなく、複数のレイヤーである可能性があるため、詳細な検査が必要です。

export class Observer {
  value: any;
  dep: Dep;
 
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)   // 将数组中的所有元素都转化为可被侦测的响应式
    } else {
      this.walk(value)
    }
  }
 
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
 
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

配列内の新しい要素の検出

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   // 如果是push或unshift方法,那么传入参数就是新增的元素
        break
      case 'splice':
        inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素
        break
    }
    if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式
    // notify change
    ob.dep.notify()
    return result
  })
})

vueデータの全体的な応答原理は次のようになります。オブジェクトを監視したり、オブジェクトのプロパティを増減したり、配列の添え字の変更について配列を監視したりすることはできません。Vue.set()メソッドとVue.deleteメソッドがvueに追加されてそれらを処理します。

上記はすべて反応原理、または双方向データバインディングの原理です。

学習記録と同様に、次の記事を参照してください:https//blog.csdn.net/leelxp/article/details/106936827

おすすめ

転載: blog.csdn.net/weixin_44433499/article/details/114259116