Vue双方向データバインドの原理をすばやく理解

6370 ワード

一方向バインドは非常に簡単で、ModelをViewにバインドし、JavaScriptコードでModelを更新すると、Viewは自動的に更新されます.一方向バインドがあれば、双方向バインドがあります.ユーザーがViewを更新すると、Modelのデータも自動的に更新されます.これは双方向バインドです.このようなdom操作から解放されて全身がすっきりするものは、その原理を研究しなくてもいいですか?
Vueソースの英語解釈は詳しいです.以下のコードは、原理の説明にのみ使用されます.滴滴商业FEDの読解顺序を参考にコードをざっと过ごし、考え方に合わせてコードを见ることをお勧めします.
2つのコア
研究の前に、Vueがデータバインディングを実現する2つの核心理念を理解しなければならない.すなわち、
  • Object.defineProperty()リスニングデータの変動
  • オブザーバ(パブリッシュ-サブスクライバ)モードデータに対応する論理操作の関係はどうですか.一言で言えば、1つのページが複数の購読で同じデータを使用し、definePropertyで変更を傍受し、パブリッシャーが所有しているデータを更新するようにサブスクライバに通知する.

  • キーワードget/set
    Objectを使用します.defineProperty()のget/setは、new Vue({})に入力されたすべてのデータオブジェクトに対して、属性取得(get)および設定(set)時に対応する論理を追加するためのデータ傍受を行う.
    
      // ---------------    ----------------------
      observe = function(value){
        //     
        if(!value || typeof value !== 'object'){
            return
        }
        return new Observer(value)
      }
      // ------
      class Observer{
        constructor(value){
            this.value = value
            this.walk(value)
        }
        walk(value){   //         
            Object.keys(value).forEach(key => this.convert(key, value[key]))
        }
        convert(key, val){
            defineReactive(this.value, key, val)
        }
      }
      defineReactive = function(obj, key, val){
        var dep = new Dep()
        //            
        var chlidOb = observe(val)
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=> {
                //     watcher    
                if(Dep.target){
                    dep.addSub(Dep.target)
                }
                // -------
                return val
            },
            set: (newVal) => {
                if(val === newVal) return
                val = newVal
                // -------
                //        
                chlidOb = observe(newVal)
                //            
                dep.notify()
            }
        })
      }
    

    一部の枝葉を取り除き、残りのObject.definePropertyは1つのデータを傍受することができます.コードを繰り返し貼らないでください.
    オブザーバ(パブリッシュ-購読者)モード
    WHYはなぜこのパターンを使うのですか?オブザーバーモードは,動作ベースの大規模なアプリケーションを開発する有力な手段である.1回のブラウザ・セッション中に、アプリケーションで数十回、数百回、数千回のさまざまなイベントが断続的に発生する可能性があります.イベントにリスナーを登録する回数を減らして、観察者オブジェクトがイベントリスナーを借りて様々な動作を処理し、すべてのサブスクライバに情報を委任することで、メモリ消費を低減し、インタラクティブなパフォーマンスを向上させることができます.これにより,同じ要素に絶えず新しいイベントリスナーを追加する必要がなくなる.これにより、システムのオーバーヘッドを低減し、プログラムのメンテナンス性を向上させることができます.(JavaScript設計モード)頻繁なデータ操作がこのモードと非常に一致する観察者モードは、実質的にプログラム内のオブジェクトの状態を観察し、変更が発生したときに通知することができます.オブザーバモードには2つの役割があります.
  • オブザーバー(パブリッシャ)
  • 被観察者(購読者)
  • パブリッシャ:
  • []サブスクライバを管理するための配列
  • addSub()サブスクライバ
  • を追加
  • notify()メッセージを発行するために使用され、サブスクライバに新しいサブスクライバ情報があることを通知する
  • // ------------------------------------
      class Dep{
        constructor(){
            this.subs = []  //       
        }
        addSub(sub){
            //    
            var alreadyExists = this.subs.some( (el) => {
                return el === sub
            })
            if (!alreadyExists) {
                this.subs.push(sub)
            }
        }
        notify(){
            //         (Watcher),            
            this.subs.forEach((sub) => sub.update())
        }
      }
    

    購読者:
  • value自体の値です.ここでは、パブリッシャがパブリッシュした値
  • を保存するために使用されます.
  • updata()パブリッシャ更新通知
  • を受信
    // ----------------------------------
      class Watcher{
        constructor(vm, expOrFn, cb){
            this.vm = vm //     
            this.cb = cb //             
            this.expOrFn = expOrFn //       
            this.val = this.get() //             
        }
        //     
        update(){
            this.run()
        }
        run(){
            const val = this.get()
            
            if(val !== this.val){
                this.val = val;
                this.cb.call(this.vm)
            }
        }
        get(){
            //      (Watcher)       
            Dep.target = this 
            const exp = this.expOrFn //    ,            
            var val = this.vm._data[exp] //       
            Dep.target = null
            return val;
        }
        
      }
    

    統合方法
    これらのコアポイントを整理した後、部品を持っています.次はどうやって組み立てるのでしょうか?オブザーバーモードを使っている以上、誰がパブリッシャーですか?購読の追加方法まず、私たちが傍受しているすべてのデータは、データが変更されたときに各サブスクライバに通知できるように、パブリッシャであるべきです.では、そのデータ傍受を初期化するとき(defineReactive)、関数内のvar dep = new Dep()を使用することができます.
    defineReactive = function(obj, key, val){
        var dep = new Dep()
        ...
    }
    

    では、購読者は、前述したように、get/setのときに対応する論理を追加するのが役に立ちます.get:getを呼び出すと、現在のパブリッシャdepにサブスクライバを追加できます.注意!追加です./**/ここには、購読者の作成という比較的迂回した点があります.サブスクライバの作成に伴うべきは、ページのどこかでこのデータが必要であり、{{}}が現れたと言える.このときwatcherがある.このときwatcherクラスを振り返ると,new Watcher(this, expOrFn, cb)でサブスクライバの値を初期化するためにget()this.val = this.get() // が呼び出され,watcherのパラメータではどのパブリッシャの値を取得する必要があるかが分かる.
        const exp = this.expOrFn//    ,          
        var val = this.vm._data[exp] //       
    

    自然に、このステップはパブリッシャーのget()をトリガーし、Dep.target = thisを見て、watcher全体をサブスクライバのキューに追加することができます.set:setは、パブリッシャーのデータが変更されるとsetが呼び出され、新しい値が更新されると、パブリッシャーのすべてのサブスクライバに情報の更新を通知します.
    サブスクライバの追加とパブリッシャデータの更新の2つの機能が保持されています.
    class Vue{
        constructor(options = {}){
            //    $options   
            this.$options = options
            //     data   
            let data = this._data = this.$options.data
            //    data        Vue   
            Object.keys(data).forEach(key => this._proxy(key))
            //     
            console.log('listen data :')
            observe(data)
        }
        //             ,             
        // ------------------{{}}---------------------
        $watch(expOrFn, cb){
            //
            new Watcher(this, expOrFn, cb)
        }
        // -------------------------------------------
        _proxy(key){
            Object.defineProperty(this, key, {
                configurable: true,
                enumerable: true,
                get: () => this._data[key],
                set: (val) => {
                    //    
                    this._data[key] = val
                } 
            })
        }
        
      }
    
    
      // test-----------
      var t = new Vue({
          data: {
              name: 'NAME'
          }
      })
      t.$watch('name', () => console.log('cb:the one'))
      t.$watch('name', () => console.log('cb:the second'))
      t.name = 'ghjk'
    

    このVue双方向データバインディングの簡単な論理も基本的に完成し,インタフェースとの解析的インタラクションが残っている.
    現在のフロントエンド環境では、双方向データバインドはほとんどフレームワークを選択する標準であると信じています.ざっと計算すると、半週間のVue双方向データバインドを研究し、わずかに得られ、記録をし、不足したり、間違いがあったりして、指摘してください.