• Vue中的观察者与发布订阅


      大家好,今天为大家讲解一下 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);
        },

      

      之后 {{}} 表达式是不是也需要一个啊,因为一个标签中是可以存在多个表达式的,所以我们要遍历表达式

      列如:<p> {{ shool.name }}  {{  shool.name  }} </p>

    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 是不是也需要添加一个啊,当然还有好多,基本都是这样的一种形式,可以仿照者写

      

    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
                    }
                })
            }
        }
    }
  • 相关阅读:
    rancher2.x添加node的坑。
    k8s相关端口表-以及周边工具
    基于Helm和Operator的K8S应用管理的分享
    iptables -F 与 -X 区别
    ansible批量免秘登录
    keepalived工作原理和配置说明
    k8s使用nfs动态存储(已测试成功)
    ansible-playbook快速入门填坑
    Service Account和其secrets 作用和场景,看了不亏。。
    kubectl管理多个k8s集群
  • 原文地址:https://www.cnblogs.com/nie5135257/p/12112442.html
Copyright © 2020-2023  润新知