模拟vue实现数据的双向绑定 v-model

<!DOCTYPE html>
<html lang="zh-cn">

<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="test">
    <h1 class="te1">{{name}}</h1>
    <h1 class="te2">{{name}}</h1>

    <h1 class="te3">{{name}}</h1>
    <h1 class="te4">{{test}}</h1>
    <h1 class="te5">{{name}}</h1>
    <h1 class="te6">{{name}}</h1>
    <h1 class="te7">{{name}}</h1>
    <h1 class="te8">{{test2}}</h1>

    <input v-model="name" />
    <input v-model="test" />
    <textarea v-model="test2" placeholder="" placeholder-class="textarea-placeholder"></textarea>

  </div>
  <script type="module">
    export class SelfVue {
      constructor({ data, el }) {
        console.log(data, 'data')
        this.data = data
        this.el = document.getElementById(el)
        this.init(data, this.el, el)
      }
      init(data, el, id) {

        let str = stringIze(el)
        //给每个元素都加上特殊的
        function addId(str) {
          for (let i = 0; i < str.length;) {
            let tempStr = str.slice(i)
            let inner = /<(.+)>{{(.+)}}<\/.+>/.exec(tempStr)
            let inner2 = /<([^/<>]+)>/.exec(tempStr)
            if (inner2) {
              let id = ' data-v-' + (new Date().getTime() + Math.floor(Math.random() * 1000) + Math.floor(Math.random() * 1000) + Math.floor(Math.random() * 1000))
              str = str.slice(0, i) + str.slice(i, i + inner2.index) + inner2[0].slice(0, inner2[1].split(' ')[0].length + 1) + id + tempStr.slice(inner2.index + inner2[1].split(' ')[0].length + 1)
              i = i + inner2.index + inner2[0].length + 1 + id.length

            } else {
              break
            }
          }
          return str
        }
        str = addId(str)
        for (const key in data) {
          str = changeText(key, str, data[key])
          str = handleModel(key, str, data[key])

          defineDeby(key, str, data[key])
        }
        for (const key2 in data) {
          setChange(key2, str, data[key2])
        }

        function defineDeby(key, str, value, nonee) {
          console.log('define', data, key, data[key], data.test, nonee)
          Object.defineProperty(data, key, {

            set(newVal) {
              console.log(key + 'change', newVal, data)
              value = newVal
              str = stringIze(el)
              str = addId(str)
              for (const key2 in data) {
                str = changeText(key2, str, data[key2])
                str = handleModel(key2, str, data[key2])


                defineDeby(key2, str, data[key2], 111)
              }
              for (const key2 in data) {
                setChange(key2, str, data[key2])
              }
              console.log(str, 'str obj')
            }, get() {
              console.log('read' + key, value)
              return value
            }
          })
        }
        console.log(str, 'changeStr', document.getElementById(id), document)
        //改变v-model变量的值
        function handleModel(key, str, value) {
          let a = ['input', 'textarea']
          if (str.indexOf(`v-model="${key}"`) > -1) {
            for (let i = 0; i < str.length;) {
              let tempStr = str.slice(i)
              if (tempStr.indexOf(key) === -1) break
              let inner = /<([a-zA-Z0-9]+)[^/]*(data-v-[0-9]+)[^/]*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\"[^/]*>/.exec(tempStr)
              let inner2 = /<([a-zA-Z0-9]+).*(data-v-[0-9]+).*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\".*>/.exec(tempStr)

              if ((inner && inner[3] === key && inner[1] === 'textarea')) {
                str = str.slice(0, i) + str.slice(i, i + inner.index) + inner[0].slice(0, inner[0].length - 1) + ` value="${value}">` + tempStr.slice(inner[0].length + inner.index)
                console.log(str.slice(0, i), '1', str.slice(i, i + inner.index), '2', inner[0].slice(0, inner[0].length - 1), '3', tempStr.slice(inner[0].length + inner.index), '4')

                i = i + inner.index + inner[0].length + 1 + ` value="${value}">`.length
              } else if ((inner2 && inner2[3] === key && inner2[1] === 'input')) {
                str = str.slice(0, i) + str.slice(i, i + inner2.index) + inner2[0].slice(0, inner2[0].length - 1) + ` value="${value}">` + tempStr.slice(inner2[0].length + inner2.index)
                console.log(str.slice(0, i), '1', str.slice(i, i + inner2.index), '2', inner2[0].slice(0, inner2[0].length - 1), '3', tempStr.slice(inner2[0].length + inner2.index), '4')

                i = i + inner2.index + inner2[0].length + 1 + ` value="${value}">`.length
              }

              else {
                i++
              }
            }
            document.getElementById(id)?.parentNode.removeChild(document.getElementById(id))
            document.getElementsByTagName('body')[0].appendChild(parseElement(str))
            console.log(str, 'str', document.getElementsByTagName('body'))

          }
          return str
        }
        function setChange(key, str, value) {
          for (let i = 0; i < str.length;) {
            let tempStr = str.slice(i)
            if (tempStr.indexOf(key) === -1) break
            let inner = /<([a-zA-Z0-9]+)[^/]*(data-v-[0-9]+)[^/]*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\"[^/]*>/.exec(tempStr)
            let inner2 = /<([a-zA-Z0-9]+).*(data-v-[0-9]+).*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\".*>/.exec(tempStr)

            if ((inner && inner[3] === key && inner[1] === 'textarea')) {
              console.log(document.querySelector(`${inner2[1]}[${inner2[2]}]`))
              document.querySelector(`${inner[1]}[${inner[2]}]`).value = value
              document.querySelector(`${inner[1]}[${inner[2]}]`).addEventListener('change', (e) => {
                console.log('key', 'change')
                data[key] = e.target.value
              })
              i = i + inner.index + inner[0].length + 1 + ` value="${value}">`.length
            } else if ((inner2 && inner2[3] === key && inner2[1] === 'input')) {
              console.log(document.querySelector(`${inner2[1]}[${inner2[2]}]`))
              document.querySelector(`${inner2[1]}[${inner2[2]}]`).addEventListener('change', (e) => {
                console.log('key', 'change')
                data[key] = e.target.value
              })
              i = i + inner2.index + inner2[0].length + 1 + ` value="${value}">`.length
            }
            else {
              i++
            }
          }
        }
        // 改变{{}}为变量值
        function changeText(key, str, value) {
          if (str.indexOf(key) > -1) {
            for (let i = 0; i < str.length;) {
              let tempStr = str.slice(i)
              if (tempStr.indexOf(key) === -1) break
              let inner = /{{(.+)}}/.exec(tempStr)
              if (inner && inner[1] === key) {
                str = str.slice(0, i) + str.slice(i, i + inner.index) + value + tempStr.slice(inner[0].length + inner.index)
                i = i + inner.index + inner[0].length + 1

              } else {
                i++
              }
            }
          }
          document.getElementById(id)?.parentNode.removeChild(document.getElementById(id))
          document.getElementsByTagName('body')[0].appendChild(parseElement(str))
          return str
        }

        /*将元素节点类型字符串化*/
        function stringIze(obj) {
          var o = document.createElement("div");
          o.appendChild(obj);
          return o.innerHTML;
        }
        /*字符串解析成元素节点类型*/

        function parseElement(str) {
          var o = document.createElement("div");
          o.innerHTML = str;
          return o.childNodes[0];
        }
      }


    }
    let selfVue = new SelfVue({ data: { name: '111', test: '1', test2: 'z' }, el: 'test' })

  </script>
