学习原理的总结
TODO: 下一篇把vue中的虚拟dom算法手写学习下
<div id="app">
<input type="text" v-model="name" />
<div style="margin: 20px 0 20px 0" v-html="name"></div>
<button v-html="name" @click="changeit"></button>
<p>{{name}}</p>
</div>
<script>
class Dep {
constructor() {
this._listeners = [];
}
add(watcher) {
this._listeners.push(watcher);
}
notify() {
this._listeners.forEach(watcher=> watcher.update());
}
}
class Watcher {
constructor(vm,key,updateFunction) {
this.vm = vm
this.key = key
this.updateFn = updateFunction
Dep.target = this
this.vm[this.key]
Dep.target = null
}
update(val){
this.updateFn.call(this.vm,this.vm[this.key],val)
}
}
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data();
this.observerRoot();
this.observerData(this.$data);
this.createFragment();
this.compile();
}
compile() {
this._compileElement(this.$fragment);
this.$el.appendChild(this.$fragment);
}
_compileElement(ele) {
Array.from(ele.childNodes).forEach((node) => {
if (node.childNodes) this._compileElement(node);
if (node.nodeType === 1) {
this._compileNode(node);
} else if (this.isInter(node)) {
this.compileText(node);
}
});
}
compileText(node) {
this.update(node, RegExp.$1, "text");
}
isInter(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
text(node, exp) {
this.update(node, exp, "text");
}
textUpdater(node, val) {
node.textContent = val;
}
html(node, exp) {
this.update(node, exp, "html");
}
htmlUpdater(node, val) {
node.innerHTML = val;
}
model(node, exp) {
this.update(node, exp, "model");
}
modelUpdater(node, val, exp) {
node.oninput = (e) => {
this.$data[exp] = e.target.value || "";
};
node.value = val;
}
update(node, exp, dir) {
const fn = this[dir + "Updater"];
fn && fn.call(this, node, this.$data[exp], exp);
new Watcher(this,exp,function(val){
fn&& fn.call(this,node,val,exp)
});
}
_compileNode(node) {
let res = this._checkHasBind(node);
this._resolveBind(node, res);
}
_checkHasBind(node) {
let attributes = node.attributes;
let dir_reg = /^v\-\w*$/;
let event_reg = /^\@\w/;
let result = {
directives: [],
events: [],
};
if (attributes)
Array.from(attributes).forEach((item) => {
if (dir_reg.test(item.name))
result.directives.push({ name: item.name, value: item.value });
if (event_reg.test(item.name))
result.events.push({ name: item.name, value: item.value });
});
return result;
}
_resolveBind(node, res) {
let _this = this;
let data = this.$data;
let { directives, events } = res;
events.length &&
events.forEach((item) => {
let method_name = item.value;
let target_event = item.name.slice(1, item.name.length);
node.addEventListener(target_event, () => {
this.$options.methods[method_name].call(this);
});
});
directives.length &&
directives.forEach((item) => {
const dir = item.name.substring(2);
let update = () => {
this[dir] && this[dir](node, item.value);
};
update();
});
}
createFragment() {
this.$el = document.querySelector(this.$options.el);
this.$fragment = document.createDocumentFragment();
while (this.$el.firstChild) {
this.$fragment.appendChild(this.$el.firstChild);
}
}
observerRoot() {
Object.keys(this.$data).forEach((item) => {
Object.defineProperty(this, item, {
enumerable: true,
configurable: false,
get() {
return this.$data[item];
},
set(newVal) {
this.$data[item] = newVal;
},
});
});
}
observerData(obj) {
if (!obj || typeof obj !== "object") return;
Object.keys(obj).forEach((item) => {
let val = obj[item];
if (typeof val === "object") {
this.observerData(val);
} else {
let dep = new Dep();
Object.defineProperty(obj, item, {
enumerable: true,
configurable: false,
get() {
Dep.target && dep.add(Dep.target);
return val;
},
set(newVal) {
val = newVal;
dep.notify();
},
});
}
});
}
}
</script>
<script>
let app = new Vue({
el: "#app",
data() {
return {
name: "xiaoming",
};
},
methods: {
changeit() {
this.name = Math.random();
},
},
});
</script>