实现Vue的数据双向绑定

8 篇文章 0 订阅
7 篇文章 0 订阅

Vue2中的数据双向绑定使用到了Object.defineProperty做代理, 建议先了解一下 Object.defineProperty这个属性

不多说,直接上代码

创建myVue.js文件,在里面定义一个Vue类,实现基本的双向数据绑定

class Vue {
  constructor(options) {
    //通过选择器获取根对象
    this.$el = document.querySelector(options.el)

    //数据创造之前
    if (typeof options.beforeCreate == 'function') {
      options.beforeCreate.bind(this)()
    }
    //保存options对象
    this.$options = options

    // this.$watchEvent[key] = [event1,event2,event3]
    //设置一个对象专门保存修改更新的事件
    this.$watchEvent = {}
    //代理options的data数据,即:将数据绑定到this上
    this.proxyData()
    //劫持设置事件
    this.observe()

    //数据创造之后
    if (typeof options.created == 'function') {
      options.created.bind(this)()
    }

    //挂载之前
    if (typeof options.beforeMount == 'function') {
      options.beforeMount.bind(this)()
    }
    //把view的数据和事件进行绑定
    this.complie(this.$el)

    //挂载之后
    if (typeof options.mounted == 'function') {
      options.mounted.bind(this)()
    }
  }

  proxyData() {
    //循环通过get和set方法实现代理数据
    for (let key in this.$options.data) {
      Object.defineProperty(this, key, {
        configurable: false, //是否可配置的  默认false
        enumerable: true, //是否可通过循环读出 默认false
        // writable 定义是否可以修改
        get() {
          //获取this[key]时,即返回options的data[key]
          return this.$options.data[key]
        },
        set(val) {
          this.$options.data[key] = val
        }
      })
    }
  }

  observe() {
    //劫持事件
    for (let key in this.$options.data) {
      //获取data里的值,保存到value上
      let value = this.$options.data[key]
      let that = this
      Object.defineProperty(this.$options.data, key, {
        configurable: false, //是否可配置的  默认false
        enumerable: true, //是否可通过循环读出 默认false
        // writable 定义是否可以修改
        get() {
          // console.log('触发获取内容事件')
          //获取this[key]时,即返回options的data[key]
          return value
        },
        set(val) {
          // console.log('触发设置内容事件')
          value = val
          //触发key值的更新事件
          if (that.$watchEvent[key]) {
            that.$watchEvent[key].forEach((item, index) => {
              item.update()
            })
          }
        }
      })
    }
  }
  
  complie(cNode) {
    console.log([this.$el])
    cNode.childNodes.forEach((node, index) => {
      if (node.nodeType == 1) {
        //元素类型 getAttribute  hasAttribute
        //判断是否有v-html
        if (node.hasAttribute('v-html')) {
          let vmKey = node.getAttribute('v-html').trim()
          // console.log(this[vmKey])
          if (this.hasOwnProperty(vmKey)) {
            node.innerHTML = this[vmKey]
            let watcher = new Watch(this, vmKey, node, 'innerHTML')
            if (this.$watchEvent[vmKey]) {
              this.$watchEvent[vmKey].push(watcher)
            } else {
              this.$watchEvent[vmKey] = []
              this.$watchEvent[vmKey].push(watcher)
            }
            //删除节点事件
            node.removeAttribute('v-html')
          }
        }

        //判断是否有v-model属性
        if (node.hasAttribute('v-model')) {
          let vmKey = node.getAttribute('v-model')
          if (this.hasOwnProperty(vmKey)) {
            node.value = this[vmKey]
            let watcher = new Watch(this, vmKey, node, 'value')
            if (this.$watchEvent[vmKey]) {
              this.$watchEvent[vmKey].push(watcher)
            } else {
              this.$watchEvent[vmKey] = []
              this.$watchEvent[vmKey].push(watcher)
            }
            //删除节点事件
            node.removeAttribute('v-html')
          }
          node.addEventListener('input', (event) => {
            this[vmKey] = node.value
          })
        }

        //判断是否有@click属性
        if (node.hasAttribute('@click')) {
          let vmKey = node.getAttribute('@click').trim()
          node.addEventListener('click', (event) => {
            this.eventFn = this.$options.methods[vmKey].bind(this)
            this.eventFn(event)
          })
        }

        if (node.childNodes.length > 0) {
          this.complie(node)
        }
      }
      if (node.nodeType == 3) {
        //文本类型
        let reg = /\{\{(.*?)\}\}/g
        let text = node.textContent
        node.textContent = text.replace(reg, (match, vmKey) => {
          // console.log(match, vmKey)
          vmKey = vmKey.trim()
          if (this.hasOwnProperty(vmKey)) {
            node.innerHTML = this[vmKey]
            let watcher = new Watch(this, vmKey, node, 'textContent')
            if (this.$watchEvent[vmKey]) {
              this.$watchEvent[vmKey].push(watcher)
            } else {
              this.$watchEvent[vmKey] = []
              this.$watchEvent[vmKey].push(watcher)
            }
          }
          return this[vmKey]
        })
      }
    })
  }
}


class Watch {
  constructor(vm, key, node, attr, nodeType) {
    this.vm = vm //vm是实例化的app对象
    this.key = key //key即是绑定的vm触发的属性
    this.node = node //node即,此vm[key]数据绑定的html节点
    this.attr = attr //vm数据绑定的html节点的属性名称
  }
  update() {
    //数据更新之前
    if (typeof options.beforeUpdate == 'function') {
      options.beforeUpdate.bind(this)()
    }
    this.node[this.attr] = this.vm[this.key]

    //数据更新之后
    if (typeof options.updated == 'function') {
      options.updated.bind(this)()
    }
  }
}

然后我们就新建一个index.html文件,将myVue.js导入,进行测试

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./js/myVue.js" type="text/javascript"></script>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="msg" name="" id="" value="" />
      <h1>{{msg}}</h1>
      <h2>{{name}}</h2>
      <h1 v-html="msg"></h1>
      <button type="button" @click="changeEvent">修改msg</button>
    </div>
  </body>
  <script type="text/javascript">
    let options = {
      el: "#app",
      data: {
        msg: "hello world",
        name: "老王",
      },
      methods: {
        changeEvent() {
          this.msg = "Vue!!!"
        },
      },
      beforeCreate() {
        console.log("beforeCreate")
      },
      created() {
        console.log("created")
      },
      beforeMount() {
        console.log("beforeMounte")
      },
      mounted() {
        console.log("mounted")
      },
      beforeUpdate() {
        console.log("beforeUpdate")
      },
      updated() {
        console.log("updated")
      },
    }
    let app = new Vue(options)
    console.log(app)
  </script>
</html>

在这里插入图片描述
然后就可以看到基本的数据双向绑定已经实现了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值