Vue MVVMフレームワークの実現原理


MVVMのフレームワーク原理
  • データハイジャック
  • パブリッシュ購読モード
  • 原理を実現する過程
  • dataオプションの属性を巡回し、データの観測を追加し、observeの方法を実行し、Objectを使用する.definePropertyメソッドをgetメソッドとsetメソッドに変換し、データのハイジャックを実現し、各要素ノードを判断するcompilerメソッドを追加し、テキストノードであれば命令テンプレートに基づいてデータ
  • を置換する
  • データが変化すると、observeのsetメソッドがトリガーされ、直ちにDep.notify()メソッドが呼び出され、すべてのサブスクライバを巡り始め、実行者のUpdateメソッドが呼び出され、サブスクライバは通知を受けた後にビューを更新する
  • .
  • v-model実装データの双方向バインドcomplierメソッドで各要素ノードタイプを判定する場合、ラベルノードであれば、v-model命令が存在するか否かを判定し、存在するとサブスクライバを作成し、そのラベルノードの初期値を設定し、inputイベントリスニングを追加し、入力ボックスに入力された値が変化するとset,getメソッドを実行し、ビューを更新する
  • .
    実装原理のコード
    index.html
    
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Documenttitle>
    head>
    <body>
      <div id="app">
        {{ message }}
        <div>{{message}}div>
        <p>{{count}}p>
        <input type="text" v-model="message"/>
        div>
      div>
      	
    	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
    	
    	<script src="./mvvm.js">script>
    	<script>
    	    var app = new MVVM({
    	      el: '#app',
    	      data: {
    	        message: 'hello world',
    	        count: 1
    	      }
    	    });
    	 script>
     body>
    

    mvvm.js
    
    //      publish
    class Dep{
      constructor(){
        this.subs = [];
      }
      //     
      addSub(watcher){
        this.subs.push(watcher);
      }
      //         ,       
      notify(newVal){
        this.subs.forEach(sub=>{
          sub.update(newVal);
        })
      }
    }
    
    //       subscribe   dom  
    class Watcher{
      constructor(cb){
        this.cb = cb;
      }
      update(newVal){
        console.log('   ....');
        this.cb(newVal);
      }
    }
    
    //         ,  key     , new  Dep
    // new Dep()
    //       , new  watcher
    // new Watcher()
    //         ,     
    // addSub()
    //     ,    
    // notify()
    
    let watch = null;
    
    class MVVM{
      constructor(options){
        //           
        this.$options = options;
        this.$data = this._data = options.data;
        //       
        this.observer(this.$data);
        //     
        this.compiler(options.el);
      }
      //     
      observer(data){
        //   data   key value
        Object.entries(data).forEach(([key, value])=>{
          //      
          console.log('   :', key);
          const dep = new Dep();
          //  key           
          Object.defineProperty(data, key, {
            configurable: true,
            enumerable: true,
            //     
            set(newVal){
              // console.log('set run......');
              if(newVal !== value){
                //      
                value = newVal;
                //     dom
                console.log('    .....');
                dep.notify(newVal);
              }
            },
            //     
            get(){
              // console.log('get run......');
              console.log('    .....');
              if(watcher){
                dep.addSub(watcher);
                watcher = null;
              }
              return value;
            }
          });
        })
      }
    
      //     
      compiler(el){
        //        dom
        const element = document.querySelector(el);
        //        dom
        this.compilerNode(element);
      }
    
      compilerNode(element){
        //        dom       
        const childNodes = element.childNodes;
        //         ,    
        Array.from(childNodes).forEach(node=>{
          const {nodeType, textContent} = node;
          //       
          if(nodeType === 3){
            //                 
            let reg = /\{\{\s*(\S*)\s*\}\}/;
            // 
            if(reg.test(textContent)){
              //   ,dom    
              //      
              console.log('   :', RegExp.$1);
              watcher = new Watcher((newVal)=>{
                //       
                node.textContent = newVal;
              });
              //      dom
              node.textContent = this.$data[RegExp.$1];
            }
          }
          
          //      
          else if(nodeType === 1){
            //          
            let attrs = Array.from(node.attributes);
            //       
            attrs.forEach(attr=>{
              //         
              if(attr.name.startsWith('v-')){
                //   ,     
                let dirName = attr.name.substr(2);
                if(dirName === 'model'){  //v-model="message"
                  let key = attr.value;
                  //      
                  watcher = new Watcher((newVal)=>{
                    node.value = newVal;
                  });
                  //      
                  node.value = this.$data[key];
                  //         
                  node.addEventListener('input', (ev)=>{
                    this.$data[key] = ev.target.value;
                  });
                }
                else if(dirName === 'bind'){
    
                }
              }
            })
          }
    
          //     ,       
          if(node.childNodes.length > 0){
            //      
            this.compilerNode(node);
          }
        })
      }
    }