vue中v-module双向数据绑定理解:我们可以简单分为四个过程

  实现一个监听器Observer:对数据对象进行遍历,包括子属性对象的属性,利用Object.definePropery()对属性都加上setter和getter。这样的话,给这个对象的每个值赋值,就回触发setter,那么就能监听到数据变化。

 

  实现一个解析器Compile:解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

 

  实现一个订阅者Watcher:Watcher订阅者是Observer和Compile之间的通信桥梁,主要的任务是订阅Observer中的属性值变化的消息,当收到属性变化的消息时,触发解析器Compile中对应的更新函数。

 

  实现一个订阅器Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理。 

    

js代码:

class Dep{
  constructor() {
  this.listenFunc = []
  }
  addFunc(obj) {
    this.listenFunc.push(obj);
  }
  changeWatch() {
    this.listenFunc.forEach(val => {
      val.sendVal()
    })
  }
}
 
Dep.target = null;
const dep = new Dep()
 
class Watcher{
  constructor(data, key, cbk) {
  // 每一次实例watcher的时候,均会把当前实例赋值给Dep的target静态属性
  Dep.target = this;
  this.data = data;
  this.key = key;
  this.cbk = cbk;
  // 每一次的实例都会调用该函数
  this.init()
  }
  // 获取对应key的值
  init() {
    // 获取对应key的值
    this.value = utils.getValue(this.data, this.key);
    Dep.target = null;
    return this.value;
  }
  sendVal() {
    let newVal = this.init()
    this.cbk(newVal)
  }
}
 
class Observer{
  constructor(data) {
    if (!data || typeof data !== 'object') {
      return;
    }
    this.data = data;
    this.init()
  }
  init() {
    Object.keys(this.data).forEach(val => {
      this.observer(this.data, val, this.data[val])
    })
  }
  observer(obj, key, value) {
    // 通过递归实现每个属性的数据劫持
    new Observer(obj[key])
    Object.defineProperty(obj, key, {
      // 添加劫持之后的属性获取方法
      get() {
        if (Dep.target) {
          // 给dep实例属性listenFunc添加一个watcher实例
          dep.addFunc(Dep.target)
        }
        return value
      },
      // 添加劫持之后的属性设置方法
      set(newValue) {
        if (value === newValue) {
          return;
        }
        value = newValue;
        // 触发每一个listenFunc里面的watcher实例
        dep.changeWatch();
        // 为了兼容新值为一个对象的时候,该对象的属性也得添加劫持
        new Observer(value);
      }
    })
  }
}
 
const utils = {
  setValue(node, data, key) {
    node.value = this.getValue(data, key)
  },
  getValue(data, key) {
    if (key.indexOf('.') > -1) {
      let arr = key.split('.');
      for(let i = 0; i < arr.length; i++) {
        data = data[arr[i]]
      }
      return data
    } else {
      return data[key]
    }
  },
  getContent(node, key, data) {
    node.textContent = this.getValue(data, key)
  },
  // 2.在input事件发生之后,改变对应的属性值
  changeKeyVal(data, key, newVal) {
    if (key.indexOf('.') > -1) {
      let arr = key.split('.');
      for(let i = 0; i < arr.length - 1; i++) {
        data = data[arr[i]]
      }
      data[arr[arr.length - 1]] = newVal
    } else {
      data[key] = newVal
    }
  }
}
 

// 实现双向数据绑定
class Mvvm{
      constructor({el, data}) {
            this.el = el;
            this.data = data;
            // 初始化执行数据绑定实例对象的过程(以及数据劫持)
            this.init();
            // 替换文本中的属性为真实的数据
            this.initDom();
      }
      init() {
            Object.keys(this.data).forEach(val => {
                  this.observer(this, val, this.data[val])
            })
            // 给当前数据集合的每一个属性添加劫持
            new Observer(this.data)
      }
      observer(obj, key, value) {
            Object.defineProperty(obj, key, {
                  get() {
                        return value
                  },
                  set(newValue) {
                        value = newValue
                  }
            })
      }
      initDom() {
            this.$el = document.getElementById(this.el);
            // 文本碎片--> 避免因为操作DOM而导致浏览器的多次重绘(操作完成之后可把整个碎片添加进去,浏览器课一并识别渲染)
            let newFargment = this.createFragment();
            // 根据nodeType来替换对应的属性值
            this.compiler(newFargment);
            this.$el.appendChild(newFargment);
      }
      createFragment() {
            let newFragment = document.createDocumentFragment();
            let firstChild;
            while(firstChild = this.$el.firstChild) {
                  newFragment.appendChild(firstChild);
            }
            return newFragment;
      }
      compiler(node) {
            if (node.nodeType === 1) {
                  let attributes = node.attributes;
                  Array.from(attributes).forEach(val => {
                        if (val.nodeName === 'v-model') {
                              // 1.捕捉input输入框的修改事件
                              node.addEventListener('input', (e) => {
                                    utils.changeKeyVal(this.data, val.nodeValue, e.target.value)
                              })
                              utils.setValue(node, this.data, val.nodeValue)
                        }
                  })
            } else if (node.nodeType === 3) {
                  let contentVal = node.textContent.indexOf("{{") > -1 && node.textContent.split('{{')[1].split('}}')[0];
                  contentVal && utils.getContent(node, contentVal, this.data);
                  // 添加属性监听
                  contentVal && new Watcher(this.data, contentVal, (newVal) => {
                        node.textContent = newVal
                  })
            }
            // 通过递归的形式保证每一级的文本都可获取到并替换
            if (node.childNodes && node.childNodes.length > 0) {
                  node.childNodes.forEach(val => {
                        this.compiler(val)
                  })
            }
      }
}