Vueのオブザーバーとパブリッシュ購読

34751 ワード

皆さん、こんにちは、今日はVueの中の観察者と、発表と購読の実現について説明します.
1、まず観察者を作成します.
  
/**
 *    
 */
class Watcher{
    constructor(vm,expr,cd){
        this.vm = vm;
        this.expr = expr;
        this.cd = cd;
        
        //    
        this.oldValue = this.get();
    }
    get(){ //    
        Dep.target = this; //       this  
        //                 
        let value = CompileUtil.getVal(this.vm,this.expr);
        Dep.target = null; //             watcher
        return value;
    }
    updata(){
        let newVal = CompileUtil.getVal(this.vm,this.expr); //    
        if(newVal !== this.oldValue){
            this.cd(newVal)
        }
    }
    
}

 
次に、パブリケーションとサブスクリプションのコンストラクション関数を作成します.
/**
 *  (    )         
 */
class Dep{
    constructor(){
        this.subs = [] //      watcher
    }
    //  
    addSub(watcher){ //   watcher
        this.subs.push(watcher)
    }
    //  
    notify(){
        this.subs.forEach(watcher=>watcher.updata());
    }
}

 
 
観察者にはこのようなコードがあります
   get(){ //    
        Dep.target = this; //       this  
        //                 
        let value = CompileUtil.getVal(this.vm,this.expr);
        Dep.target = null; //             watcher
        return value;
    }

 
jsは単一スレッドであるため,各観察者が古い値を取得するときに,この観察者にtarget属性を追加することができ,この属性はその観察者自身を指す.その後、このtargetをクリアします.
    
クリアする前に何があったのでしょうか?
実はデータハイジャックが発生しました.これは前編で述べました.
//      
    defineReactive(obj,key,value){
        this.observer(value); //           ,         ,        
        
        let dep = new Dep(); //                     
        
        Object.defineProperty(obj,key,{
            get(){
                //  watcher ,             watcher      
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set: (newVal)=>{
                if(value !== newVal){
                    this.observer(newVal); //          get set   
                    value = newVal;
                    dep.notify(); //           
                }
                
            }
        })
    }

 
データハイジャックでは、まず、パブリケーションとサブスクリプションの機能インスタンスをnewし、各プロパティにパブリケーションとサブスクリプションの機能を追加します.
また、このプロパティに値を再割り当てするときに、パブリッシュとサブスクリプションの機能を持つ値にサブスクリプション関数を実行させます.
 
2、はい、今すべての値にパブリッシュとサブスクリプション機能があります.では、このオブザーバーは誰に追加されますか.私たちのオブザーバーはまだ実行されていません.0.0
   
まず、v-modelはバインドする必要がある観察者です.数値は変化できるからでしょう.
model(node,expr,vm){ //node     expr     vm   
        // console.log(node)
        let fn = this.updater['modelUpdater'];
        
        //                 ,           
        new Watcher(vm,expr,(newVal)=>{ //newVal          ,        
            fn(node,newVal);
        });
        
        node.addEventListener('input',(e)=>{
            let value = e.target.value; //        
            this.setValue(vm,expr,value) //  v-model  
        })
        
        let value = this.getVal(vm,expr);
        // console.log(value)
        fn(node,value);
    },

 
 
  
あと{{}}式も1つ必要でしょうか.1つのラベルに複数の式が存在するので、式を巡ります
例:{{shool.name}{{shool.name}}
getContentValue(vm,expr){
        //                           
        return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            return this.getVal(vm,args[1]);
        });
    },
    text(node,expr,vm){
        let fn = this.updater['textUpdater']
        //console.log(expr) :{{ school.name }}
        let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
            
            //             
            new Watcher(vm,args[1],()=>{
                fn(node,this.getContentValue(vm,expr)); //         
            })
            
            return this.getVal(vm,args[1]);
        })
        // console.log(content) //
        fn(node,content);
    },

 
  
