公開購読から双方向データバインディングへ


前言
双方向データーバインディングはすでに話題になっています.原理について言えば、みんながデータハイジャックに言及できると思います.しかし、どのように完全に双方向データバインディングの疑似コードを実現するかについては、多くの人が深く研究していないと思います.そこで、本稿では整理したリリースによる購読モードを利用して、浅いものから深いものまで双方向データバインディングを実現する.
購読モードを公開
双方向データバインディングの下の設計モードは、購読モードのリリースです.
  • による購読モードの発表は、観察者モード
  • とも呼ばれる.
  • 観察者は、オブジェクトが変更された場合、各観察者に通知
  • を受信することができます.
    通俗的に例を引く
    一番ポピュラーなものを挙げると、紅ちゃん、明ちゃん、シロさんがたくさん並んでいるAJ 1に注目します.
    class Pinduoduo {
      constructor() {
        //    
        this.subscribers = [];
      }
      //     
      subscribe({name, callback}) {
        if (~this.subscribers.indexOf(name)) return;
        this.subscribers.push({
          name, callback
        });
      }
      //       
      publish() {
        this.subscribers.forEach(({name, callback}) => {
          let prize = 666;
          if (name === '  ') prize = 100;
          callback && callback(name, prize);
        })
      }
    }
    
    
    const pinInstance = new Pinduoduo();
    const commonFn = (name, prize) => {
      console.log(`${name}        ,AJ1      ${prize}`)
    }
    
    
    //   
    pinInstance.subscribe({
      name: '  ',
      callback: commonFn
    });
    pinInstance.subscribe({
      name: '  ',
      callback: commonFn
    });
    pinInstance.subscribe({
      name: '  ',
      callback: commonFn
    });
    
    
    //   
    pinInstance.publish();
    //   
    //           ,AJ1      666
    //           ,AJ1      100
    //           ,AJ1      666
    したがって、リリース購読モードを実現するための2つのポイントを覚えてください.リリース(トリガ)&購読(傍受)
    EventEmitter
    これにより、EventEmitterの疑似コードも実現できます.
    function EventEmitter() {
        this.events = Object.create(null);
    }
    
    //       
    EventEmitter.prototype.on = (type, event) => {
        if (!this.events) this.events = Object.create(null);
        if (!this.events[type]) this.events[type] = [];
        this.events[type].push(event);
    }
    
    //       
    EventEmitter.prototype.emit = (type, ...args) => {
        if (!this.events[type]) return;
        this.events[type].forEach(event => {
            event.call(this, ...args);
        })
    }
    
    //   
    function Girl() {}
    //     
    Girl.prototype = Object.create(EventEmitter.prototype);
    const lisa = new Girl();
    lisa.on('  ', () => {
        console.log('   !');
    });
    
    lisa.emit('  ');
    
    // console:    !
    双方向データバインディングを深入にする
    下記の例definePropertyは、以下のコードに基づいて構成されている.

    最も簡単な実現
    注釈しないでください.みんなが読めます.
    const inputDom = document.getElementsByTagName('input')[0];
    const textDom = document.getElementsByTagName('p')[0];
    inputDom.addEventListener('input', e => {
        const val = e.target.value;
        textDom.innerText = val;
    });
    defineProperty実現
    1、極簡略版
    const vm = {
        data: ''
    };
    const inputDom = document.getElementsByTagName('input')[0];
    const textDom = document.getElementsByTagName('p')[0];
    
    Object.defineProperty(vm, 'data', {
        set(newVal) {
            if (vm['data'] === newVal) return;
            //         
            textDom.innerText = newVal;
        }
    });
    
    
    inputDom.addEventListener('input', e => {
        vm.data = e.target.value;
    });
    2、階段版は属性を変えたり、domを追加したりすれば、多重化できなくなります.私たちは反復して、複数のv-modelの状況に適応できる.
    まず整理して何をするべきですか?
  • 追加対象ハイジャックv-model
  • 購読者管理センターを追加し、新規購読者と購読者に通知して
  • を更新します.
  • は、Object.definePropertyを巡回して、Dom Treeおよびv-modelを解析する.v-textにイベントバインディングの変更を行い、v-modelに購読者を追加し、v-textに購読してビューの更新を実現する.
  • /*
     *       
    */
    const vm = {
        data: ''
    };
    
    function observe(obj) {
        Object.keys(obj).forEach(key => {
            let val = obj[key];
            Object.defineProperty(obj, key, {
                get() {
                    return val;
                },
                set(newVal) {
                    if (newVal === val) return;
                    //   vm    
                    val = newVal;
                }
            })
        })
    }
    /*
     *         
    */
    
    const Dep = {
        target: null,
        subs: [],
        addSubs(sub) {
            this.subs.push(sub)
        },
        notify() {
            this.subs.forEach(sub => {
                sub.update();
            });
        }
    }
    getterにウォッチを追加し、setterでデータの変化を観測した時、すべての「購読者」の更新をトリガします.
    // ...
    get() {
        //    target        watcher  
        if (Dep.target) Dep.addSubs(Dep.target);
        return val;
    },
    set(newVal) {
        if (newVal === val) return;
        //   vm    
        val = newVal;
        Dep.notify();
    }
    // ...
    次に【購読者】vmを定義し、この例では、各nodeノードとして理解することができる.
    function Watcher(node, vm, name) {
        Dep.target = this;
        this.node = node;
        this.vm = vm;
        // name      key
        this.name = name;
        //  watcher   dep 
        this.update();
        Dep.target = null;
    }
    
    // Watcher  update   get  
    Watcher.prototype = {
        update() {
            this.get();
            this.node.innerText = this.value;
        },
        //          getter Dep.addSub
        get() {
            this.value = this.vm[this.name];
        }
    }
    その後、対応するノードに対して解析処理を行う.
    function complie(node, vm) {
        if (node.nodeType === 1) {
            [...node.attributes].forEach(attr => {
                const name = attr.nodeValue;
                if (attr.nodeName === 'v-model') {
                    node.addEventListener('input', e => {
                        vm[name] = e.target.value;
                    })
                } else if (attr.nodeName === 'v-text') {
                    new Watcher(node, vm, name)
                }
            })
        }
    }
    今はノードごとにバインディング処理ができます.
    function MVVM(id, vm) {
        observe(vm);
        const node = document.getElementById(id);
        //  fragment    ,      
        const fragment = document.createDocumentFragment();
        let child;
        while(child = node.firstChild) {
            fragment.appendChild(child)
        }
    }
    呼び出し
    MVVM(vm);
    全体のコードは最初はかなり遠回りに見えるが、watcherobservecomplieDepWactherといういくつかの概念を理解すれば、MVVMがほぼ理解できると信じている.
    (未完は継続します.ProxyのMVM実現方法を更新します.)