JS 简易实现 Vue 的 Watcher,Observer,Dep,Compiler

整体思路:Observer 作为发布者,Watcher 作为订阅者,Compiler 将二者连接起来

1、实现 Observer

function Obsever(data) {
  Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
}

function observe(data) {
  if (typeof data !== 'object' || data === null) {
    return
  }
  return new Obsever(data)
}

function defineReactive(obj, key, val) {
  observe(val)
  var dep = new Dep()
  Object.defineProperty(obj, key, {
    configurable: false,
    enumerable: true,
    get: function () {
      Dep.target && dep.add(Dep.target)
      return val
    },
    set: function (newValue) {
      if (newValue !== val) {
        val = newValue
        dep.notify(newValue)
      }
    }
  })
}

function Dep() {
  this.subs = []
}

Dep.target = null

Dep.prototype = {
  constructor: Dep,
  add: function (sub) {
    this.subs.push(sub)
  },
  notify: function (value) {
    this.subs.forEach(sub => sub.update(value))
  }
}

2、实现 Watcher

function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.val = this.get()
}

Watcher.prototype = {
  constructor: Watcher,
  get: function () {
    Dep.target = this;
    var val = this.vm[this.exp];
    Dep.target = null;
    return val
  },
  update: function (newValue) {
    this.cb(newValue)
  }
}

3、实现 Compiler

function Compiler(vm, el) {
  var dom = document.querySelector(el);
  this.vm = vm;
  this.$el = dom;
  this.init();
}

Compiler.prototype = {
  constructor: Compiler,
  init: function () {
    var childNodes = this.$el.childNodes;
    [].slice.call(childNodes).forEach(child => this.compile(child));
  },
  compile: function (child) {
    if (child.nodeType === 3) {
      var val = child.textContent;
      var reg = /\{\{(.*)\}\}/;
      var result = val.match(reg);
      if (!result) return;
      this.bind(val.match(reg)[1], "text", child);
    } else {
      var attrs = child.attributes;
      [].slice.call(attrs).forEach(attr => {
        var directiveReg = /v-/,
          eventDirectiveReg = /v-on/,
          attrName = attr.name,
          attrValue = attr.value;
        if (attrName.match(directiveReg)) {
          if (attrName.match(eventDirectiveReg)) {
            this.event(attrName.slice(5), attrValue, child);
          } else {
            this.bind(attrValue, attrName.slice(2), child);
          }
          child.removeAttribute(attrName);
        }
      });
      [].slice.call(child.childNodes).forEach(child => this.compile(child));
    }
  },
  bind: function (exp, type, node) {
    var me = this;
    if (type === "model") {
      node.addEventListener("input", function (e) {
        me.vm[exp] = e.target.value;
      });
    }
    var updateFn = updateUtil[type];
    console.log('me.vm[exp]', me.vm[exp])
    updateFn && updateFn(node, me.vm[exp]);
    new Watcher(me.vm, exp, function () {
      updateFn && updateFn(node, me.vm[exp]);
    });
  },
  event: function (eventType, exp, node) {
    var handler = this.vm.options.methods[exp].bind(this.vm)
    node.addEventListener(eventType, handler, false);
  }
};

var updateUtil = {
  text: function (node, newValue) {
    node.textContent = newValue;
  },
  model: function (node, newValue) {
    node.value = newValue;
  }
};

4、实现 mvvm

function MVVM(options) {
  this.options = options
  this.proxy(options.data)
  this.init(options)
}
MVVM.prototype = {
  constructor: MVVM,
  init: function (opt) {
    observe(opt.data)
    this.initComputed()
    this.initWatch()
    this.$compiler = new Compiler(this, opt.el)
  },
  initComputed() {
    var computed = this.options.computed
    Object.keys(computed).forEach(key => {
      var valueFn = computed[key].bind(this)
      Object.defineProperty(this, key, {
        configurable: false,
        enumerable: true,
        get: function () {
          return valueFn()
        },
        set: function (newValue) {
          return newValue
        }
      })
    })
  },
  initWatch() {
    var watch = this.options.watch
    Object.keys(watch).forEach(key => {
      new Watcher(this, key, watch[key].bind(this))
    })
  },
  proxy: function (data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        configurable: false,
        enumerable: true,
        get: function () {
          return data[key]
        },
        set: function (newValue) {
          return data[key] = newValue
        }
      })
    })
  }
}

5、测试





  
  
  
  MVVM 框架测试
  
  
  
  



  
{{message}} {{messages}}

参考

  • 本文源码
  • 剖析vue实现原理,自己动手实现mvvm

你可能感兴趣的:(JS 简易实现 Vue 的 Watcher,Observer,Dep,Compiler)