Vue响应式原理

解析都在代码注释中;

<!DOCTYPE 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>Document</title>
</head>
<body>
  <div id='app'>
    {{name}}
    <input type="text" v-model='name'>
  </div>
  <script>
    //存储所有的订阅者
    class Dep{
      constructor(){
        //储存所有的订阅者
        this.subs=[]
      }
      //添加订阅者
      addSub(item){
        this.subs.push(item);
      }
      //通知所有的订阅者
      notify(){
        for(let ele of this.subs){
          ele.update();
        }
      }
    }
    //订阅类
    class Watcher{
      constructor(vm,node,name,type){
        Dep.target=this;
        this.vm = vm;//vue实例
        this.node = node;//节点Dom对象
        this.name=name;//该dom节点使用的vue实例上的data数据属性名
        this.type=type;//当给不同节点赋值时,表单需要用到value属性,而文本节点则需要通过nodeValue赋值
        this.update();
        Dep.target=null;
      }
      update(){
        //调用get方法获取数据的最新值
        this.get();
        //为节点赋值
        this.node[this.type]=this.value;
      }
      //获取vm实例上数据的最新值
      get(){
        this.value=this.vm[this.name];
      }
    }
    class Compile{
      constructor(node,vm){
        this.frag = this.nodeToFragment(node,vm);
      }
      //循环将所有节点
      //这里我们只涉及一层子节点
      nodeToFragment(node,vm){
        let fragment = document.createDocumentFragment();
        let child;
        while(child=node.firstChild){
          //将所有节点进行编译以及对双向绑定的表单元素绑定事件
          this.compileElement(child,vm);
          fragment.appendChild(child);
        }
        return fragment;
      }
      compileElement(node,vm){
        //编译元素节点以及文本节点
        //匹配文本节点的正则表达式
        let reg = /\{\{(.*)\}\}/;
        if(node.nodeType===1){
          //元素节点
          //获取节点的所有属性,检测是否有v-model,即是否需要进行双向数据绑定
          let attributes = node.attributes;
          for(let ele of attributes){
            if(ele.nodeName==='v-model'){
              let name = ele.nodeValue;
              node.addEventListener('input',(e)=>{
                vm[name]=e.target.value;
              })
              //订阅数据改变通知
              new Watcher(vm,node,name,'value');
            }
          }
        }
        if(node.nodeType===3){
          //文本节点
          if(reg.test(node.nodeValue)){
            let name = RegExp.$1;//获取第一个捕获组中的内容
            new Watcher(vm,node,name,'nodeValue');
          }
        }
      }
    }
    class Vue{
      constructor(params){
        //将传递过来的data内的数据挂载为实例的根元素
        let data = params.data;
        let keyArr = [];
        for(let key in data){
          this[key] = data[key];
          keyArr.push(key);
        }
        this.observe(keyArr,this)
        //编译模板文件
        this.el=params.el;
        let model = document.querySelector(this.el);
        //接收编译好的模板代码
        let compiledModel = new Compile(model,this).frag;
        //将编译好的模板代码添加至DOM中
        model.appendChild(compiledModel);
      }
      //循环递归遍历每一个属性
      observe(keyArr,self){
        keyArr.forEach(key=>{
          //循环判断每一个属性值如果是对象需要递归进行遍历
          if(Object.prototype.toString.call(self[key])==='[object Object]'){
            let dataArr = [];
            for(let keyOne in self[key]){
              dataArr.push(keyOne);
            }
            observe(dataArr,self[key]);
          }else{
            this.defineActive(self,key,self[key]);
          }
        })
      }
      //数据劫持
      defineActive(self,key,val){
          //创建一个储存订阅者实例的Dep实例
          let dep = new Dep();
          Object.defineProperty(this,key,{
            get(){
              //检测,如果Dep.target不为null,则代表有watcher实例在初始化中,需要将其放入Dep实例中存储
              if(Dep.target){
                dep.addSub(Dep.target);
              }
              return val;
            },
            set(newVal){
              if(val===newVal){
                return;
              }
              val=newVal;
              //当vue实例中的数据发生变化,通知所有的订阅者
              dep.notify();
            }
          })
      }
    }
    let vm = new Vue({
      el:'#app',
      data:{
        name:'jack',
        age:16
      }
    })
  </script>
</body>
</html>

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
旅游社交小程序功能有管理员和用户。管理员有个人中心,用户管理,每日签到管理,景点推荐管理,景点分类管理,防疫查询管理,美食推荐管理,酒店推荐管理,周边推荐管理,分享圈管理,我的收藏管理,系统管理。用户可以在微信小程序上注册登录,进行每日签到,防疫查询,可以在分享圈里面进行分享自己想要分享的内容,查看和收藏景点以及美食的推荐等操作。因而具有一定的实用性。 本站后台采用Java的SSM框架进行后台管理开发,可以在浏览器上登录进行后台数据方面的管理,MySQL作为本地数据库,微信小程序用到了微信开发者工具,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得旅游社交小程序管理工作系统化、规范化。 管理员可以管理用户信息,可以对用户信息添加修改删除。管理员可以对景点推荐信息进行添加修改删除操作。管理员可以对分享圈信息进行添加,修改,删除操作。管理员可以对美食推荐信息进行添加,修改,删除操作。管理员可以对酒店推荐信息进行添加,修改,删除操作。管理员可以对周边推荐信息进行添加,修改,删除操作。 小程序用户是需要注册才可以进行登录的,登录后在首页可以查看相关信息,并且下面导航可以点击到其他功能模块。在小程序里点击我的,会出现关于我的界面,在这里可以修改个人信息,以及可以点击其他功能模块。用户想要把一些信息分享到分享圈的时候,可以点击新增,然后输入自己想要分享的信息就可以进行分享圈的操作。用户可以在景点推荐里面进行收藏和评论等操作。用户可以在美食推荐模块搜索和查看美食推荐的相关信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值