https://juejin.im/post/5a83c7125188257a836c3508
#多种方式实现MVVM,本章先讲解基于defineProperty来实现,也就是类似于实现vue的mvvm功能。本系列有3种实现方式,defineProperty(VUE),脏检查(angular),原生js实现(发布订阅者模式)
github地址 查看源码 https://github.com/honeydlp/mvvm.git
#vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
先看原理图,再结合代码
代码贴图了,今天用掘金code 总是显示在一行,所以截图了,抱歉哈,代码在github上
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><input type="text" v-model="msg">{{msg}}</div><script>// vue MVVM 的实现function MVVM (opts) {this.data = opts.data;this.el = opts.el;var data = this.data;var me = this;var el = document.querySelector(this.el);observer(data, this);var dom = nodeToFragment(el, this);el.appendChild(dom);}// 将el内元素 编译成fragment dom 片段function nodeToFragment (node, vm) {var frag = document.createDocumentFragment();var child;while (child = node.firstChild) {complie(child, vm);frag.append(child);}return frag;}// 解析指令,{{}}function complie (node, vm) {var reg = /\{\{(.*)\}\}/;if (node.nodeType == 1) { // 元素var attrs = node.attributes; // 拿到属性键值对for (var i = 0, len = attrs.length; i < len; i++) {if (attrs[i].nodeName == 'v-model') {var name = attrs[i].nodeValue;node.addEventListener('input', function (e) {vm[name] = e.target.value;})node.value = vm[name];node.removeAttribute('v-model');new Watch(vm, node, name, 'input'); // 添加个监听器}}}if (node.nodeType == 3) { // 元素或属性中的文本内容if (reg.test(node.nodeValue)) {var name = RegExp.$1;name = name.trim();node.nodeType = 'text';new Watch(vm, node, name, 'text');}}}// 监听器function Watch (vm, node, name, nodeType) {Dep.target = this;this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;this.update();Dep.target = null;}Watch.prototype = {update: function () {this.get();if (this.nodeType == 'text') {this.node.nodeValue = this.value;}if (this.nodeType == 'input') {this.node.value = this.value;}},get: function () {this.value = this.vm[this.name];}};// observerfunction observer (data, vm) {Object.keys(data).forEach(function (key) {defineReactive(vm, key, data[key]);})}function defineReactive (vm, key, val) {var dep = new Dep();Object.defineProperty(vm, key, {get: function () {if (Dep.target) {dep.addSub(Dep.target);}return val;},set: function (newVal) {if (newVal == val) return;val = newVal;dep.notify();}})}function Dep () {this.subs = [];}Dep.prototype = {addSub: function (sub) {this.subs.push(sub);},notify: function () {this.subs.forEach(function (sub) {sub.update();})}}// MVVM调用var vm = new MVVM({el: '#app',data: {'msg': 'hello word'}});</script></body></html>
参考文档:
white 改写
function MVVM(opts) {this.el = opts.el;this.data = opts.data;observer(this.data, this);const el = document.querySelector(this.el);const dom = nodeToFragment(el, this);el.appendChild(dom);}function nodeToFragment(node, vm) {const frag = document.createDocumentFragment();let child;while (child = node.firstChild) {node.removeChild(child)compile(child, vm);frag.append(child)}return frag;}function compile(node, vm) {const reg = /\{\{(.*)\}\}/;if (node.nodeType === 1) {const attrs = node.attributes;for (let i = 0, len = attrs.length; i < len; i++) {if (attrs[i].nodeName === 'v-model') {const name = attrs[i].nodeValue;node.addEventListener('input', function (e) {vm[name] = e.target.value;});new Watcher(vm, node, name, 'input')node.removeAttribute('v-model');}}} else if (node.nodeType === 3) {if (reg.test(node.nodeValue)) {let name = RegExp.$1;name = name.trim();new Watcher(vm, node, name, 'text')}}}function observer(data, vm) {Object.keys(data).forEach(key => {defineReactive(vm, key, data[key]);})}function defineReactive(vm, key, value) {const dep = new Dep();Object.defineProperty(vm, key, {get() {if (Dep.target) {dep.addSub(Dep.target);}return value;},set(newValue) {if (newValue === value) return;value = newValue;dep.notify();},})}class Dep {subs = [];addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => {sub.update();})}}class Watcher {constructor(vm, node, name, nodeType) {Dep.target = this;this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;this.update();Dep.target = null;}update() {if (this.nodeType === 'text') {this.node.nodeValue = this.vm[this.name];}if (this.nodeType === 'input') {this.node.value = this.vm[this.name];}}}
