vue双向绑定原理解析实现

前提: 因为大量使用vue,趁项目比较闲的时候整理一下vue最核心的双向绑定的原理

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的

项目演示地址: 请点击

首先需要明白 Object.defineProperty() :对js中的对象进行属性添加和属性修改

 // 定义一个对象
  var O={}
  // 为对象添加attr属性
  Object.defineProperty(O,'attr',{
    set:function(value){
      attr=value
    },
    get:function(){
      return attr
    }
  })
  //通过set方法设置attr属性
  O.attr='我的名字叫做安'
  console.log(O.attr)

在这里插入图片描述

首先:我们需要将vue data里面的数据的key全部设置成data对象的属性然后为这些属性设置Set ,Get方法
遍历vue.data

    //将data中的数据绑定为vue的属性,并为其附加get,set方法
    Object.keys(this.data).forEach(function (key) {
      that.observe(that.data,key,that.data[key])
    })
  newVue.prototype={
    observe:function(d,k,v){
      var that = this
      Object.defineProperty(d,k,{
        get:function(){
          return v
        },
        set:function(newValue){
          if (newValue === v) {
            return;
          }
          v=newValue
          that.map.update(k,v)
        }
      })
    },

这个时候data中所有数据的key都注册成vue.data的属性,可以通过get方法获取属性值,并通过修改其属性来触发set函数中的逻辑

然后遍历所有的节点解析指令

nodeMap:function(el){
      var childNodes= el.childNodes;
      var that = this;
      [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/;
        var text = node.textContent;
          //遍历节点获取所有节点属性
        if(typeof node.attributes !='undefined'){
          let attributes=node.attributes;
          //遍历节点属性
          [].slice.call(attributes).forEach(function (attr) {
            console.log(attr.name)
            if(attr.name==='v-model'){
              node.addEventListener('input', function(e) {
                var newValue = e.target.value;
                var val=that.data[attr.value]
                if (val === newValue) {
                  return;
                }
                that.data[attr.value] = newValue;
                val = newValue;
              });
            }
          })
        }
        //如果这个阶段内有{{}},将data内的值与{{}}内的值关联起来
        if(reg.test(text)){
          //方法用于检索字符串中的正则表达式的匹配
          var value= reg.exec(text)[1]
          //将data中的数据更新到页面
          node.textContent = typeof that.data[value]== 'undefined' ? '' : that.data[value];
         //将model对象保存
          that.map.add({value,node})
          console.log(that.map)
        }
      })
    }

最后创建一个容器,收集所有的订阅器,当data中的数据发生改变时遍历订阅器即可

function modelMap() {
    this.list = []
  }
  modelMap.prototype={
    add:function(s){
      this.list.push(s)
    },
    //遍历model对象
    update:function(key,value){
      this.list.forEach(function (s) {
        let val= s.node.textContent
        if(key === s.value&&value !==val){
          s.node.textContent=value
        }
      })
    }
  }

最后将以上组装到vue对象中,即可通过vue对象管理dom和数据

  var el = document.querySelector("#vm")
  function newVue(s) {
    this.data=s.data
    this.map=new modelMap()
    this.nodeMap(s.el)
    var that = this
    //将data中的数据绑定为vue的属性,并为其附加get,set方法
    Object.keys(this.data).forEach(function (key) {
      that.observe(that.data,key,that.data[key])
    })
  }

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <script type="text/javascript">



  </script>
</head>
<body>
<div id="vm">
  <div style="height: 50px;width: 200px;border: dashed 1px gray">
 {{test}}
  </div>
  <input v-model="test">
</div>
</body>
<script type="text/javascript">
  var el = document.querySelector("#vm")
  function newVue(s) {
    this.data=s.data
    this.map=new modelMap()
    this.nodeMap(s.el)
    var that = this
    //将data中的数据绑定为vue的属性,并为其附加get,set方法
    Object.keys(this.data).forEach(function (key) {
      that.observe(that.data,key,that.data[key])
    })
  }
  newVue.prototype={
    observe:function(d,k,v){
      var that = this
      Object.defineProperty(d,k,{
        get:function(){
          return v
        },
        set:function(newValue){
          if (newValue === v) {
            return;
          }
          v=newValue
          that.map.update(k,v)
        }
      })
    },
    nodeMap:function(el){
      var childNodes= el.childNodes;
      var that = this;
      [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/;
        var text = node.textContent;
          //遍历节点获取所有节点属性
        if(typeof node.attributes !='undefined'){
          let attributes=node.attributes;
          //遍历节点属性
          [].slice.call(attributes).forEach(function (attr) {
            console.log(attr.name)
            if(attr.name==='v-model'){
              node.addEventListener('input', function(e) {
                var newValue = e.target.value;
                var val=that.data[attr.value]
                if (val === newValue) {
                  return;
                }
                that.data[attr.value] = newValue;
                val = newValue;
              });
            }
          })
        }
        //如果这个阶段内有{{}},将data内的值与{{}}内的值关联起来
        if(reg.test(text)){
          //方法用于检索字符串中的正则表达式的匹配
          var value= reg.exec(text)[1]
          //将data中的数据更新到页面
          node.textContent = typeof that.data[value]== 'undefined' ? '' : that.data[value];
         //将model对象保存
          that.map.add({value,node})
          console.log(that.map)
        }
      })
    }
  }
  function modelMap() {
    this.list = []
  }
  modelMap.prototype={
    add:function(s){
      this.list.push(s)
    },
    //遍历model对象
    update:function(key,value){
      this.list.forEach(function (s) {
        let val= s.node.textContent
        if(key === s.value&&value !==val){
          s.node.textContent=value
        }
      })
    }

  }
  var vue = new newVue({
    data:{
      test:''
    },
    el:el
  })
  console.log(vue)
//  vue.test=321
/*  function nodeMap(el,vue){
    this.vue=vue
    this.map(el)
  }
  nodeMap.prototype={
    map:function (el) {
      var childNodes = el.childNodes
      var that = this
      Array.prototype.slice.call(childNodes).forEach(function (node) {
        var reg = /\{\{(.*)\}\}/;
        var text = node.textContent;
        if(reg.test(text)){
          //方法用于检索字符串中的正则表达式的匹配
          console.log( reg.exec(text)[1])
        }

      })
    }
  }*//*  function nodeMap(el,vue){
    this.vue=vue
    this.map(el)
  }
  nodeMap.prototype={
    map:function (el) {
      var childNodes = el.childNodes
      var that = this
      Array.prototype.slice.call(childNodes).forEach(function (node) {
        var reg = /\{\{(.*)\}\}/;
        var text = node.textContent;
        if(reg.test(text)){
          //方法用于检索字符串中的正则表达式的匹配
          console.log( reg.exec(text)[1])
        }

      })
    }
  }*/
//  var n = new nodeMap(el)


</script>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_39168678/article/details/83039147