vue2 双向数据绑定实现
1. 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>vue2双向数据绑定实现</title>
</head>
<body>
<div id="app">
<input type="text" v-model="name.firstName" placeholder="姓氏"><span>姓氏:{{name.firstName}}</span><br>
<input type="text" v-model="name.lastName" placeholder="名称"><span>名称:{{name.lastName}}</span><br>
<input type="text" v-model="phone" placeholder="手机号"><span>手机号:{{phone}}</span>
</div>
<!-- 自定义 vue.js 文件 -->
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
name: {
firstName: '张',
lastName: '三'
},
phone: '12312'
}
})
console.log(vm);
</script>
</body>
</html>
2. vue.js 文件
class Vue {
constructor(obj_instance) {
this.$data = obj_instance.data;
this.$el = document.querySelector(obj_instance.el);
Observer(this.$data);
Compile(this);
}
}
function Observer(data_instance) {
if (!data_instance || typeof data_instance !== 'object') return;
const dependency = new Dependency();
Object.keys(data_instance).forEach(key => {
let value = data_instance[key];
Observer(value);
Object.defineProperty(data_instance, key, {
configurable: true,
enumerable: true,
get() {
Dependency.temp && dependency.addSub(Dependency.temp);
return value;
},
set(newValue) {
value = newValue;
Observer(newValue);
dependency.notify();
}
});
});
}
function Compile(vm) {
const fragment = document.createDocumentFragment();
let child;
while ((child = vm.$el.firstChild)) {
fragment.append(child);
}
function fragment_compile(node) {
const pattern = /{{\s*(\S+)\s*}}/;
if (node.nodeType === 3) {
const regex = pattern.exec(node.nodeValue);
const xxx = node.nodeValue;
if (regex) {
const value = regex[1]
.split('.')
.reduce((pre, current) => pre[current], vm.$data);
node.nodeValue = xxx.replace(pattern, value);
new Watcher(vm, regex[1], newValue => {
node.nodeValue = xxx.replace(pattern, newValue);
});
}
return;
}
if (node.nodeType === 1 && node.nodeName.toLowerCase() === 'input') {
let key = node.attributes['v-model'].nodeValue;
const value = key
.split('.')
.reduce((pre, current) => pre[current], vm.$data);
node.value = value;
new Watcher(vm, key, newValue => {
node.value = newValue;
});
node.addEventListener('input', e => {
const keyArr = key.split('.');
const arr = keyArr.reduce((pre, current) => {
if (!pre[current] || typeof pre[current] !== 'object') {
return pre;
}
return pre[current];
}, vm.$data);
arr[keyArr[keyArr.length - 1]] = e.target.value;
});
}
node.childNodes.forEach(child => fragment_compile(child));
}
fragment_compile(fragment);
vm.$el.appendChild(fragment);
}
class Dependency {
constructor() {
this.subscribers = [];
}
addSub(sub) {
this.subscribers.push(sub);
}
notify() {
this.subscribers.forEach(sub => sub.update());
}
}
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
Dependency.temp = this;
key.split('.').reduce((pre, current) => pre[current], vm.$data);
Dependency.temp = null;
}
update() {
const value = this.key
.split('.')
.reduce((pre, current) => pre[current], this.vm.$data);
this.callback(value);
}
}