<!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>Vue双向绑定实现</title>
</head>
<body>
<div id="app">
<span>阿香婆 {{ name }} </span>
<input type="text" v-model="name">
<span>更多 {{more.like}} </span>
<input type="text" v-model="more.like">
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
name: '蛋老师',
more: {
like : '一键三连'
}
}
})
console.log(vm);
</script>
</body>
</html>
class Vue {
constructor(obj_instance) {
this._data = obj_instance.data
Observe(this._data)
Compile(obj_instance.el, this)
}
}
function Observe(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]
Observe(value)
Object.defineProperty(data_instance, key, {
enumerable: true,
configurable: true,
get() {
Dependency.temp && dependency.addSub(Dependency.temp)
return value
},
set(newValue) {
value = newValue
Observe(newValue)
dependency.notify(key)
}
})
})
}
function Compile(element, vm) {
vm.$el = document.querySelector(element)
const fragment = document.createDocumentFragment()
let child;
while (child = vm.$el.firstChild) {
fragment.append(child)
}
fragment_compile(fragment)
function fragment_compile(node) {
const pattern = /\{\{\s*(\S+)\s*\}\}/
if (node.nodeType === 3) {
const originNode = node.nodeValue
const result_regex = pattern.exec(node.nodeValue)
if (result_regex) {
const arr = result_regex[1].split('.')
const value = arr.reduce((total, current) =>
total[current],
vm._data)
node.nodeValue = originNode.replace(pattern, value)
new Watcher(vm, result_regex[1], newValue => {
node.nodeValue = originNode .replace(pattern, newValue)
})
}
return
}
if (node.nodeType === 1 && node.nodeName === "INPUT") {
const attr = Array.from(node.attributes)
attr.forEach(i => {
if (i.nodeName === 'v-model') {
const value = i.nodeValue.split('.').reduce(
(total, current) => total[current], vm._data
)
node.value = value
new Watcher(vm, i.nodeValue, newValue => {
node.value = newValue
})
node.addEventListener('input', e => {
const arr1 = i.nodeValue.split('.')
const arr2 = arr1.slice(0, arr1.length - 1)
const final = arr2.reduce(
(total, current) => total[current], vm._data
)
final[arr1[arr1.length - 1]] = e.target.value
})
}
})
}
node.childNodes.forEach(child => fragment_compile(child))
}
vm.$el.appendChild(fragment)
}
class Dependency {
constructor() {
this.subscribers = []
}
addSub(sub) {
const index = this.subscribers.findIndex((item)=>{
return item.key === sub.key
})
index === -1 && this.subscribers.push(sub)
}
notify(key) {
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((total, current) => total[current], vm._data)
Dependency.temp = null
}
update() {
const value = this.key.split('.').reduce((total, current) =>
total[current],
this.vm._data)
this.callback(value)
}
}