参考文章
效果
整体步骤
- 首先是在Vue的构造函数中对data中的数据响应化(使用的是defineProperty)
- 然后对html中相关指令和{ {变量}}形式代码编译(compile), 因为这些变量与Vue.data里的数据有关,需要绑定在一起。
- 绑定过程是每一个与Vue.data中数据相关的节点,都会创建Watcher对象,然后这个代表节点的Watcher会被收集到数据自己的dep中,当这个数据变化时,会遍历dep中的Watcher。
- 数据变化的通知过程,使用defineProperty实现,通过劫持set操作,在其中通知Watcher,通过劫持get操作,来收集依赖。
代码地址
主要代码
复制保存为vmodel.js
// vm == view modelclass Dep{ constructor(){ this.subscribers = []; } addSubscriber(sub) { this.subscribers.push(sub); } notify() { this.subscribers.forEach((sub) => { sub.update(); }); }}class Watcher { constructor(vm, node, name, nodeType) { Dep.target = this; this.vm = vm; this.node = node; this.name = name; this.nodeType = nodeType; this.update(); Dep.target = null; } update() { this.get(); if (this.nodeType == 'text') { this.node.data = this.value; } if (this.nodeType == 'input') { this.node.value = this.value; } } get() { this.value = this.vm.data[this.name]; }}class Vue{ constructor(options) { this.data = options.data; toObserve(this.data); let root = document.querySelector(options.el); var newChild = nodeToFragment(root, this); root.appendChild(newChild); }}function toReactive(key, value, obj) { let dep = new Dep(); Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.addSubscriber(Dep.target); } return value; }, set(newValue) { if (newValue == value) { return; } value = newValue; dep.notify(); } });}function toObserve(obj) { Object.keys(obj).forEach((key) => { toReactive(key, obj[key], obj); });}function nodeToFragment(node, vm) { let flag = document.createDocumentFragment(); let child = node.firstChild; while (child) { // 遍历child compile(child, vm); flag.appendChild(child); child = node.firstChild; } return flag;}function compile(node, vm) { let reg = /\{\{(.*)\}\}/; if (node.nodeType == Node.ELEMENT_NODE) { let attrNode = node.attributes; for (let i = 0; i < attrNode.length; i++) { let attr = attrNode[i]; if (attr.nodeName == 'v-model') { let name = attr.nodeValue; node.addEventListener('input', (e) => { console.log(e); vm.data[name] = e.target.value; // 这里会通知name的subscribers进行update console.log(vm); }); node.value = vm.data[name]; // 通知 node.removeAttribute('v-model'); new Watcher(vm, node, name, 'input'); } } } else if (node.nodeType == Node.TEXT_NODE) { if (reg.test(node.nodeValue)) { let name = RegExp.$1; name = name.trim(); node.nodeValue = vm.data[name]; new Watcher(vm, node, name, 'text'); } }}复制代码
使用
复制保存为index.html,和vmodel.js放在同一文件夹。
Document { { vueData }}复制代码