js实现mvvm_1

手写mvvm_1

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <h1>{{ name  }}</h1>
    <!-- 这是一个注释 -->
    <h2>{{age}}</h2>
    {{   name  }}{{age }}

    <input type="text" v-model='name'>
    <input type="text" v-model='age'>
  </div>
  <!-- <script src="../node_modules/vue/dist/vue.js"></script> -->
  <script>
    function Vue(options) {
      // $el 存储的是当前元素
      this.$el = document.querySelector(options.el);

      // $data 存储的是data中的属性和值
      this.$data = options.data;
      //  $data 应该是被劫持的,以及它的每一个属性都应该被劫持
      observe(this.$data); // 将 this.$data 中的属性进行劫持

      // 将当前元素转译到文档碎片上,并将实例传过去
      nodeToFragment(this.$el, this);
    }

    // 实现数据劫持的核心: Object.defineProperty
    function observe(data) {
      for (let key in data) {
        if (data.hasOwnProperty(key)) {
          let value = data[key];
          let dep = new Dep();
          Object.defineProperty(data, key, {
            get() {
              // console.log('触发get函数');
              return value;
            },
            set(val) {
              // console.log('触发set函数');
              if(typeof val === 'object') {  
                // 如果赋值为对象,继续进行深度劫持
                observe(val); 
              }
              value = val;
            }
          });
          // 深度劫持
          if(typeof value === 'object') {
            observe(value);
          }
        }
      }
    }

    // 模板编译:将元素节点转移到文档碎片上
    function nodeToFragment(node, vm){
      // 创建一个文档碎片
      let fragment = document.createDocumentFragment(),
          child;
      
      // 将node的子节点转移到文档碎片上,直到全部转完为止
      while(node.firstChild) { // 只知道开始和结束条件,用while循环
        child = node.firstChild;
        // console.dir(child);

        // 每一次转移,都需要将节点进行编译
        // 在编译的时候,需要将编译的内容例如{{name}}跟实例的$data中的数据关联起来,所以需要把节点 node 和当前实例 vm 传给模板编译函数
        compile(child, vm);
        fragment.appendChild(child);
      };
      // console.log(node, node.firstChild, fragment);
      // 最后将编译好的模板"还"给元素节点
      node.appendChild(fragment);
    }

    function compile(node, vm) {
      // 对于每一种节点类型,编译的逻辑不一样
      /* 
       node.nodeType =1 :元素节点
       node.nodeType =3:文本节点,换行+文本内容
       node.nodeType =8:注释节点
      */
     if(node.nodeType === 1) {  // 处理元素节点
       // 1、获取元素节点的行内属性,看看是否有v-开头的指令(这里只考虑v-model)
       let attrs = node.attributes;  // 获取元素节点的所有行内属性(是一个类数组)
       [...attrs].forEach(item => {
         if(/^v\-/.test(item.nodeName)) { // 如果是 v- 开头的,获取其值
          // 获取该行内属性的属性值 "name"
          let key = item.nodeValue;  
          // 获取vm.$data中对应属性的值
          let val = vm.$data[key];  
          // 将拿到的值赋值给 input标签的value(放到input框中)
          node.value = val;
          // 监听input框的input事件,将更改的值同步到vm.$data上
          // 封装监听事件使用DOM2级事件绑定,这样绑定的方法,除非手动移除,否则不会被其他方法替换掉
          node.addEventListener('input', e =>{
            // console.log(e);
            vm.$data[key] = e.target.value;
            // console.log(vm.$data[key])
          });
          
         }
       });
      
       // 处理元素节点的子节点
       let ch = node.childNodes
       for(let i=0; i<ch.length; i++) {
        //  console.log(ch[i]);
         compile(ch[i], vm);
       }
     }

     if(node.nodeType === 3) { // 处理文本节点
      let txt = node.textContent;  // 获取文本节点内容 "{{ name  }}"
      let str = '';
      // 去除拿到的内容中的所有空格
      for(let i=0; i<txt.length; i++) {
        if(txt[i] != ' ') {
          str+= txt[i];
        }
      }
      str = str.replace(/{{(\w+)}}/g, (a,b) => {
        // console.log(a,b);
        return vm.$data[b];
      });
      node.textContent = str;
     }
    }

    // 最后使用观察者模式将 数据劫持 和 模板编译联系起来
    class Dep{
      constructor() {
        this.deps = [];
      }
      add(dep) {
        // 这里添加的是每一个被观察者
        this.deps.app(dep);
      }
      del(dep) {
        let index = this.deps.indexOf(dep);
        this.deps.splice(index, 1);
      }
      notify() {
        this.deps.forEach(item => {
          item.updata();
        })
      }
    }
    let vm = new Vue({
      el: '#app',
      data: {
        name: '测试',
        age: 30,
        obj: {
          a: 10
        }
      }
    });
  </script>
</body>

</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值