v-htmlも1つ追加する必要があるのではないでしょうか.もちろん、まだたくさんありますが、基本的にはこのような形式で、真似して書くことができます.
  
html(node,expr,vm,eventName){
        let fn = this.updater['htmlUpdater']
        
        //                 ,          
        new Watcher(vm,expr,(newVal)=>{ //newVal          ,        
            fn(node,newVal);
        });
        
        let value = this.getVal(vm,expr);
        
        fn(node,value);
    },

 
 
3、はい、vueのデータの双方向バインドが実現しました.以下は完全なコードです.理解して、実行してみてください.
  
/**
 *  (    )         
 */
class Dep{
    constructor(){
        this.subs = [] //      watcher
    }
    //  
    addSub(watcher){ //   watcher
        this.subs.push(watcher)
    }
    //  
    notify(){
        this.subs.forEach(watcher=>watcher.updata());
    }
}

/**
 *    
 */
class Watcher{
    constructor(vm,expr,cd){
        this.vm = vm;
        this.expr = expr;
        this.cd = cd;
        
        //    
        this.oldValue = this.get();
    }
    get(){ //    
        Dep.target = this; //       this  
        //                 
        let value = CompileUtil.getVal(this.vm,this.expr);
        Dep.target = null; //             watcher
        return value;
    }
    updata(){
        let newVal = CompileUtil.getVal(this.vm,this.expr); //    
        if(newVal !== this.oldValue){
            this.cd(newVal)
        }
    }
    
}

/**
 *     
 */
class Observer{
    constructor(data) {
        this.observer(data);
    }
    observer(data){
        //        
        if(data && typeof data === 'object'){
            
            for (let key in data) { //   data       
                this.defineReactive(data,key,data[key]);
            }
        }
    }
    //      
    defineReactive(obj,key,value){
        this.observer(value); //           ,         ,        
        
        let dep = new Dep(); //                     
        
        Object.defineProperty(obj,key,{
            get(){
                //  watcher ,             watcher      
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set: (newVal)=>{
                if(value !== newVal){
                    this.observer(newVal); //          get set   
                    value = newVal;
                    dep.notify(); //           
                }
                
            }
        })
    }
}

/**
 *     
 */
class Compiler{
    constructor(el,vm) {
        this.vm = vm;
        //  el                
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        // console.log(this.el);
        
        //                         
        let fragment = this.node2fragment(this.el);
        
        //           
        
        //          
        this.compile(fragment);
        
        //         
        this.el.appendChild(fragment);
    }
    
    //         v-   
    isDirective(attrName){
        return attrName.startsWith('v-');
        // return /^v-/.test(attrName)
    }
    
    //     
    compileElement(node){
        let attributes = node.attributes; //   ,     node         
        // console.log(attributes)
        attributes = [...attributes]
        // console.log(attributes)
        attributes.forEach(attr=>{ //       attr
            let {name, value:expr} = attr; //:expr  value       expr **school.name
            //     vue  
            if(this.isDirective(name)){  // v-model='asdad' v-on:click='dsa'
                let [,directive] = name.split('-');
                let [directiveName, eventName] = directive.split(':');
                //             *** v-if v-modle v-show v-else
                CompileUtil[directiveName](node,expr,this.vm,eventName);
            }
        })
    }
    
    //     
    compileText(node){ //          {{}}
        let content = node.textContent;
        if(/\{\{.+?\}\}/.test(content)){
            
            CompileUtil['text'](node,content,this.vm);
        }
        
    }
    
    //        dom       
    compile(node){ 
        let childNodes = node.childNodes; //  node      
        [...childNodes].forEach(child=>{
            if(this.isElementNode(child)){ //         
            
                this.compileElement(child); //      
                
                //                             
                this.compile(child);
            }else{ //    
                this.compileText(child); //      
            }
        })
    }
    
