双向绑定实现

class Vue {
  constructor(options) {
    this.$data = options.data
    // 监听者 监听this.$data的每一个属性 
    Observer(this.$data)

    // 属性代理 可以直接在vm上挂载 相当于找vm.name = vm.$data.name 做一层代理 
    // 循环每个$data上的属性 然后挂载到
    for (let key in this.$data) {
      // 为this定义属性
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          // 当有人向vm拿属性的时候 直接返回$data上的
          return this.$data[key]
        },
        set(newValue) {
          this.$data[key] = newValue
        }
      })
    }

    //调用模板编译 结构   数据 
    Compile(options.el, this)
  }


}
// 数据劫持
function Observer(obj) {
  // 递归遍历
  if (!obj || typeof obj !== 'object') return
  const dep = new Dep()
  for (let key in obj) {
    let value = obj[key]
    // 递归子节点
    Observer(value)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        console.log('触发get');
        // new 的watcher实例存入dep.subs中
        console.log(dep);

        Dep.target && dep.addSubs(Dep.target)
        return value
      },
      set(newValue) {
        console.log('触发set');
        value = newValue
        //由于对象重新赋值 get和set都会没有 所以要对新赋值的进行监听
        Observer(value)
        dep.notify()
      }
    })
  }

}

// 模板编译的方法 
function Compile(el, vm) {
  // 得到Dom元素
  vm.$el = document.querySelector(el)

  // 创建文档碎片
  const fragment = document.createDocumentFragment()
  // 把所有元素全部存入fragment里
  while (vm.$el.firstChild) {
    fragment.appendChild(vm.$el.firstChild)
  }
  // 进行模板编译
  replace(fragment)

  // 把 编译完成的文档碎片放回去
  vm.$el.appendChild(fragment)



  // 负责对DOM模板进行编译的方法
  function replace(node) {
    let reg = /\{\{(.*)\}\}/
    if (node.nodeType === 3) {
      // 是文本节点 终止递归 在此进行模板语言替换 {{ }} node.nodeValue == node.textContent
      let text = node.nodeValue
      // text.match(reg) == reg.exec(text)
      if (reg.exec(text)) {
        // 用reduce的方法 如果reg.exec(text)[1] 是info.a 类似的字符串 则先分隔 通过reduce滚雪球来取值
        const value = reg.exec(text)[1].split('.').reduce((newObj, key) => newObj[key], vm)
        console.log(reg.exec(text)[1].split('.'),'value');
        node.nodeValue = text.replace(reg, value)

        // 在此时创建watcher类的实例 因为此时作为node 你已经知道怎么更新自己的内容 
        // 为了再次更新 就把这行代码存入watcher实例的cb函数中 
        new watcher(vm, reg.exec(text)[1], (newValue) => {
          // 
          node.nodeValue = text.replace(reg, newValue)
        })
      }
      return
    }


    // 如果是input 且有v-model 
    if (node.nodeType === 1 && node.tagName === 'INPUT') {
      console.log(node);
      const attrs = Array.from(node.attributes)
      const finRes = attrs.find((x) => x.name === 'v-model')
      const expStr = finRes.value

      if (finRes) {
        console.log(finRes,'finRes');
        const value = expStr.split('.').reduce((newObj, k) => newObj[k], vm)
        node.value = value
        // 创建watcher
        new watcher(vm,expStr,(newValue) => node.value = newValue)
      }
      // 监听文本框的输 入 双向数据榜单 拿到文本框最新的值 把最新的值放到VM上
      node.addEventListener('input',  e => {
        const keyArr = expStr.split('.')
        console.log(keyArr,'keyArr');
        const obj = keyArr.slice(0,keyArr.length - 1).reduce((newObj,k) => newObj(k),vm)
        obj[keyArr[keyArr.length-1]] = e.target.value
      })
    }
    // 不是文本节点 需要递归 把它的子节点放入
    node.childNodes.forEach(child => replace(child))

  }

}
// 依赖收集者
class Dep {
  constructor() {
    this.subs = []
  }
  //提供添加subs的方法
  addSubs(watcher) {
    this.subs.push(watcher)
  }
  // 通知订阅者 调用订阅者存在subs里的回调函数
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

// 订阅者
class watcher {
  // cb回调函数中记录订阅者如何更新自己的文本内容
  // 还必须拿到最新的数据 ,所以要在new Watcher期间把VM传递进来 因为vm保存着最新数据
  // 还要根据Key来找到vm上自己对应的数据
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
    // 负责把watcher实例存在dep实例的subs数组中 
    Dep.target = this
    // 主要是为了去调用get 此时Dep.target还是指向Wather 存入subs
    key.split('.').reduce((newObj, k) => newObj[k], vm)
    Dep.target = null
  }
  // update 让发布者能够通知我们去更新
  update() {
    const value = this.key.split('.').reduce((newObj, k) => newObj[k], this.vm)
    this.cb(value)
  }

}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    姓名是:{{name}}
    <div>
      年龄是:{{age}}
    </div>
    <div>
      {{info.a}}
    </div>
    name:<input type="text" v-model="name">
    age:<input type="text" v-model="age">
  

  </div>

</body>
<script src="./MVVM.js"></script>
<script>
  const vm = new Vue({
    el: '#app',
    data: {
      name: "ruirui",
      age: '20',
      info: {
        a: 'a1',
        c: 'c1'
      }
    }
  })
</script>

</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值