</body>

</html>

这个是一开始使用的方法,但是考虑到一直销毁元素创建元素,可能不是很好,就用了另外的一种方式

上面的这种方式是将dom转成字符串进行遍历处理;下面的方式是直接遍历dom,拷贝一份dom,在数据变化后,使用拷贝的dom来判断某个元素是否是采用data的数据,需要改变了

下面的方法是 一直在同一个dom上面操作,没有销毁创建等操作

<!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>
<div id="test">
  <h1>
    <strong>{{text}}</strong>
    <strong>{{text.length+name}}</strong>

  </h1>
  <p>{{name}}</p>
  <p @click="changeTest"></p>
  <p>{{new Date()}}</p>
  <input type="text" v-model="name">
  <textarea placeholder="" placeholder-class="textarea-placeholder" type="text" v-model="text"></textarea>

</div>

<body>
  <script>
    class SelfVue {
      constructor({ data, el }) {
        this.data = data
        this.el = el
        this.init()
      }
      init() {
        let children = document.getElementById(this.el).children
        //深拷贝出来,保存{{}},v-model之类的
        let cloneDocu = clone(document.getElementById(this.el))
        mapChildren(children, this, cloneDocu.children)
        for (let key in this.data) {
          defineObey(this, key, this.data[key])
        }
        function defineObey(_this, key, value) {
          Object.defineProperty(_this.data, key, {
            set(newValue) {
              console.log(key + 'change', newValue)
              value = newValue
              mapChildren(children, _this, cloneDocu.children)
            },
            get() {
              return value
            }
          })
        }
        function mapChildren(children, _this, cloneDocu) {
          let biao = ['TEXTAREA', 'INPUT']
          for (let i = 0; i < children.length; i++) {
            if (biao.indexOf(children[i].nodeName) > -1 && cloneDocu[i].getAttribute('v-model')) {
              let model = cloneDocu[i].getAttribute('v-model')
              for (let key in _this.data) {
                if (model === key) {
                  children[i].value = _this.data[key]
                  children[i].addEventListener('input', (e) => {
                    if (_this.data[key] !== e.target.value)
                      _this.data[key] = e.target.value
                  })
                }
              }
            }
            //获取非子节点的文本
            var arr = [];
            var content = cloneDocu[i];
            for (var j = 0, len = content.childNodes.length; j < len; j++) {
              if (content.childNodes[j]?.nodeType === 3) {  // 通过nodeType是不是文本节点来判断
                arr.push(content.childNodes[j].nodeValue);
              }
            }
            var str = arr.join("");

            let exec = /{{(.+)}}/.exec(str)
            if (exec) {
              exec[1] = handleStr(exec[1], _this)

              children[i].innerText = eval(exec[1])


            }
            mapChildren(children[i].children, _this, cloneDocu[i].children)

          }

        }
        function handleStr(str, _this) {
          const dataKeys = Object.keys(_this.data)
          for (let j = 0; j < str.length;) {
            const tempStr = str.slice(j)
            let min = []
            for (let k = 0; k < dataKeys.length; k++) {
              if (tempStr.indexOf(dataKeys[k]) > -1) {
                if (tempStr.indexOf(dataKeys[k]) < (min[0] || 999999) && tempStr[tempStr.indexOf(dataKeys[k]) - 1] !== '.') {
                  min[0] = tempStr.indexOf(dataKeys[k])
                  min[1] = dataKeys[k]
                }
              }
            }
            if (min[0] !== undefined && min[1] !== undefined) {
              str = str.slice(0, j) + str.slice(j, j + min[0]) + '_this.data.' + tempStr.slice(min[0])
              j = j + min[0] + min[1].length + '_this.data.'.length
            } else {
              j++
            }
          }
          return str
        }
        function clone(origin) {
          var originNew = origin.cloneNode();
          // originNew.innerText = origin.innerText
          //进行attr属性的复制过来  同理,想在克隆的时候复制其他同样可以加进来
          if (origin.attributes != null) {
            for (var index = 0; index < origin.attributes.length; index++) {
              var attr = origin.attributes[index];
              var name = attr.name;
              var value = attr.value;
              originNew.setAttribute(name, value);
            }
          }

          //克隆子标签
          var originChildNodes = origin.childNodes;
          for (var index = 0; index < originChildNodes.length; index++) {
            var node = originChildNodes[index];
            if (node != null) {
              //递归进行子标签的克隆
              var childNode = clone(node);
              originNew.appendChild(childNode);
            }
          }
          return originNew;
        };

      }
    }
    let a = { data: { name: 'xxx', text: 'shh' }, el: 'test' }
    let vue = new SelfVue(a)
  </script>
</body>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值