手写MVVM

121 阅读1分钟
<!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">
        <input type="text" v-model='name' class='qqq'>
        <input type="text" v-model='age'>
        <h1>{{name}}</h1>
        <h2>{{age}}</h2>
    </div>
</body>
</html>
<!-- <script src="./node_modules/vue/dist/vue.js"></script> -->
<script>
  function observe(data){
    //  判断 data是否是一个对象
    if(({}).toString.call(data) !== '[object Object]')return ;
    let keys = Object.keys(data);// 获取所有的属性, 为了对每一个属性进行劫持
    keys.forEach(key=>{
      defineReactive$$1(data,key,data[key])
    })
  }
  function defineReactive$$1(target,key,val){
    var dep = new Dep();// 每一个 key 都有自己的 dep
    Object.defineProperty(target,key,{
      enumerable:true,
      get(){
        // console.log('get')
        if(Dep.target){
          // 为什么要有这个判断  能够触发get的方式有很多种
          dep.addSub(Dep.target)// 我们在事件池中放置的都是一些订阅者
        }

        return val
      },
      set(newV){
        if(newV !== val){
          val = newV
          dep.notify()
        }

      }
    })
  }


  function nodeToFragment(el,vm){
    // 把文档上的节点转到了 文档碎片上
    let fragment = document.createDocumentFragment();
    let child;
    while(child = el.firstChild){
      // debugger
      // child是每一个节点 我们要把每一个节点都编译了
      compile(child,vm)
      fragment.appendChild(child)
    }
    el.appendChild(fragment)
  }
  function compile(node,vm){
    // 编译 node节点; 先要判断 节点的类型; 看他是 元素节点还是文本节点; 元素节点我们要考虑是 行内属性和他的子节点
    // 文本节点直接进行替换即可
    // 1 3 8 9
    if(node.nodeType == 1){
      // 证明这是一个元素节点
      let attrs = node.attributes; // 获取所有的行内属性
      [...attrs].forEach(item=>{
        // 我们需要的是 v-xxx的行内属性 v-model  model  =  'name'
        console.dir(item)
        if(/^v-/.test(item.nodeName)){
          // 证明是 v-model;我们需要获取到 v-model后边对应的这个值
          let vName = item.nodeValue ;// name这个词汇
          let val = vm.$data[vName] ;// 获取到了 珠峰  这两个字
          new Watcher(node,vName,vm)
          node.value = val;// 把 珠峰 这两个字 放到 input框中
          node.addEventListener('input',(e)=>{
            vm.$data[vName] = e.target.value
          })
        }
      });
      // 以上处理的是行内属性  还有 子节点要考虑

      [...node.childNodes].forEach(item=>{
        // 递归编译 子节点
        compile(item,vm)
      })
    }else{
      // 文本节点  我们需要获取对应的文本字符串  然后把里边的小胡子转成真实内容
      // console.dir(node)
      let str = node.textContent;// "{{name}}"
      if(/\{\{(\w+)\}\}/.test(str)){
        // 证明里边有小胡子语法
        console.log(str)
        str = str.replace(/\{\{(\w+)\}\}/,(a,b)=>{
          // b ---> name
          new Watcher(node,b,vm)
          return vm.$data[b]
        })
        console.log(str)
        node.textContent = str;
      }
    }
  }

  //创造一个订阅武器
  class Dep{
    // 每一个属性 都应该有自己的订阅器
    constructor(){
      this.subs = [];
    }
    addSub(sub){
      this.subs.push(sub)
    }
    notify(){
      this.subs.forEach(sub=>{
        // 负责通知各个事件
        // sub就是一些 watcher实例
        sub.update()
      })
    }
  }
  //创造一个订阅者
  class Watcher{
    constructor(node,key,vm){
      Dep.target = this;// this就是watcher实例
      this.node = node;
      this.key = key;
      this.vm = vm;
      this.getValue();//这个代码可以让我们把 当前的这个watcher实例放到对应的事件池中;
      Dep.target = null
    }
    update(){
      // 负责更新DOM
      this.getValue();// 获取新的value值
      if(this.node.nodeType == 1){
        // 只考虑input框
        this.node.value = this.value
      }else{
        this.node.textContent = this.value
      }
    }
    getValue(){
      this.value = this.vm.$data[this.key];// 会触发我们的get函数
    }
  }

  function Vue(option){
    // 私有属性 $el 对应的是 我们的App元素
    // 私有属性 $data对应的是 我们传进来的data
    this.$el = document.querySelector(option.el)
    this.$data = option.data
    // 先去劫持数据
    observe(this.$data)// 负责劫持数据
    nodeToFragment(this.$el,this) // 负责模板编译  把模板中的 vue语法转成真实变量
  }
</script>
<script>
    let vm = new Vue({
        el:'#app',
        data:{
            name:"珠峰",
            age:11
        }
    });
</script>