利用 Proxy API 实现一个简易 MVVM

vue 3 使用了 proxy api,有些手痒,就弄一个简单的结构玩玩吧。Proxy API 见 MDN Proxy

效果图

思路

依赖收集:Mvvm 初始化时劫持数据,并设置观察者 dep。模仿 vue 结构是在 get 时往观察者 dep 推入被观察者 watcher,然后 set 时让观察者通知所有被观察者开始更新。

数据响应:这里只是简单在 compiler 里面去扫描了一遍所有带着 v-text 和 v-model 钩子的标签做了处理:定义被观察者 watcher,被观察者的触发函数写上节点 DOM 的更新。

当然我们知道 vue 的数据响应过程比这个复杂多了,有着虚拟 DOM 和复杂的 diff 算法。

代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div  v-text="text1"></div>
    <input type="text"  v-model="text1">

    <div  v-text="text2"></div>
    <input type="text" v-model="text2">
  </div>

  <script>
    class Watcher {
      constructor(cb) {
        this.cb = cb;
      }
      run() {
        this.cb();
      }
    }
    class Dep {
      constructor() {
        this.subs = [];
        this.target = null;
      }
      notify() {
        this.subs.forEach(item => {
          item.run();
        })
      }
    }
    class Mvvm {
      constructor(data) {
        let that = this;
        this.dep = new Dep();
        this.data = new Proxy(data, {
          get(obj, key, prox) {
            that.dep.target && that.dep.subs.push(that.dep.target);
            return obj.data[key];
          },
          set(obj, key, value, prox) {
            obj.data[key] = value;
            that.dep.notify();
            return true;
          }
        });
        this.compiler();
      }
      compiler() {
        let that = this;
        let app = document.getElementById('app');
        let bindTextNodes = app.querySelectorAll('[v-text]');
        let bindInputNodes = app.querySelectorAll('[v-model]');

        bindTextNodes.forEach(bindTextNode => {
          let textModel = bindTextNode.getAttribute('v-text');
          let watcher = new Watcher(function() {
            bindTextNode.innerText = that.data[textModel];
          });
          that.dep.target = watcher;
          bindTextNode.innerText = that.data[textModel];
          this.dep.target = null;
        })

        bindInputNodes.forEach(bindInputNode => {
          let inputModel = bindInputNode.getAttribute('v-model');
          let watcher = new Watcher(function() {
            bindInputNode.value = that.data[inputModel];
          });
          this.dep.target = watcher;
          bindInputNode.value = that.data[inputModel];

          bindInputNode.addEventListener('input', function(evt) {
            that.data[inputModel] = evt.target.value;
          })
          that.dep.target = null; 
        })
      }
    }

    new Mvvm({
      data: {
        text1: '123',
        text2: '789'
      }
    })
  </script>
</body>
</html>

为什么时 Proxy 而不是 Object.defineProperty ?

我们知道 Vue2.0 的反应系统是使用 Object.defineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。

而为什么使用 Proxy 替代 Object.defineProperty?Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性。节省内存,速度加倍。

为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章