美文网首页
vue双向绑定

vue双向绑定

作者: forever远方 | 来源:发表于2020-12-09 21:15 被阅读0次

    Vue-2.+
    采用数据劫持结合发布者-订阅者模式的方式,使用Object.defineProperty()方法对象通过 递归+遍历 的方式,重写属性的setter,getter方法来实现对数据的监控的。在数据变动时发布消息给订阅者,触发相应的监听回调。
    缺点:

    1. 监听数组的方法不能触发Object.defineProperty方法中的set操作(如果要监听的到话,需要重新编写数组的方法:push、pop、shift、unshift、splice、sort、reverse)。
    2. 必须遍历每个对象的每个属性,如果对象嵌套很深的话,需要使用递归调用。

    Vue-3.0
    Proxy
    const obj = new Proxy(data, handler);
    当外界每次对obj进行操作时,就会执行handler对象上的一些方法。handler中常用的对象方法如下:

    1. get(target, propKey, receiver)

    2. set(target, propKey, value, receiver)

    3. has(target, propKey)

    4. construct(target, args):

    5. apply(target, object, args)

    6. 需要实现一个数据监听器 Observer, 能够对所有数据进行监听,如果有数据变动的话,拿到最新的值并通知订阅者Watcher.

    7. 需要实现一个指令解析器Compile,它能够对每个元素的指令进行扫描和解析,根据指令模板替换数据,以及绑定相对应的函数。

    8. 需要实现一个Watcher, 它是链接Observer和Compile的桥梁,它能够订阅并收到每个属性变动的通知,然后会执行指令绑定的相对应
      的回调函数,从而更新视图。

    image.png
    <!DOCTYPE html>
    <html>
    <head>
        <title>MyVue</title>
    </head>
    <body>
    <div id="app">
        <input id="input1" type="text" v-model="inputValue" style="margin-top: 50px"/>
        <button id="btn1" type="button" v-click="add">add</button>
        <span id="span1" v-bind="inputValue" style="margin-left: 30px"></span>
    
        <br/>
    
        <input type="text" v-model="inputValue2" style="margin-top: 50px"/>
        <span v-bind="inputValue2" style="margin-left: 30px"></span>
    
        <br/>
    
        <input type="text" v-model="inputValue3" style="margin-top: 50px"/>
        <span v-bind="inputValue3" style="margin-left: 30px"></span>
        <span style="margin-left: 30px;color:red">{{inputValue3}}</span>
    
    </div>
    </body>
    
    
    <!--<script>-->
    <!--  let data = {-->
    <!--    inputValue: '',-->
    <!--  }-->
    <!--  // Vue无法监听到对象属性的添加和删除-->
    
    <!--  // Object.defineProperty(data, 'inputValue', {-->
    <!--  //   get() {-->
    <!--  //     return data.inputValue-->
    <!--  //   },-->
    <!--  //   set(newVal) {-->
    <!--  //     document.getElementById('span1').innerHTML = newVal-->
    <!--  //   }-->
    <!--  // })-->
    <!--  const handler = {-->
    <!--    get: function(target, key) {-->
    <!--      console.log('handler=get===>', target, key)-->
    <!--      return target[key];-->
    <!--    },-->
    <!--    set: function(target, key, newVal) {-->
    <!--      console.log('handler=set===>', target, key, newVal)-->
    <!--      target[key] = newVal;-->
    <!--      document.getElementById('span1').innerHTML = newVal-->
    <!--    }-->
    <!--  };-->
    <!--  data = new Proxy(data, handler);-->
    
    <!--  document.getElementById('input1').addEventListener('input', (e)=>{-->
    <!--    data.inputValue = e.target.value-->
    <!--  })-->
    <!--  document.getElementById('btn1').addEventListener('click', (e)=>{-->
    <!--    data.inputValue = 'q'-->
    <!--  })-->
    <!--</script>-->
    
    
    <script>
        let data = ['1'];
    
        const handler = {
            set: function(target, key, newVal) {
              const res = Reflect.set(target, key, newVal);
              console.log('handler=set===>', target)
              document.getElementById('span1').innerHTML = JSON.stringify(target)
              return res
            }
        };
        data = new Proxy(data, handler);
    
        document.getElementById('input1').addEventListener('input', (e)=>{
          console.log('input===>', e.target.value)
          data.push(e.target.value)
        })
    </script>
    
    
    <script>
      class MyVue {
        _binding
    
        constructor(options) {
          this._init(options)
        }
    
        _init(options) {
          this.$options = options  // options 为上面使用时传入的结构体,包括el,data,methods
          this.$el = document.querySelector(options.el)  // el是 #app, $el是id为app的Element元素
          this.$data = options.data
          this.$methods = options.methods
          this.$watch = options.watch || {}
          console.log('myVue=_init==>', this)
    
          // 初始化_binding,保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
          this._binding = {}
    
          this._observer()
          // this._proxyObserver()
          console.log('myVue=_obsever==>', this)
    
          // this._compile()
          // console.log('myVue=_compile==>', this)
    
          const dom = this._nodeToFragment(this.$el)
          this.$el.appendChild(dom)
        }
    
        // 数据劫持:更新数据
        _observer() {
          const vm = this
          const obj = vm.$data
          const mWatch = vm.$watch
          Object.keys(obj).map(key => {
            let value = obj[key]
            // if (typeof value == 'object') {  //如果值还是对象,则继续遍历
            //   vm._observer(value)
            // }
            // 对对象已有的属性添加数据描述
            Object.defineProperty(vm.$data, key, {
              configurable: true, // 能否使用delete、能否需改属性特性、或能否修改访问器属性,false为不可重新定义,默认值为true
              enumerable: true, // 对象属性是否可通过for-in循环,false为不可循环,默认值为true
              get: () => {  // 重写get
                console.log('myVue=get===>', key, value)
                return value
              },
              set: (newVal) => {   // 重写set
                console.log('myVue=set===>', key, newVal, value)
    
                if (value !== newVal) {
                  if (mWatch[key]) {
                    mWatch[key](newVal, value)
                  }
                  //利用闭包的特性,修改value,get取值时也会变化
                  //不能使用obj[key]=newVal
                  //因为在set中继续调用set赋值,引起递归调用
                  value = newVal
                  // obj[key] = newVal
    
                  // 当value更新时,出发_binding中绑定的watcher
                  vm._binding[key].map(item => {
                    item.update() // 顶用watcher中的update方法更新dom
                  })
                }
              }
            })
          })
        }
    
        _proxyObserver() {
          const vm = this
          const mWatch = vm.$watch
    
          const handler = {
            get(target, key) {
              console.log('myVue=get===>', target, key, target[key])
              return Reflect.get(target, key)
            },
            set(target, key, newVal) {
              console.log('myVue=set===>', target, key, newVal)
              if (mWatch[key]) {
                mWatch[key](newVal, target[key])
              }
              // 当value更新时,出发_binding中绑定的watcher
              const res = Reflect.set(target, key, newVal)
              vm._binding[key].map(item => {
                item.update() // 顶用watcher中的update方法更新dom
              })
              return res
            }
          }
          // 把代理器返回的对象代理到this.$data,即this.$data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法
          vm.$data = new Proxy(vm.$data, handler)
        }
    
        // 将view与model进行绑定,解析指令(v-bind,v-model,v-click)等
        _compile() {
          const vm = this
          const el = vm.$el
    
          let nodes = Array.prototype.slice.call(el.children) // 将伪数组转成数组
          console.log('_compile====>', el.children)
          console.log('_compile1====>', nodes)
    
          nodes.map(node => {
            console.log('node=====>', node)
    
            if (node.children && node.children.length > 0) {  // 对所有元素进行遍历,并进行处理
              vm._compile(node)
            }
    
            // 解析属性
            if (node.hasAttribute('v-click')) {
              // v-click绑定的attrVal为methods里的方法名
              const attrVal = node.getAttribute('v-click')
              node.onclick = vm.$methods[attrVal].bind(vm.$data)  // // bind是使data的作用域与method函数的作用域保持一致
            }
    
            if (node.hasAttribute('v-model') && node.tagName === 'INPUT') {
              // v-model绑定的为 data里的key
              const attrVal = node.getAttribute('v-model')
              if (!vm._binding[attrVal]) vm._binding[attrVal] = []
              vm._binding[attrVal].push(new Watcher(node, 'value', vm.$data, attrVal))
    
              node.addEventListener('input', () => {
                vm.$data[attrVal] = node.value
              })
            }
    
            if (node.hasAttribute('v-bind')) {
              const attrVal = node.getAttribute('v-bind')
              if (!vm._binding[attrVal]) vm._binding[attrVal] = []
              vm._binding[attrVal].push(new Watcher(node, 'innerHTML', vm.$data, attrVal))
            }
          })
        }
    
        _nodeToFragment(node, flag){
          flag = flag || document.createDocumentFragment()
          let child
          while (child = node.firstChild) {
            this._compile2(child)
    
            // appendChild 方法有个隐蔽的地方,就是调用以后 child 会从原来 DOM 中移除
            // 所以,第二次循环时,node.firstChild 已经不再是之前的第一个子元素了
            flag.appendChild(child)
            if (child.firstChild) {
              this._nodeToFragment(child, flag)
            }
          }
          return flag
        }
    
        _compile2(node) {
          console.log('node=====>', node, node.nodeType, node.nodeValue)
    
          const vm = this
    
          if (node.nodeType === 1) {
            const attrs = node.attributes
    
            Object.keys(attrs).map(i => {
              const attr = attrs[i]
              // console.log('node====>', attr)
              if (!attr) return
    
              if (attr.nodeName === 'v-model' ) {
                const key = attr.nodeValue  // 获取 v-model 绑定的属性名
                node.value = vm.$data[key]
    
                node.addEventListener('input', (e) => {
                  vm.$data[key] = e.target.value
                })
    
                if (!vm._binding[key]) vm._binding[key] = []
                vm._binding[key].push(new Watcher(node, 'value', vm.$data, key))
    
                node.removeAttribute('v-model')
              }
    
              if (attr.nodeName === 'v-bind') {
                const key = attr.nodeValue  // 获取 v-model 绑定的属性名
                node.innerHTML = vm.$data[key]
    
                console.log('v-bind===>', node, node.innerHTML)
    
                if (!vm._binding[key]) vm._binding[key] = []
                vm._binding[key].push(new Watcher(node, 'innerHTML', vm.$data, key))
    
                node.removeAttribute('v-bind')
              }
            })
          } else if (node.nodeType === 3) {
            const reg = /\{\{(.*)\}\}/
            if (reg.test(node.nodeValue)) {
              const key = RegExp.$1.trim() // 获取匹配到的字符串
    
              if (!vm._binding[key]) vm._binding[key] = []
              vm._binding[key].push(new Watcher(node, 'nodeValue', vm.$data, key))
            } else {
              console.log('qqqqqqqqq====>', node)
            }
          }
        }
      }
    
      // 订阅者,用于监听更新dom
      class Watcher {
        el
        attr
        data
        key
    
        constructor(el, attr, data, key) {
          this.el = el
          this.attr = attr
          this.data = data
          this.key = key
    
          this.update()
        }
    
        update() {
          this.el[this.attr] = this.data[this.key]
        }
      }
    
      const myVue = new MyVue({
        el: '#app',
        data: {
          inputValue: '',
          inputValue2: '',
          inputValue3:''
        },
        watch: {
          inputValue(newV, oldV) {
            console.log('watch=inputValue===>', newV, oldV)
          }
        },
        methods: {
          add() {
            console.log('myVue=add===>')
            this.inputValue += '1'
          },
          add2() {
            console.log('myVue=add2===>')
            this.inputValue2 += '2'
          }
        }
      })
    </script>
    
    
    </html>
    

    相关文章

      网友评论

          本文标题:vue双向绑定

          本文链接:https://www.haomeiwen.com/subject/fujkgktx.html