使用 defineProperty / Proxy 两种方法实现的数据绑定。
MVVMDefineProperty
<body>
<div id="app">
<p>
My name is {{firstName + ' ' + lastName}}, I am {{age}} years old.
</p>
</div>
<script>
const target = document.getElementById('app');
window.appData = {
firstName: 'Ring',
lastName: 'Chenng',
age: 25
}
new MVVMDefineProperty(target, appData)
// My name is Ring Chenng, I am 25 years old.
// appData.firstName = 'Ruilin'
// appData.age = 26
// My name is Ruilin Chenng, I am 26 years old.
</script>
</body>
class MVVMDefineProperty {
constructor(el, data) {
this.el = el;
this.data = data;
this.changes = [];
this.init();
this.parse(this.el);
this.applyChanges();
}
init() {
Object.keys(this.data).forEach(key => {
let value = this.data[key];
const self = this;
Object.defineProperty(this.data, key, {
get() {
return value;
},
set(newValue) {
value = newValue;
self.applyChanges();
}
});
});
}
parse(el) {
Array.from(el.childNodes).forEach(child => {
if (!(child instanceof Text)) {
this.parse(child);
} else {
this.changes.push(((originExp) => () => {
this.insertExpForTextNode(child, originExp);
})(child.nodeValue));
}
});
}
insertExpForTextNode(node, originExp) {
const newValue = originExp.replace(/{{[\s\S]+?}}/g, exp => {
exp = exp.replace(/[{}]/g, '');
return this.execute(exp);
});
if (newValue !== node.nodeValue) {
node.nodeValue = newValue;
}
}
execute(exp) {
const func = new Function(
...Object.keys(this.data),
`return ${exp}`,
)
return func(...Object.values(this.data));
}
applyChanges() {
setTimeout(() => {
this.changes.forEach(func => func());
});
}
}
MVVMProxy
<body>
<div id="app">
<p>
My name is {{firstName + ' ' + lastName}}, I am {{age}} years old.
</p>
</div>
<script>
const target = document.getElementById('app');
const appData = {
firstName: 'Ring',
lastName: 'Chenng',
age: 25
}
window.app = new MVVMProxy(target, appData)
// My name is Ring Chenng, I am 25 years old.
// app.data.firstName = 'Ruilin'
// app.data.appData.age = 26
// My name is Ruilin Chenng, I am 26 years old.
</script>
</body>
class MVVMProxy {
constructor(el, data) {
this.el = el;
this.data = data;
this.changes = [];
this.init();
this.parse(this.el);
this.applyChanges();
}
init() {
const self = this;
this.data = new Proxy(this.data, {
get(obj, prop) {
return obj[prop];
},
set(obj, prop, value) {
obj[prop] = value;
self.applyChanges()
}
})
}
parse(el) {
Array.from(el.childNodes).forEach(child => {
if (!(child instanceof Text)) {
this.parse(child);
} else {
this.changes.push(((originExp) => () => {
this.insertExpForTextNode(child, originExp);
})(child.nodeValue));
}
});
}
insertExpForTextNode(node, originExp) {
const newValue = originExp.replace(/{{[\s\S]+?}}/g, exp => {
exp = exp.replace(/[{}]/g, '');
return this.execute(exp);
});
if (newValue !== node.nodeValue) {
node.nodeValue = newValue;
}
}
execute(exp) {
const func = new Function(
...Object.keys(this.data),
`return ${exp}`,
)
return func(...Object.values(this.data));
}
applyChanges() {
setTimeout(() => {
this.changes.forEach(func => func());
});
}
}