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

相关推荐
JNU freshman20 分钟前
Element Plus组件
前端·vue.js·vue3
一只专注api接口开发的技术猿27 分钟前
容器化与调度:使用 Docker 与 K8s 管理分布式淘宝商品数据采集任务
开发语言·前端·数据库
我有一棵树32 分钟前
性能优化之前端与服务端中的 Gzip 压缩全解析
前端
魔术师卡颂36 分钟前
不就写提示词?提示词工程为啥是工程?
前端·人工智能·后端
訾博ZiBo1 小时前
【Vibe Coding】001-前端界面常用布局
前端
IT_陈寒1 小时前
《Redis性能翻倍的7个冷门技巧,90%开发者都不知道!》
前端·人工智能·后端
歪歪1001 小时前
React Native开发Android&IOS流程完整指南
android·开发语言·前端·react native·ios·前端框架
知识分享小能手1 小时前
uni-app 入门学习教程,从入门到精通,uni-app组件 —— 知识点详解与实战案例(4)
前端·javascript·学习·微信小程序·小程序·前端框架·uni-app
ZYMFZ2 小时前
python面向对象
前端·数据库·python
长空任鸟飞_阿康2 小时前
在 Vue 3.5 中优雅地集成 wangEditor,并定制“AI 工具”下拉菜单(总结/润色/翻译)
前端·vue.js·人工智能