<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>简单实现vue双向绑定</title></title>
</head>
<script>
class Vue {
constructor(options) {
// 1,保存数据
this.$options = options
this.$data = options.data
this.$el = options.el
// 2,将data添加到响应式数据中
new Observer(this.$data)
// 3,代理this.$data的数据
Object.keys(this.$data).forEach(key => {
this._proxy(key)
})
// 4,处理el
new Compiler(this.$el, this)
}
_proxy(key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
set(newValue) {
this.$data[key] = newValue
},
get() {
return this.$data[key]
}
})
}
}
class Observer {
constructor(data) {
this.data = data
Object.keys(data).forEach(key => {
this.defineReactive(this.data, key, data[key])
})
}
defineReactive(data, key, val) {
const dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set(newValue) {
if(newValue === val) {
return
}
val = newValue
dep.notify()
}
})
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
constructor(node, name, vm) {
this.node = node
this.name = name
this.vm = vm
Dep.target = this
this.update()
Dep.target = null
}
update() {
if(this.node.nodeName === 'INPUT') {
this.node.value = this.vm[this.name]
} else {
this.node.nodeValue = this.vm[this.name]
}
}
}
const reg = /\{\{(.*)\}\}/
class Compiler {
constructor(el, vm) {
this.el = document.querySelector(el)
this.vm = vm
this.frag = this._createFragment()
this.el.appendChild(this.frag)
}
_createFragment() {
const frag = document.createDocumentFragment()
let child
while( child = this.el.firstChild ) {
this._compile(child)
frag.appendChild(child)
}
return frag
}
_compile(node) {
console.dir(node)
if( node.nodeType === 1 ) {
const attrs = node.attributes
if(attrs.hasOwnProperty('v-model')) {
const name = attrs['v-model'].nodeValue
if(node.nodeName === 'INPUT') {
node.addEventListener('input', e => {
this.vm[name] = e.target.value
})
new Watcher(node, name, this.vm)
}
}
}
if( node.nodeType === 3 ) {
if(reg.test(node.nodeValue)) {
const name = RegExp.$1.trim()
new Watcher(node, name, this.vm)
}
}
}
}
</script>
<body>
<div id="app">
<input v-model="name"></input>
{{name}}
</div>
</body>
<script>
let app = new Vue({
el: "#app",
data: {
name: 'Adam'
}
})
</script>
</html>
代码与思路整理自B站老师ilovecoding的教程(补充了一点对input的处理)最全最新Vue、Vuejs教程,从入门到精通_哔哩哔哩_bilibili?
|