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

相关推荐
万少4 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站6 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名8 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫9 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊9 小时前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter9 小时前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折9 小时前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_9 小时前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
Angelial9 小时前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js
jiayu10 小时前
Angular学习笔记24:Angular 响应式表单 FormArray 与 FormGroup 相互嵌套
前端