    //      ,     
    node2fragment(node){
        //        
        let fragment = document.createDocumentFragment();
        let firstChild;
        
        // node          firstChild   node              
        while(firstChild = node.firstChild){ 
            //appendChild     
            fragment.appendChild(firstChild);
        }
        return fragment;
    }
    
    //        
    isElementNode(node){
        return node.nodeType === 1;
    }
}

CompileUtil = {
    
    //            
    getVal(vm,expr){ 
        // 7. reduce()   
         //        (    )1.      ,2.        ,3.   4.     
         //  (     )   
         //
         expr = expr.trim()
        return expr.split('.').reduce((data,current)=>{ //[school,name] 
            // console.log(data,current)
            return data[current];
        },vm.$data);
    },
    setValue(vm,expr,value){
        expr.split('.').reduce((data,current,index,arr)=>{ //[school,name]
            if(index === arr.length - 1){ //            
                //console.log(data,current)
                //                 data         
                data[current] = value; //        school.name = xxxx
                
            }
            return data[current];
        },vm.$data);
    },
    model(node,expr,vm){ //node     expr     vm   
        // console.log(node)
        let fn = this.updater['modelUpdater'];
        
        //                 ,           
        new Watcher(vm,expr,(newVal)=>{ //newVal          ,        
            fn(node,newVal);
        });
        
        node.addEventListener('input',(e)=>{
            let value = e.target.value; //        
            this.setValue(vm,expr,value) //  v-model  
        })
        
        let value = this.getVal(vm,expr);
        // console.log(value)
        fn(node,value);
    },
    on(node,expr,vm,eventName){
        node.addEventListener(eventName, (e)=>{ // node    
            vm[expr].call(vm,e)
        })
    },
    html(node,expr,vm,eventName){
        let fn = this.updater['htmlUpdater']
        
        //                 ,          
        new Watcher(vm,expr,(newVal)=>{ //newVal          ,        
            fn(node,newVal);
        });
        
        let value = this.getVal(vm,expr);
        
        fn(node,value);
    },
    getContentValue(vm,expr){
        //                           
        return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            return this.getVal(vm,args[1]);
        });
    },
    text(node,expr,vm){
        let fn = this.updater['textUpdater']
        //console.log(expr) :{{ school.name }}
        let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
            
            //             
            new Watcher(vm,args[1],()=>{
                fn(node,this.getContentValue(vm,expr)); //         
            })
            
            return this.getVal(vm,args[1]);
        })
        // console.log(content) //
        fn(node,content);
    },
    updater: {
        //      value 
        modelUpdater(node,value){ 
            node.value = value;
        },
        htmlUpdater(node,value){
            node.innerHTML = value;
        },
        //      
        textUpdater(node,value){
            //textContent                 ,        。
            node.textContent = value;
        }
    }
}

class Vue{
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.computed = options.computed;
        this.methods = options.methods;
        //            
        if(this.$el){
             
            //       this.$data           Object.defineProperty    
            new Observer(this.$data);
            
            //  vm              vm.$data  
            this.proxyVm(this.$data);
            
            //     
            for (let key in this.computed) {
                Object.defineProperty(this.$data,key,{ //        
                    get:()=>{
                        return this.computed[key].call(this);
                    }
                })
            };
            
            //     
            for (let key in this.methods) {
                
                Object.defineProperty(this,key,{
                    get: ()=>{
                        return this.methods[key]
                    }
                })
            }
            
            //    
            new Compiler(this.$el,this);
        }
    }
    // vm.$data   
    proxyVm(data){
        for (let key in data) {
            //     vm.xxx     vm.$data.xxx
            Object.defineProperty(this,key,{ //this   Vue   
                get:()=>{ // vm        vm.$data  
                    // console.log(this)
                    return data[key]; //       
                },
                set: (newVal)=>{ //  vm         vm.$data  
                    data[key] = newVal
                }
            })
        }
    }
}