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];
}
};
// observer
function 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];
}
}
}