首先在页面上新建基本的HTML结构
<body>
<div id="app">
<span>up主:{{name}}</span>
<input type="text" v-model="name">
<span>更多:{{more.like}}</span>
<input type="text" v-model="more.like">
</div>
<!-- 引用同级目录下的伪Vue -->
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
name: '奥克斯的',
more: {
like: 'asd'
}
}
})
console.log(vm)
</script>
</body>
接下来便是Vue双向绑定的基本实现 首先创建Vue类,Observer方法通过Object.defineProperty来实现对属性的监听
class Vue {
constructor(obj_instance) {
this.$data = obj_instance.data
Observer(this.$data)
Complie(obj_instance.el, 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, {
enumerable: true,
configurable: true,
get() {
Dependency.temp && dependency.addSub(Dependency.temp)
return value
},
set(newVal) {
console.log('出发了setter函数');
value = newVal
Observer(value)
dependency.notify()
}
})
})
}
function Complie(element, vm) {
vm.$el = document.querySelector(element)
const fragment = document.createDocumentFragment()
let child;
while (child = vm.$el.firstChild) {
fragment.append(child)
}
fragment_complie(fragment)
function fragment_complie(node) {
const pattern = /\{\{\s*(\S+)\s*\}\}/
if (node.nodeType === 3) {
const xxx = 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 = xxx.replace(pattern, value)
new Watcher(vm, result_regex[1], (newVal) => {
node.nodeValue = xxx.replace(pattern, newVal)
})
}
return
}
if (node.nodeType === 1 && node.nodeName === 'INPUT') {
const attrs = Array.from(node.attributes)
attrs.forEach((attr) => {
if(attr.nodeName === 'v-model') {
const value = attr.nodeValue.split('.').reduce((total, current) => total[current], vm.$data)
node.value = value
new Watcher(vm, attr.nodeValue, (newVal) => {
node.value = newVal
})
node.addEventListener('input', (e) => {
const attrArr1 = attr.nodeValue.split('.')
const attrArr2 = attrArr1.slice(0, attrArr1.length - 1)
const final = attrArr2.reduce((total, current) => total[current], vm.$data)
final[attrArr1[attrArr1.length-1]] = e.target.value
})
}
})
}
node.childNodes.forEach(child => fragment_complie(child))
}
vm.$el.append(fragment) // 将文档碎片应用到对应的dom元素上
}
// 依赖:收集和通知订阅者
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((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)
}
}