阅读 105

迷你数据绑定(defineProperty / Proxy)

使用 defineProperty / Proxy 两种方法实现的数据绑定。

MVVMDefineProperty

<body>
  <div id="app">
    <p>
      My name is {{firstName + ' ' + lastName}}, I am {{age}} years old.
    </p>
  </div>

  <script>
    const target = document.getElementById('app');

    window.appData = {
      firstName: 'Ring',
      lastName: 'Chenng',
      age: 25
    }

    new MVVMDefineProperty(target, appData)

    // My name is Ring Chenng, I am 25 years old.

    // appData.firstName = 'Ruilin'
    // appData.age = 26

    // My name is Ruilin Chenng, I am 26 years old.
  </script>
</body>
复制代码
class MVVMDefineProperty {
  constructor(el, data) {
    this.el = el;
    this.data = data;
    this.changes = [];
    this.init();
    this.parse(this.el);
    this.applyChanges();
  }

  init() {
    Object.keys(this.data).forEach(key => {
      let value = this.data[key];
      const self = this;

      Object.defineProperty(this.data, key, {
        get() {
          return value;
        },
        set(newValue) {
          value = newValue;
          self.applyChanges();
        }
      });
    });
  }

  parse(el) {
    Array.from(el.childNodes).forEach(child => {
      if (!(child instanceof Text)) {
        this.parse(child);
      } else {
        this.changes.push(((originExp) => () => {
          this.insertExpForTextNode(child, originExp);
        })(child.nodeValue));
      }
    });
  }

  insertExpForTextNode(node, originExp) {
    const newValue = originExp.replace(/{{[\s\S]+?}}/g, exp => {
      exp = exp.replace(/[{}]/g, '');
      return this.execute(exp);
    });

    if (newValue !== node.nodeValue) {
      node.nodeValue = newValue;
    }
  }

  execute(exp) {
    const func = new Function(
      ...Object.keys(this.data),
      `return ${exp}`,
    )

    return func(...Object.values(this.data));
  }

  applyChanges() {
    setTimeout(() => {
      this.changes.forEach(func => func());
    });
  }
}
复制代码

MVVMProxy

<body>
  <div id="app">
    <p>
      My name is {{firstName + ' ' + lastName}}, I am {{age}} years old.
    </p>
  </div>

  <script>
    const target = document.getElementById('app');

    const appData = {
      firstName: 'Ring',
      lastName: 'Chenng',
      age: 25
    }

    window.app = new MVVMProxy(target, appData)

    // My name is Ring Chenng, I am 25 years old.

    // app.data.firstName = 'Ruilin'
    // app.data.appData.age = 26

    // My name is Ruilin Chenng, I am 26 years old.
  </script>
</body>
复制代码
class MVVMProxy {
  constructor(el, data) {
    this.el = el;
    this.data = data;
    this.changes = [];
    this.init();
    this.parse(this.el);
    this.applyChanges();
  }

  init() {
    const self = this;
    this.data = new Proxy(this.data, {
      get(obj, prop) {
        return obj[prop];
      },
      set(obj, prop, value) {
        obj[prop] = value;
        self.applyChanges()
      }
    })
  }

  parse(el) {
    Array.from(el.childNodes).forEach(child => {
      if (!(child instanceof Text)) {
        this.parse(child);
      } else {
        this.changes.push(((originExp) => () => {
          this.insertExpForTextNode(child, originExp);
        })(child.nodeValue));
      }
    });
  }

  insertExpForTextNode(node, originExp) {
    const newValue = originExp.replace(/{{[\s\S]+?}}/g, exp => {
      exp = exp.replace(/[{}]/g, '');
      return this.execute(exp);
    });

    if (newValue !== node.nodeValue) {
      node.nodeValue = newValue;
    }
  }

  execute(exp) {
    const func = new Function(
      ...Object.keys(this.data),
      `return ${exp}`,
    )

    return func(...Object.values(this.data));
  }

  applyChanges() {
    setTimeout(() => {
      this.changes.forEach(func => func());
    });
  }
}
复制代码