vue-mvvm 解析

87 阅读1分钟

背景

以vue为例 使用数据劫持defineProperty(老版本),新版本使用proxy

实现

  • Object.defineProperty
  • 编译模板compile
  • 发布订阅模式
  • 视图与数据链接

代码部分

使用function实现

  • 主体骨架
function Vue(options = {}) {
  this.$options = options;
  let data = (this._data = this.$options.data);
  observe(data);
  for (let key in data) {
    Object.defineProperty(this, key, {
      enumerable: true,
      get() {
        return this._data[key];
      },
      set(newValue) {
        this._data[key] = newValue;
      },
    });
  }
  new Compile(options.el, this);
}
  • 实现 Observe
function Observe(data) {
  let dep = new Dep();
  for (let key in data) {
    let value = data[key];
    observe(value);
    Object.defineProperty(data, key, {
      enumerable: true,
      get() {
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set(newValue) {
        if (newValue === value) {
          return;
        }
        value = newValue;
        observe(newValue);
        dep.notify();
      },
    });
  }
}
  • 实现发布订阅
function Dep() {
  this.subs = [];
}
Dep.prototype.addSub = function (sub) {
  this.subs.push(sub);
};
Dep.prototype.removeSub = function (sub) {
  this.subs.pop(sub);
};
Dep.prototype.notify = function (sub) {
  this.subs.forEach(function (sub) {
    sub.update();
  });
};
// 通过类创建的实例都有原型上的方法
function Watcher(vm, exp, fn) {
  this.fn = fn;
  this.vm = vm;
  this.exp = exp;
  Dep.target = this;
  let val = vm,
    arr = exp.split('.');
  arr.forEach((k) => {
    val = val[k];
  });
  Dep.target = null;
}
Watcher.prototype.update = function () {
  let val = this.vm,
    arr = this.exp.split('.');
  arr.forEach(function (k) {
    val = val[k];
  });
  this.fn(val);
};
  • 实现compile
function Compile(el, vm) {
  vm.$el = document.getElementById(el);
  let fragment = document.createDocumentFragment();
  while ((child = vm.$el.firstChild)) {
    fragment.appendChild(child);
  }
  replace(fragment);
  function replace(fragment) {
    Array.from(fragment.childNodes).forEach(function (node) {
      let text = node.textContent;
      let reg = /\{\{(.*)\}\}/;
      if (node.nodeType === 3 && reg.test(text)) {
        let arr = RegExp.$1.split('.');
        let val = vm;
        arr.forEach((k) => {
          val = val[k];
        });
        new Watcher(vm, RegExp.$1, function (newValue) {
          node.textContent = text.replace(reg, newValue);
        });
        node.textContent = text.replace(reg, val);
      }
      if (node.childNodes) {
        replace(node);
      }
    });
  }
  vm.$el.appendChild(fragment);
}
  • 最后
let vue = new Vue({ el: 'app', data: { a: { a: 12 } } });
 <div id="app">
      <p>a:{{a.a}}</p>
 </div>

总结

可以使用class实现更方便,发布订阅模式也可以直接继承Extends,当中代码需要优化得比较多,主题思路大概就是这样子。