整体流程图
在10~15分钟完成的自定义Vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>EasyFrame</title> <script src="./easy-frame.js"></script> </head> <body> <div id="app"> <p>{{count}}</p> <button @click="add">+1</button> </div> <script> const app = new EasyFrame({ el: "#app", data: { count: 0, }, methods: { add() { console.log("add"); this.count++; }, }, }); </script> </body> </html>
EasyFrame
function EasyFrame(options) { this.$options = options; this.$el = this.$options.el; this.$data = this.$options.data; this.$methods = this.$options.methods; observe(this.$data); proxy(this); compile(this.$el, this); }
observe
function observe(obj) { function defineReactive(obj, key, val) { observe(val); const dep = new Dep(); Object.defineProperty(obj, key, { get() { // 绑定后清空target,下个对象响应化时,对应绑定 Dep.target && dep.addDep(Dep.target); console.log("getter"); return val; }, set(newVal) { if (newVal != val) { console.log("setter"); observe(val); val = newVal; dep.notify(); } }, }); } Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key])); }
compile
function compile(el, vm) { function traverse(nodes) { nodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { Array.from(node.attributes).forEach((attr) => { if (attr.name.startsWith("@")) { const attrName = attr.name.substring(1); const attrValue = attr.value; node.addEventListener(attrName, vm.$methods[attrValue].bind(vm)); } }); } if ( node.nodeType === Node.TEXT_NODE && /\{\{(.*)\}\}/.test(node.textContent) ) { node.textContent = vm[RegExp.$1]; new Watcher(vm, RegExp.$1, function (val) { node.textContent = val; }); } if (node.childNodes && node.childNodes.length > 0) { traverse(node.childNodes); } }); } const childNodes = document.querySelector(el).childNodes; traverse(childNodes); }
Watcher&Dep
function Dep() { this.watchers = []; this.addDep = function (watcher) { this.watchers.push(watcher); }; this.notify = function () { this.watchers.forEach((watcher) => watcher.update()); }; } function Watcher(vm, key, fn) { // 触发依赖收集 Dep.target = this; vm[key]; Dep.target = null; // 回调最新val this.update = function () { fn && fn(vm[key]); }; }
proxy
function proxy(vm) { Object.keys(vm.$data).forEach((key) => { Object.defineProperty(vm, key, { get() { return vm.$data[key]; }, set(val) { vm.$data[key] = val; }, }); }); }