JavaScriptは簡単なMVVMフロントエンドフレームワークを実現する(ES 6構文)

6592 ワード

前言
フロントエンドの各フレームワークの台頭に伴い、私たちの普段の開発にかなりの便利さをもたらし、私たちはずっと応用面にとどまることができず、今日は自分で乞食版のMVVMの小さなフレームワークを実現しました.
完全コードgithubアドレス
効果




    
    MVVM



{{a}}

{{b.b}}

const vm = new Mvvm({ el: '#app', data: { a: 1, b: { b : 2 } } })

基本的にはvueの呼び出し方式を模倣します
実装手順
  • データハイジャックObserve
  • データエージェント(Mvvmオブジェクトがデータを処理できるようにする)
  • テンプレートコンパイルCompile
  • 発行購読
  • ビューデータに関連付ける
  • 双方向データバインディング
  • を実現する.
    コード解析
    // mvvm.js
    //       Mvvm,          new Mvvm()    
    class Mvvm {
        constructor(options){
            /**
             * options          
             * {
                    el: '#app',
                    data: {
                        a: 1,
                        b: { b : 2 }
                    }
                }
             */
            const {el,data} = options;
            this._data = data;
            Observe.observeData(data); //              data      
            this.mount(data); //     data     this,   Mvvm   
            Mvvm.compile(el,this); //       ,     HTML  {{a}} {{b.b}}
        }
        //  data       this 
        mount(data){
            //   data     defineProperty         this 
            for(let key in data){
                Object.defineProperty(this,key,{
                    enumerable:true, //    
                    get(){
                        return this._data[key];
                    },
                    set(newVal){
                        this._data[key] = newVal;
                    }
                })
            }
        }
        //       
        static compile(el,_that){
            new Compile(el,_that);
        }
    }
    //        
    class Observe{
        constructor(data){
            this.deepObserve(data);
        }
        deepObserve(data){
            let dep = new Dep(); //          
            for(let key in data){
                let value = data[key];
                Observe.observeData(value); //           
                this.mount(data,key,value,dep); //         
            }
        }
        mount(data,key,value,dep){
            //      data            defineProperty    
            Object.defineProperty(data,key,{
                enumerable:true,
                get(){
                    Dep.target && dep.addSub(Dep.target); //Dep.target = watcher        ,           
                    return value; // get    
                },
                set(newVal){
                    //      ,        
                    if(newVal === value){
                        return;
                    }
                    value = newVal;
                    Observe.observeData(newVal);//              
                    dep.notify(); //               
                }
            })
        }
        static observeData(data){
            //        ,        !!           mvvm
            if(typeof data !== 'object'){
                return ;
            }
            return new Observe(data);
        }
    }
    class Compile{
        constructor(el,vm){
            // vm = this
            vm.$el = document.querySelector(el);
            //         
            let fragment = document.createDocumentFragment();
            let child;
            while(child = vm.$el.firstChild){
                //     DOM,       (  ) 
                fragment.appendChild(child);
            }
            // replace   HTML     
            this.replace(fragment,this,vm);
            //             DOM,         
            vm.$el.appendChild(fragment);
        }
        //   DOM
        replace(fragment,that,vm){
            //         DOM  
            Array.from(fragment.childNodes).forEach(function (node) {
                let text = node.textContent; //    
                let reg = /\{\{(.*)\}\}/; //     {{}}    
                // nodeType === 3       
                if(node.nodeType === 3 && reg.test(text)){
                    let arr = RegExp.$1.split('.'); // RegExp.$1    b.b ,    .     
                    let val = vm; // val      vm    
                    arr.forEach(function (k) {
                        val = val[k]; // vm['b']         
                    });
                    //    node    watcher  ,            
                    new Watcher(vm,RegExp.$1,function (newVal) {
                        node.textContent = text.replace(reg,newVal);
                    });
                    //      {{a}} ==> 1
                    node.textContent = text.replace(reg,val);
                }
                //     
                if(node.nodeType === 1){
                    let nodeAttrs = node.attributes; //   DOM        
                    //        
                    Array.from(nodeAttrs).forEach((attr)=>{
                        let name = attr.name; //       v-model
                        let exp = attr.value; //       "a"
                        if(name.startsWith('v-')){
                            node.value = vm[exp]; //     a     input    
                        }
                        //   node    watcher  ,        
                        new Watcher(vm,exp,function (newVal) {
                            node.value = newVal; //        
                        });
                        //        
                        node.addEventListener('input',function (e) {
                            //          set  ,     dep.notify()              
                            vm[exp] = e.target.value;
                        },false);
                    })
                }
                //     DOM  
                if(node.childNodes){
                    that.replace(node,that,vm);
                }
            });
        }
    }
    //        
    class Dep{
        constructor(){
            this.subs = [];
        }
        addSub(sub){
            this.subs.push(sub);
        }
        notify(){
            this.subs.forEach(sub=>{
                sub.update();
            })
        }
    }
    // Watcher  ,   node     。        node  Watcher  ,     
    class Watcher{
        constructor(vm,exp,fn){
            this.vm = vm; // this  
            this.exp = exp; //  
            this.fn = fn; //     
            Dep.target = this; //       Dep,      target = this     watcher
            let val = vm;
            let arr = exp.split('.');
            arr.forEach(function (k) {
                val = val[k]; //               get,      watcher        
            });
            Dep.target = null;
        }
        //    watcher    update  ,    
        update(){
            //      this  ,       ,  watcher     ,   node      ,          
            let val = this.vm;
            let arr = this.exp.split('.');
            arr.forEach(function (k) {
                val = val[k];
            });
            this.fn(val); //      val          
        }
    }

    小結
    コードにはすでに詳細なコメントが書かれていますが、理解しにくい点があるかもしれません.このときはもっと練習して、MVVMの原理をもっと熟知させるかもしれません.