MVVM 数据响应式

index.html

<!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>
<body>
  <div id="app">
    <p>
      <input type="text" v-model="name" placeholder="请输入名称" />
      <input type="text" v-model="age" placeholder="请输入年龄" />
      <input type="text" v-model="email" placeholder="请输入邮箱" />
    </p>
    <p>
      <p>
        <input type="text" v-model="tel" placeholder="请输入电话" />
      </p>
    </p>

    <p>
      <p>姓名: <span>{{ name }}</span></p>
      <p>年龄: <span>{{ age }}</span></p>
      <p>邮箱: <span>{{ email }}</span></p>
      <p>电话: <span>{{ tel }}</span></p>
    </p>
    <button id="btn">改名字</button>
  </div>
  <script src="./MVVM.js"></script>
  <script>
    var app = new MVVM('#app', {
      name: 'Lance',
      age: 26,
      email: '',
      tel: ''
    });
    var btn = document.getElementById('btn');
    btn.addEventListener('click', () => {
      // app.name = 'GC';
      app.setData('name', 'GC');
    });
  </script>
</body>
</html>

MVVM.js

const reg_valid = /\\{\\{(.+?)\\}\\}/;
class MVVM {
  constructor(el, data) {
    this.$el = document.querySelector(el);
    this._data = data;
    this.domPool = {};
    this.init();
  }

  init() {
    this.initData();
    // this.initProxyData();
    this.initDOM();
  }

  initProxyData() {
    let self = this;
    this.data = this._data;
    this.data = new Proxy(this.data, {
      get(target, key) {
        console.log(`get ${key}`);
        return Reflect.get(target, key);
      },
      set(target, key, newValue) {
        if (newValue !== target[key]) {
          console.log(`set ${key}: ${newValue}`);
          self.domPool[key].forEach(node => {
            if (node.tagName.toLowerCase() === 'input') {
              node.value = newValue;
            } else {
              node.innerText = newValue;
            }
          });
          return Reflect.set(target, key, newValue);
        }
      }
    });
  }

  initData() {
    let self = this;
    this.data = {}; // 不修改传入对象
    for (let key in this._data) {
      Object.defineProperty(this.data, key, {
        get() {
          console.log(`get ${key}`);
          return self._data[key];
        },
        set(newValue) {
          if (newValue !== self._data[key]) {
            console.log(`set ${key}: ${newValue}`);
            self._data[key] = newValue;
            self.domPool[key].forEach(node => {
              if (node.tagName.toLowerCase() === 'input') {
                node.value = newValue;
              } else {
                node.innerText = newValue;
              }
            });
          }
        }
      });
    }
  }

  initDOM() {
    this.bindInput();
    this.bindDOM(this.$el);
  }

  bindInput() {
    let inputs = this.$el.querySelectorAll('input');
    inputs.forEach(input => {
      let vModel = input.getAttribute('v-model');
      if (vModel) {
        input.value = this.data[vModel];
        input.addEventListener('input', this.handleInput.bind(this, input, vModel), false);
        if (!this.domPool[vModel]) this.domPool[vModel] = [];
        this.domPool[vModel].push(input);
      }
    });
  }

  handleInput(input, key) {
    let val = input.value;
    this.data[key] = val;
  }

  bindDOM(el) {
    el.childNodes.forEach(node => {
      if (node.nodeType === 3) {
        let val = node.nodeValue;
        if (val.trim()) {
          let isValid = reg_valid.test(val);
          if (isValid) {
            let key = val.match(reg_valid)[1].trim();

            if (!this.domPool[key]) this.domPool[key] = [];
            this.domPool[key].push(node.parentNode);

            node.parentNode.innerText = this.data[key] || undefined;
          }
        }
      }
      node.childNodes && this.bindDOM(node);
    });
  }

  setData(key, value) {
    this.data[key] = value;
  }
}