一、什么是双向绑定?
在前端开发中,我们经常会遇到 数据(Model) 和 视图(View) 的同步问题。
- 单向绑定 :数据从 Model → View
👉 当我们更新Model
时,页面会自动更新。 - 双向绑定 :数据可以 双向流动(Model ↔ View)
👉 当用户修改输入框(View),数据(Model)也会随之变化。
举个栗子
当用户在表单里输入内容时:
- 视图(input 的 value)改变
- 数据(绑定在 data 里的值)也自动更新
这就叫做 双向绑定。
二、双向绑定的原理
Vue 采用了 MVVM 模式 来实现双向绑定。
- Model:数据和业务逻辑
- View:页面上的 DOM、UI
- ViewModel:Vue 的核心,负责把数据和视图关联起来
ViewModel 有两个核心职责:
- 数据变化 → 更新视图
- 视图变化 → 更新数据
要实现这一点,它主要依赖两个机制:
- Observer(监听器) :监听数据变化
- Compiler(解析器) :解析模板,把数据绑定到 DOM
三、Vue 中的双向绑定流程
-
初始化 Vue 实例
- 对
data
进行响应式处理(Observe) - 编译模板(Compile),找到动态绑定的数据并渲染到页面
- 对
-
依赖收集(Dep & Watcher)
- 每个
key
都有一个Dep
管家 - 视图上用到
key
时,生成一个Watcher
订阅者 - 当数据变化时,
Dep
会通知所有Watcher
,更新视图
- 每个
-
数据和视图的联动
- 用户修改表单(View),触发
setter
,数据更新 - 数据更新触发
Dep.notify()
,页面自动更新
- 用户修改表单(View),触发
流程图可以这样理解:
objectivec
用户输入 → setter → Dep.notify() → Watcher.update() → 更新视图
数据修改 → getter → 收集依赖 → 绑定 Watcher
四、核心代码实现(简化版)
1. Vue 构造函数
kotlin
class Vue {
constructor(options) {
this.$options = options
this.$data = options.data
// 对 data 做响应式处理
observe(this.$data)
// 代理 data 到 Vue 实例上
proxy(this)
// 编译模板
new Compile(options.el, this)
}
}
2. 数据响应化(Observer)
javascript
function observe(obj) {
if (typeof obj !== "object" || obj == null) return
new Observer(obj)
}
class Observer {
constructor(value) {
this.walk(value)
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
3. 数据劫持(defineReactive)
javascript
function defineReactive(obj, key, val) {
const dep = new Dep() // 每个 key 一个管家
Object.defineProperty(obj, key, {
get() {
// 收集依赖
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
dep.notify() // 通知视图更新
}
})
}
4. 依赖收集(Dep & Watcher)
kotlin
class Dep {
constructor() {
this.deps = [] // 存储所有订阅者
}
addDep(dep) {
this.deps.push(dep)
}
notify() {
this.deps.forEach(dep => dep.update())
}
}
class Watcher {
constructor(vm, key, updater) {
this.vm = vm
this.key = key
this.updater = updater
// 在 getter 中收集依赖
Dep.target = this
vm[key] // 触发 getter
Dep.target = null
}
update() {
this.updater.call(this.vm, this.vm[this.key])
}
}
5. 模板编译(Compile)
javascript
class Compile {
constructor(el, vm) {
this.$vm = vm
this.$el = document.querySelector(el)
if (this.$el) {
this.compile(this.$el)
}
}
compile(el) {
const childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
console.log("编译元素:", node.nodeName)
} else if (this.isInterpolation(node)) {
console.log("编译插值文本:", node.textContent)
}
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node) // 递归编译子节点
}
})
}
isElement(node) {
return node.nodeType === 1
}
isInterpolation(node) {
return node.nodeType === 3 && /{{(.*)}}/.test(node.textContent)
}
}
五、总结
Vue 的 双向绑定 本质就是:
- 用
Object.defineProperty
劫持数据的 getter / setter - 在 getter 里收集依赖,在 setter 里通知更新
- Watcher 负责更新视图,Dep 负责管理依赖
一句话总结:
👉 数据驱动视图,视图反过来也能驱动数据。
六、拓展思考
- Vue3 改用了 Proxy 替代
Object.defineProperty
,性能更好,支持对数组和嵌套对象的更全面监听。 - React 本质上是 单向数据流 ,要实现类似效果,需要手动写
onChange
+setState
。
📌 本文部分内容借助 AI 辅助生成,并由作者整理审核。