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

相关推荐
C_心欲无痕21 分钟前
nginx - alias 和 root 的区别详解
运维·前端·nginx
我是苏苏3 小时前
Web开发:C#通过ProcessStartInfo动态调用执行Python脚本
java·服务器·前端
无羡仙3 小时前
Vue插槽
前端·vue.js
用户6387994773054 小时前
每组件(Per-Component)与集中式(Centralized)i18n
前端·javascript
SsunmdayKT4 小时前
React + Ts eslint配置
前端
开始学java4 小时前
useEffect 空依赖 + 定时器 = 闭包陷阱?count 永远停在 1 的坑我踩透了
前端
zerosrat4 小时前
从零实现 React Native(2): 跨平台支持
前端·react native
狗哥哥4 小时前
🔥 Vue 3 项目深度优化之旅:从 787KB 到极致性能
前端·vue.js
青莲8434 小时前
RecyclerView 完全指南
android·前端·面试