简版vue

92 阅读1分钟
function defineReactive(obj, key, val) {
  observe(val);
  let dep = new Dep(key);
  console.log(dep);
  Object.defineProperty(obj, key, {
    get() {
      console.log(`get ${key}:${val}`);

      Dep.target && dep.addWatch(Dep.target);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        observe(newVal);
        console.log(`set ${key}:${newVal}`);
        val = newVal;
        dep.updata();
      }
    }
  });
}
function observe(obj) {
  if (typeof obj !== "object" || obj == null) {
    return;
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}
function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(val) {
        vm.$data[key] = val;
      }
    });
  });
}
class Svue {
  constructor(options) {
    this.$options = options;
    this.$data = options.data;
    observe(this.$data);
    proxy(this);
    new Complie(options.el, this);
  }
}
class Complie {
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el);
    if (this.$el) {
      this.complie(this.$el);
    }
  }
  complie(el) {
    const childNodes = el.childNodes;
    [...childNodes].forEach(node => {
      console.log(node);
      if (node.nodeType == 1) {
        this.compileElement(node);
      } else if (node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)) {
        //插值
        this.compileText(node);
      }
      if (node.childNodes && node.childNodes.length > 0) {
        this.complie(node);
      }
    });
  }

  compileText(node) {
    console.log(99);
    this.upData(node, RegExp.$1, "text");
  }
  compileElement(node) {
    console.log(node.attributes);
    [...node.attributes].forEach(att => {
      console.dir(att);
      if (att.nodeName.startsWith("s-")) {
        let funName = att.nodeName.slice(2);
        this[funName] && this[funName](node, att.nodeValue);
      }
    });
  }

  isDirective(attr) {
    return attr.indexOf("s-") == 0;
  }
  text(node, exp) {
    this.upData(node, exp, "text");
  }

  html(node, exp) {
    this.upData(node, exp, "html");
  }
  upData(node, key, type) {
    console.log(type + "Updater");
    let fn = this[type + "Updater"];
    fn(node, this.$vm[key]);
    new Watch(this.$vm, key, function(val) {
      fn(node, val);
    });
  }
  textUpdater(node, val) {
    node.textContent = val;
  }
  htmlUpdater(node, val) {
    node.innerHTML = val;
  }
}
class Dep {
  constructor() {
    this.deps = [];
  }
  addWatch(watch) {
    this.deps.push(watch);
  }
  updata() {
    this.deps.forEach(watch => {
      watch.updata();
    });
  }
}
class Watch {
  constructor(vm, key, fun) {
    this.fun = fun;
    this.vm = vm;
    this.key = key;
    Dep.target = this;
    this.vm[key];
    Dep.target = null;
  }
  updata() {
    this.fun.call(this.vm, this.vm[this.key]);
  }
}
<div id="app">
  <p style="color:red">{{counter}}</p>
  <p s-html="counter"></p>
</div>
<script src="./svue.js"></script>
<script>
  const app = new Svue({
    el: "#app",
    data: {
      counter: 1,
    }
  });
  setInterval(() => {
    app.counter++;
  }, 1000);
</script>