一文搞懂 Vue 的双向绑定

一、什么是双向绑定?

在前端开发中,我们经常会遇到 数据(Model)视图(View) 的同步问题。

  • 单向绑定 :数据从 Model → View
    👉 当我们更新 Model 时,页面会自动更新。
  • 双向绑定 :数据可以 双向流动(Model ↔ View)
    👉 当用户修改输入框(View),数据(Model)也会随之变化。

举个栗子

当用户在表单里输入内容时:

  • 视图(input 的 value)改变
  • 数据(绑定在 data 里的值)也自动更新
    这就叫做 双向绑定

二、双向绑定的原理

Vue 采用了 MVVM 模式 来实现双向绑定。

  • Model:数据和业务逻辑
  • View:页面上的 DOM、UI
  • ViewModel:Vue 的核心,负责把数据和视图关联起来

ViewModel 有两个核心职责:

  1. 数据变化 → 更新视图
  2. 视图变化 → 更新数据

要实现这一点,它主要依赖两个机制:

  • Observer(监听器) :监听数据变化
  • Compiler(解析器) :解析模板,把数据绑定到 DOM

三、Vue 中的双向绑定流程

  1. 初始化 Vue 实例

    • data 进行响应式处理(Observe)
    • 编译模板(Compile),找到动态绑定的数据并渲染到页面
  2. 依赖收集(Dep & Watcher)

    • 每个 key 都有一个 Dep 管家
    • 视图上用到 key 时,生成一个 Watcher 订阅者
    • 当数据变化时,Dep 会通知所有 Watcher,更新视图
  3. 数据和视图的联动

    • 用户修改表单(View),触发 setter,数据更新
    • 数据更新触发 Dep.notify(),页面自动更新

流程图可以这样理解:

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 辅助生成,并由作者整理审核。

相关推荐
鹏北海10 分钟前
多标签页登录状态同步:一个简单而有效的解决方案
前端·面试·架构
_AaronWong14 分钟前
基于 Vue 3 的屏幕音频捕获实现:从原理到实践
前端·vue.js·音视频开发
孟祥_成都23 分钟前
深入 Nestjs 底层概念(1):依赖注入和面向切面编程 AOP
前端·node.js·nestjs
let_code23 分钟前
CopilotKit-丝滑连接agent和应用-理论篇
前端·agent·ai编程
Apifox1 小时前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
木易士心1 小时前
深入剖析:按下 F5 后,浏览器前端究竟发生了什么?
前端·javascript
在掘金801101 小时前
vue3中使用medium-zoom
前端·vue.js
xump1 小时前
如何在DevTools选中调试一个实时交互才能显示的元素样式
前端·javascript·css
折翅嘀皇虫1 小时前
fastdds.type_propagation 详解
java·服务器·前端
Front_Yue1 小时前
深入探究跨域请求及其解决方案
前端·javascript