vue双向绑定简单实现
实现 Vue 双向绑定的核心原理
Vue 的双向绑定主要通过数据劫持和发布-订阅模式实现。以下是简化版的实现思路:
数据劫持(Object.defineProperty)
通过 Object.defineProperty 拦截对象的属性读写操作:
function observe(data) {
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(obj, key, val) {
observe(val) // 递归处理嵌套对象
const dep = new Dep() // 每个属性对应一个 Dep 实例
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target) // 收集依赖
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
observe(newVal) // 新值是对象时继续劫持
dep.notify() // 通知更新
}
})
}
依赖收集(Dep 类)
实现一个简单的依赖收集器:
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null // 全局唯一的 Watcher 标记
观察者(Watcher 类)
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
this.value = this.get()
}
get() {
Dep.target = this
const value = this.vm[this.exp] // 触发 getter
Dep.target = null
return value
}
update() {
const newValue = this.vm[this.exp]
if (newValue !== this.value) {
this.value = newValue
this.cb(newValue)
}
}
}
编译器(Compile)
简单模板编译实现:
function compile(node, vm) {
if (node.nodeType === 1) { // 元素节点
Array.from(node.attributes).forEach(attr => {
if (attr.name.startsWith('v-')) {
const exp = attr.value
if (attr.name === 'v-model') {
node.value = vm[exp]
node.addEventListener('input', e => {
vm[exp] = e.target.value
})
}
}
})
}
}
整合实现
class Vue {
constructor(options) {
this.$options = options
this._data = options.data
observe(this._data)
this._proxyData()
new Compile(options.el, this)
}
_proxyData() {
Object.keys(this._data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return this._data[key]
},
set(newVal) {
this._data[key] = newVal
}
})
})
}
}
使用示例
<div id="app">
<input v-model="message">
<p>{{ message }}</p>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue'
}
})
</script>
现代 Vue 3 的改进
Vue 3 改用 Proxy 实现响应式,解决了 Object.defineProperty 的局限性:
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
Reflect.set(target, key, value)
trigger(target, key)
}
})
}
这个简化实现展示了 Vue 双向绑定的核心机制,实际 Vue 源码还包含虚拟 DOM、组件系统等更多复杂功能。







