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

相关推荐
卡布叻_星星7 小时前
前端JavaScript笔记之父子组件数据传递,watch用法之对象形式监听器的核心handler函数
前端·javascript·笔记
开发加微信:hedian1168 小时前
短剧小程序开发全攻略:从技术选型到核心实现(前端+后端+运营干货)
前端·微信·小程序
YCOSA202510 小时前
ISO 雨晨 26200.6588 Windows 11 企业版 LTSC 25H2 自用 edge 140.0.3485.81
前端·windows·edge
小白呀白10 小时前
【uni-app】树形结构数据选择框
前端·javascript·uni-app
吃饺子不吃馅11 小时前
深感一事无成,还是踏踏实实做点东西吧
前端·svg·图形学
90后的晨仔11 小时前
Mac 上配置多个 Gitee 账号的完整教程
前端·后端
少年阿闯~~12 小时前
CSS——实现盒子在页面居中
前端·css·html
开发者小天12 小时前
uniapp中封装底部跳转方法
前端·javascript·uni-app
阿波罗尼亚12 小时前
复杂查询:直接查询/子查询/视图/CTE
java·前端·数据库