vue双向数据绑定的基本原理

93 阅读1分钟

首先在页面上新建基本的HTML结构

<body>
  <div id="app">
    <span>up主:{{name}}</span>
    <input type="text" v-model="name">
    <span>更多:{{more.like}}</span>
    <input type="text" v-model="more.like">
  </div>
  <!-- 引用同级目录下的伪Vue -->
  <script src="./vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        name: '奥克斯的',
        more: {
          like: 'asd'
        }
      }
    })
    console.log(vm)
  </script>
</body>

接下来便是Vue双向绑定的基本实现 首先创建Vue类,Observer方法通过Object.defineProperty来实现对属性的监听

class Vue {
  constructor(obj_instance) {
    this.$data = obj_instance.data
    Observer(this.$data)
    Complie(obj_instance.el, this)
  }
}
function Observer(data_instance) {
  if (!data_instance || typeof data_instance !== 'object') return;
  const dependency = new Dependency()
  Object.keys(data_instance).forEach(key => {
    let value = data_instance[key]
    Observer(value)
    Object.defineProperty(data_instance, key, {
      enumerable: true,
      configurable: true,
      get() {
        Dependency.temp && dependency.addSub(Dependency.temp)
        return value
      },
      set(newVal) {
        console.log('出发了setter函数');
        value = newVal
        Observer(value)
        dependency.notify()
      }
    })
  })
}

function Complie(element, vm) {
  vm.$el = document.querySelector(element)
  const fragment = document.createDocumentFragment()
  let child;
  while (child = vm.$el.firstChild) {
    fragment.append(child)
  }
  fragment_complie(fragment)
  function fragment_complie(node) {
    const pattern = /\{\{\s*(\S+)\s*\}\}/
    if (node.nodeType === 3) {
      const xxx = node.nodeValue
      const result_regex = pattern.exec(node.nodeValue)
      if (result_regex) {
        const arr = result_regex[1].split('.')
        const value = arr.reduce((total, current) => total[current], vm.$data)
        node.nodeValue = xxx.replace(pattern, value)
        new Watcher(vm, result_regex[1], (newVal) => {
          node.nodeValue = xxx.replace(pattern, newVal)
        })
      }
      return
    }
    if (node.nodeType === 1 && node.nodeName === 'INPUT') {
      const attrs = Array.from(node.attributes)
      attrs.forEach((attr) => {
        if(attr.nodeName === 'v-model') {
          const value = attr.nodeValue.split('.').reduce((total, current) => total[current], vm.$data)
          node.value = value
          new Watcher(vm, attr.nodeValue, (newVal) => {
            node.value = newVal
          })
          node.addEventListener('input', (e) => {
            const attrArr1 = attr.nodeValue.split('.')
            const attrArr2 = attrArr1.slice(0, attrArr1.length - 1)
            const final = attrArr2.reduce((total, current) => total[current], vm.$data)
            final[attrArr1[attrArr1.length-1]] = e.target.value
          })
        }
      })
    }
    node.childNodes.forEach(child => fragment_complie(child))
  }
  vm.$el.append(fragment) // 将文档碎片应用到对应的dom元素上
}
 
// 依赖:收集和通知订阅者
class Dependency {
  constructor() {
    this.subscribers = []
  }
  addSub(sub) {
    this.subscribers.push(sub)
  }
  notify() {
    this.subscribers.forEach(sub => {
      sub.update()
    })
  }
}

// 订阅者
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm
    this.key = key
    this.callback = callback
    Dependency.temp = this
    key.split('.').reduce((total, current) => total[current], vm.$data)
    // Dependency.temp = null
  }
  update() {
    const value = this.key.split('.').reduce((total, current) => total[current], this.vm.$data)
    this.callback(value)
  }
}