Vue3实现数据响应式

138 阅读2分钟

今天是周末,难得的没有加班,没有出去浪,最近感觉很是无聊呀,可能我成了隔夜的杯中水吧,没有落灰,可能中了一种毒,这种毒里边还有一种蔬菜,那就是香菇呀,哈哈哈,猜到了吧,中了好菇毒,闲下来还是研究一下前端的知识吧,毕竟学海无涯,不进则退,再者也应该为了以后打算,毕竟还是要为了以后买各种东西,包括什么车啊房啊,毕竟凡人一个还是需要这些俗物的,哈哈哈,生活不止眼前的苟且,还有诗和远方。今天就看看Vue3到底是怎么实现数据响应的,数据变了然后视图也变了。

  1. Proxy数据代理和数据劫持

    Proxy 也就是代理,可以帮助我们完成很多事情,例如对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。具体参考用法:

    <div class="box"></div>
    <script>
      let data = {
        name:"xiaohua",
        age:25
      }
      
      // 用法 const p = new Proxy(target, handler)  
      //handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
      let p = new Proxy(data,{
        set(target,prop,newValue){
          console.log(targer,prop,newValue);
          return Reflect.set(...arguments); // Reflect 就是和Proxy配合 ,set将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
        },
        get(target,prop){
          console.log(target,prop,newValue);
          return Reflect.get(...arguments); //获取对象身上某个属性的值,类似于 target[name]。
        }
      })
      p.age = 21; // 改变属性值,调用了Proxy的set方法 console 出的是object(data),age,21(新值)
      console.log(p.name);  // 调用了get ,console 出来的是21
    </script>
    
  2. 实现数据渲染

    想一下我们使用vue的时候,是不是数据data里边的变量,能使用 双花括号 写上变量的名称就可以渲染到浏览器上了呢,那么来看看到底是怎么实现的呢?那我们上边说的Proxy不可能随便说说的吧,怎么会呢?无事不登三宝殿,来吧用起来吧。

    <div id="app">
      {{name}}
      <div>{{message}}</div>
    </div>
    <script src = "myvue.js"></script>
    <script>
      let vm = new vue({   // 自己写一个小的vue 吧。激动么?
        el:"#app",
        data:{
          name:"xiaohua",
          message:"带着小明"
        }
      })
    </script>
    
    class vue {
      constructor(option){ // 实例中的参数
        this.option = option;
        this._data = this.option.data;
        this.el = document.querySelector(this.option.el);
        this.compileNode(this.el);
      }
      compileNode(el){  // 进行渲染的函数来了啊
        let child = el.childeNodes;
        console.log(child);
        childe.forEach((node)=>{  // 用[...child]可以保证是数组 
          if(node.nodeType===3){
            console.log("这是文本的节点");
            let text = node.textContent; // 应该是一个{{name}}这样的东西
            //进行匹配
            let reg = /\{\{\s* ([^\s\{\}]+)  \s*\}\}/;  // 正则 [ ]表示取反,中间是 两端是{{ 但是中间不能再是{了 
            if(reg.test(text)){
              let $1 = RegExp.$1; //匹配的第一个元素 也就是name
              //if(this._data[$1]) 写成下边这样
              this._data[$1]&&(node.textContent=text.repalce(reg,this_data[$1]));
            }
          }else if(nodeType===1){ // 也就是节点元素 div或者其他
            console.log("这是一个节点元素");
            this.compileNode(node);
          }
        })
      }
    }
    
  3. 双向数据绑定

    我们渲染完数据了,那肯定想知道v-model这种双向数据绑定的了,不说下去我感觉自己都得好奇死,现在坐下来用小脑袋想一想,v-model 事件,1.要判定node上是不是有v-model属性,2.监听v-model的值的变化去改变data中的数据,3.监听一下(自定义一下这个事件)这个数据变化,再把这个渲染到页面有这个值的元素中

    <div id="app">
      <input type="text" v-model="test">
      {{test}}
    </div>
    <script>
      let vm = new vue({
        el:'#app',
        data:{
          test:"双绑定数据"
        }
      });
      console.log(vm)
      console.log(vm._data.name);
    </script>
    
    class vue {
      constructor(option){ 
        this.option = option;
        this._data = this.option.data;
        this.el = document.querySelector(this.option.el);
        this.compileNode(this.el);
      }
      compileNode(el){  
        let child = el.childeNodes;
        console.log(child);
        childe.forEach((node)=>{ 
          if(node.nodeType===3){
            let text = node.textContent; 
            let reg = /\{\{\s* ([^\s\{\}]+)  \s*\}\}/; 
            if(reg.test(text)){
              let $1 = RegExp.$1; 
              this._data[$1]&&(node.textContent=text.repalce(reg,this_data[$1]));
            }
          }else if(nodeType===1){ 
            // 1. 这是一个input 元素在这里完成第一步 
            let attr = node.attributes; // 获取所有节点属性对象
            if(attr.hasOwnProperty('v-model')	){// 对象中是否含有属性为v-model的
              let keyName = attr['v-model'].nodeValue;
              node.value = this.data_[keyName];//先把data中的数据放进input的value里边  属于渲染
             	// 2.监听一下input值的变化,改变data的值
              node.addEventListener('input',e=>{
                this._data[keyName] = node.value; 
              })
            }
            this.compileNode(node);
          }
        })
      }
    }
    
    // 3. 怎么监听data数值中的变化呢?那是不想到上边的数据劫持,正戏来了,开始的proxy该用上了,劫持一下数据
    class vue extends EventTarget{
      constructor(option){ 
       	super();    //进行继承
        this.option = option;
        this._data = this.option.data;
        this.el = document.querySelector(this.option.el);
        this.compileNode(this.el);
      }
      observe(data){ 
        let _this = this ;
        this._data = new Proxy(data,{
          set(target,propertyKey,newValue){
            console.log(propertyKey); // 对应data的test
             let event =  new CustomEvent(propertyKey,{ // 自定义一个事件,在渲染的时候触发这个函数
               detail:newValue
             });
            _this.dispatchEvent(event); // 触发事件 注意this的指向 ,然后将这事件绑定到渲染{{}}的值的时侯
            return Reflect.set(...arguments);
          }
        })
      }
    }
    
    // 查漏补缺一下
    
    // 在学习一下自定义事件 ,要自定义事件必须继承一下自定义事件 EventTarget的方法   
    //extends是继承   EventTarget 是将会创建一个新的EventTarget 对象实例。
    
    // 创建一个事件对象,名字为newEvent,类型为build 
    var newEvent = new CustomEvent('build', { bubbles:true,cancelable:true,composed:true });
           
    //给这个事件对象创建一个属性并赋值,这里绑定的事件要和我们创建的事件类型相同,不然无法触发 
    newEvent.name = "新的事件!";
    
    //将自定义事件绑定在document对象上 
    document.addEventListener("build",function(){
      alert("你触发了使用CustomEvent创建的自定义事件!" + newEvent.name);
    },false)
    //触发自定义事件 ,也叫做发布吧
    document.dispatchEvent(newEvent); 
    
  4. 完整代码

    class vue extends EventTarget{
        constructor(option){  
            super();
            this.option=option;
            this._data = this.option.data;
            this.el = document.querySelector(this.option.el);
            this.observe(this._data)
            this.compileNode(this.el);
        }
        observe(data){
            let _this = this;
            this._data = new Proxy(data,{
                set(target,prop,newValue){
                    console.log(prop);
                    let event =  new CustomEvent(prop,{
                        detail:newValue
                    });
                    _this.dispatchEvent(event);  
                    return Reflect.set(...arguments);
                }
            })
        }
        compileNode(el){  
            let child =el.childNodes;
            console.log(child);
            child.forEach(node=>{
                if (node.nodeType === 3) { 
                    let text = node.textContent;
                    let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/;  
                    if (reg.test(text)) {
                        let $1 = RegExp.$1;
                        this._data[$1]&&(node.textContent = text.replace(reg,this._data[$1]));
                        this.addEventListener($1,c=>{  //将上边自定义的函数,绑定到上边
                            node.textContent =  text.replace(reg,c.detail);
                        })
                    }
                }else if(node.nodeType===1){
                    let  attr  = node.attributes;
                    console.log(attr);
                    if (attr.hasOwnProperty('v-model')) {
                        let keyName = attr['v-model'].nodeValue;
                        node.value = this._data[keyName];
                        node.addEventListener('input',e=>{
                            this._data[keyName] = node.value;  
                        })
                        console.log(keyName);
                    }
                    this.compileNode(node);
                }
            })
        }
    }