index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p>
<input type="text" v-model="name" placeholder="请输入名称" />
<input type="text" v-model="age" placeholder="请输入年龄" />
<input type="text" v-model="email" placeholder="请输入邮箱" />
</p>
<p>
<p>
<input type="text" v-model="tel" placeholder="请输入电话" />
</p>
</p>
<p>
<p>姓名: <span>{{ name }}</span></p>
<p>年龄: <span>{{ age }}</span></p>
<p>邮箱: <span>{{ email }}</span></p>
<p>电话: <span>{{ tel }}</span></p>
</p>
<button id="btn">改名字</button>
</div>
<script src="./MVVM.js"></script>
<script>
var app = new MVVM('#app', {
name: 'Lance',
age: 26,
email: '',
tel: ''
});
var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
// app.name = 'GC';
app.setData('name', 'GC');
});
</script>
</body>
</html>
MVVM.js
const reg_valid = /\\{\\{(.+?)\\}\\}/;
class MVVM {
constructor(el, data) {
this.$el = document.querySelector(el);
this._data = data;
this.domPool = {};
this.init();
}
init() {
this.initData();
// this.initProxyData();
this.initDOM();
}
initProxyData() {
let self = this;
this.data = this._data;
this.data = new Proxy(this.data, {
get(target, key) {
console.log(`get ${key}`);
return Reflect.get(target, key);
},
set(target, key, newValue) {
if (newValue !== target[key]) {
console.log(`set ${key}: ${newValue}`);
self.domPool[key].forEach(node => {
if (node.tagName.toLowerCase() === 'input') {
node.value = newValue;
} else {
node.innerText = newValue;
}
});
return Reflect.set(target, key, newValue);
}
}
});
}
initData() {
let self = this;
this.data = {}; // 不修改传入对象
for (let key in this._data) {
Object.defineProperty(this.data, key, {
get() {
console.log(`get ${key}`);
return self._data[key];
},
set(newValue) {
if (newValue !== self._data[key]) {
console.log(`set ${key}: ${newValue}`);
self._data[key] = newValue;
self.domPool[key].forEach(node => {
if (node.tagName.toLowerCase() === 'input') {
node.value = newValue;
} else {
node.innerText = newValue;
}
});
}
}
});
}
}
initDOM() {
this.bindInput();
this.bindDOM(this.$el);
}
bindInput() {
let inputs = this.$el.querySelectorAll('input');
inputs.forEach(input => {
let vModel = input.getAttribute('v-model');
if (vModel) {
input.value = this.data[vModel];
input.addEventListener('input', this.handleInput.bind(this, input, vModel), false);
if (!this.domPool[vModel]) this.domPool[vModel] = [];
this.domPool[vModel].push(input);
}
});
}
handleInput(input, key) {
let val = input.value;
this.data[key] = val;
}
bindDOM(el) {
el.childNodes.forEach(node => {
if (node.nodeType === 3) {
let val = node.nodeValue;
if (val.trim()) {
let isValid = reg_valid.test(val);
if (isValid) {
let key = val.match(reg_valid)[1].trim();
if (!this.domPool[key]) this.domPool[key] = [];
this.domPool[key].push(node.parentNode);
node.parentNode.innerText = this.data[key] || undefined;
}
}
}
node.childNodes && this.bindDOM(node);
});
}
setData(key, value) {
this.data[key] = value;
}
}