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>