Vue中双向绑定是什么?它工作原理是什么?万字长文,一次讲透彻!

Last updated on December 6, 2025 am

大家好,我是jvxiao。

这是Vue从入门到精通系列文章的第2篇,今天我们来讲一讲Vue中一个非常核心的概念–双向绑定

本文将从概念本质、底层原理出发,结合完整实现逻辑,带大家全面拆解Vue双向绑定的核心机制。

一、核心概念:从单向绑定到双向联动

要理解双向绑定,首先要明确其与单向绑定的关联与区别——双向绑定是单向绑定的延伸与闭环,而MVVM架构则为这种闭环提供了理论支撑。

单向绑定的基础逻辑

单向绑定指数据模型(Model)到视图(View)的单向同步:当开发者通过代码修改Model中的数据时,框架会自动更新对应的View展示,无需手动操作DOM。这种模式的核心价值是“数据驱动视图”,让开发者专注于数据逻辑而非DOM操作,但它存在一个明显局限:用户对View的交互(如表单输入、按钮点击)无法自动同步回Model,需要手动编写事件处理函数来完成数据更新。

2. 双向绑定的本质闭环

双向绑定在单向绑定的基础上,补充了“View到Model的反向同步”:

  • 数据→视图:Model数据变化时,View自动更新;
  • 视图→数据:用户操作View(如输入框输入、下拉框选择)时,Model数据自动同步。

最典型的应用场景是表单交互:当用户在输入框中输入内容时,对应的Model数据会实时更新;当代码修改Model数据时,输入框的显示内容也会同步变化。这种“无需手动干预”的双向同步,正是双向绑定的核心魅力。

3. MVVM架构:双向绑定的结构支撑

Vue的双向绑定并非孤立功能,而是基于MVVM(Model-View-ViewModel)架构设计的,三层结构各司其职、协同工作:

  • Model(数据层):存储应用的核心数据与业务逻辑,是应用的“数据源”,例如Vue组件中的data选项;
  • View(视图层):应用的可视化展示部分,由HTML、CSS构成,是用户直接交互的界面;
  • ViewModel(业务逻辑层):Vue框架的核心封装,充当Model与View之间的“桥梁”。它既监听Model的变化以更新View,又监听View的交互以更新Model,实现了两层之间的解耦与联动。

ViewModel的存在让Model与View完全分离,开发者无需关心“数据如何同步到视图”“视图交互如何更新数据”,只需专注于数据逻辑与界面设计,这也是Vue开发效率高的关键原因之一。

二、底层原理:四大核心模块的协同运作

双向绑定的实现依赖ViewModel的核心能力,而ViewModel的功能又由四个关键模块共同支撑:监听器(Observer)解析器(Compiler)依赖管理器(Dep)订阅者(Watcher)。这四个模块形成闭环,完成“数据劫持-模板解析-依赖收集-更新通知”的全流程。

1. 监听器(Observer):数据的“哨兵”

Observer的核心职责是对Model中的数据进行响应式劫持,实时监听数据的变化。其实现逻辑如下:

  • 遍历data中的所有属性(包括嵌套对象),通过Object.defineProperty(Vue2)或Proxy(Vue3)重写属性的gettersetter方法;
  • 当数据被读取时(如视图渲染时访问数据),触发getter方法,用于后续的依赖收集;
  • 当数据被修改时(如用户输入更新数据),触发setter方法,向依赖管理器发送更新通知。

Vue2中使用Object.defineProperty存在一定局限(无法监听数组索引变化、对象新增属性),而Vue3改用Proxy,不仅解决了这些问题,还能直接监听整个对象,实现更全面、高效的响应式劫持。

2. 解析器(Compiler):视图的“翻译官”

Compiler的核心职责是解析View中的模板指令,建立View与Model的关联,其工作流程如下:

  • 遍历DOM树,扫描所有元素节点和文本节点,识别出包含Vue指令(如v-model{{}}插值表达式)的节点;
  • 对插值文本(如{{message}}),从Model中获取对应数据,替换文本内容并初始化视图;
  • 对指令节点(如<input v-model="message">),解析指令绑定的数据属性,同时绑定视图交互事件(如input事件);
  • 为每个绑定的数据属性创建对应的订阅者(Watcher),指定数据变化时的视图更新函数。

Compiler的存在让模板中的动态绑定与Model数据建立了精准关联,为双向绑定提供了“视图层面的入口”。

3. 依赖管理器(Dep):数据与订阅者的“中介”

Dep的核心职责是管理某个数据属性对应的所有订阅者(Watcher),充当“数据-订阅者”的桥梁。其核心逻辑如下:

  • 每个数据属性在被响应式劫持时,会创建一个专属的Dep实例;
  • 当数据被读取(触发getter)时,Dep会将当前的Watcher实例添加到自身的依赖列表中(依赖收集);
  • 当数据被修改(触发setter)时,Dep会遍历依赖列表,通知所有Watcher执行更新操作(更新通知)。

由于同一个数据属性可能在视图中多次使用(如message在多个插值文本中出现),一个Dep实例可能管理多个Watcher,确保数据变化时所有关联视图都能同步更新。

4. 订阅者(Watcher):数据与视图的“连接器”

Watcher是连接Model与View的核心载体,其核心职责是接收Dep的更新通知,执行视图更新函数。其工作流程如下:

  • 当Compiler解析模板时,会为每个动态绑定的数据属性创建一个Watcher实例,同时指定对应的视图更新函数(如替换插值文本、修改元素value值);
  • Watcher创建时,会主动读取对应的Model数据,触发数据的getter方法,从而让自身被添加到该数据的Dep依赖列表中;
  • 当数据变化时,Dep会调用Watcher的update方法,执行预设的更新函数,完成视图的同步更新。

5. 双向联动的完整闭环

结合四大模块,Vue双向绑定的完整流程可拆解为两步:

(1)数据→视图:Model驱动View更新

  1. 开发者通过代码修改Model中的数据(如this.message = "新内容");
  2. 数据的setter方法被触发,调用对应Dep实例的notify方法;
  3. Dep遍历依赖列表,通知所有关联的Watcher执行update方法;
  4. Watcher调用预设的更新函数,修改DOM元素内容或属性,View同步更新。

(2)视图→数据:View驱动Model更新

  1. 用户操作View(如在输入框中输入内容),触发原生事件(如input事件);
  2. Compiler解析v-model指令时已绑定该事件,事件回调函数会修改对应的Model数据;
  3. 数据的setter方法被触发,重复“数据→视图”的流程,确保所有关联视图同步更新。

三、完整实现:基于Vue2思路的双向绑定落地

结合上述原理,我们基于Vue2的Object.defineProperty实现一个完整的双向绑定案例,覆盖“响应式劫持-模板编译-依赖收集-双向同步”全流程。

1. 核心类与工具函数定义

(1)Vue构造函数:初始化入口

负责整合响应式处理、数据代理、模板编译三大核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Vue {
constructor(options) {
this.$options = options; // 存储配置项(el、data等)
this.$data = typeof options.data === 'function' ? options.data() : options.data; // 处理data(支持函数或对象)

// 1. 对data执行响应式处理
observe(this.$data);
// 2. 代理data属性到Vue实例(支持this.message而非this.$data.message)
proxy(this, this.$data);
// 3. 编译模板
new Compile(options.el, this);
}
}

// 数据代理:将$data的属性代理到Vue实例
function proxy(vm, data) {
Object.keys(data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return data[key];
},
set(newVal) {
data[key] = newVal;
}
});
});
}

(2)Observer:响应式劫持实现

遍历数据属性,重写gettersetter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 入口函数:判断数据类型,非对象直接返回
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return;
}
new Observer(obj);
}

class Observer {
constructor(value) {
this.value = value;
// 遍历对象属性(仅处理对象,数组需额外处理,此处简化)
this.walk(value);
}

walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
}

// 核心:重写属性的getter和setter,关联Dep
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
// 为当前属性创建Dep实例
const dep = new Dep();

Object.defineProperty(obj, key, {
get() {
// 依赖收集:Dep.target为当前Watcher实例
if (Dep.target) {
dep.addDep(Dep.target);
}
return val;
},
set(newVal) {
// 数据未变化则不触发更新
if (newVal === val) return;
val = newVal;
// 新值可能是对象,需递归响应式处理
observe(newVal);
// 通知所有Watcher更新
dep.notify();
}
});
}

(3)Dep:依赖管理器

管理Watcher实例,负责依赖收集与更新通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Dep {
constructor() {
this.deps = []; // 存储当前数据的所有Watcher
}

// 添加Watcher到依赖列表
addDep(watcher) {
this.deps.push(watcher);
}

// 通知所有Watcher执行更新
notify() {
this.deps.forEach(watcher => watcher.update());
}
}

// 静态属性:存储当前活跃的Watcher(全局唯一,避免多Watcher冲突)
Dep.target = null;

(4)Watcher:订阅者实现

连接Dep与视图更新函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Watcher {
constructor(vm, key, updaterFn) {
this.vm = vm; // Vue实例
this.key = key; // 绑定的数据属性名
this.updaterFn = updaterFn; // 视图更新函数

// 依赖收集关键步骤:将当前Watcher设为活跃状态
Dep.target = this;
// 读取数据,触发getter,完成依赖收集
this.vm[this.key];
// 重置活跃Watcher,避免重复收集
Dep.target = null;
}

// 接收Dep通知,执行更新函数
update() {
const newValue = this.vm[this.key];
this.updaterFn(newValue);
}
}

(5)Compile:模板解析实现

解析DOM中的指令和插值,建立数据与视图的关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class Compile {
constructor(el, vm) {
this.$vm = vm; // Vue实例
this.$el = document.querySelector(el); // 挂载点DOM元素

if (this.$el) {
this.compile(this.$el); // 开始编译
}
}

// 递归遍历DOM树,解析所有节点
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
// 1. 解析元素节点(处理v-model等指令)
if (this.isElementNode(node)) {
this.compileElement(node);
}
// 2. 解析文本节点(处理{{}}插值)
else if (this.isTextNode(node) && this.isInterpolation(node)) {
this.compileText(node);
}

// 递归处理子节点(支持嵌套模板)
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}

// 判断是否为元素节点(nodeType=1)
isElementNode(node) {
return node.nodeType === 1;
}

// 判断是否为文本节点(nodeType=3)
isTextNode(node) {
return node.nodeType === 3;
}

// 判断是否为插值文本(匹配{{xxx}})
isInterpolation(node) {
return /\{\{(.*)\}\}/.test(node.textContent);
}

// 解析元素节点:处理v-model指令
compileElement(node) {
const attributes = node.attributes;
Array.from(attributes).forEach(attr => {
// 提取指令名(如v-model)和绑定的属性名(如message)
const attrName = attr.name;
if (this.isDirective(attrName)) {
const directiveName = attrName.slice(2); // 去掉"v-"
const key = attr.value; // 绑定的数据属性名

// 处理v-model指令(双向绑定核心)
if (directiveName === 'model') {
this.modelUpdater(node, key);
}
}
});
}

// 判断是否为Vue指令(以v-开头)
isDirective(attrName) {
return attrName.startsWith('v-');
}

// 解析插值文本:替换{{xxx}}为实际数据
compileText(node) {
// 提取{{}}中的属性名(如message)
const key = RegExp.$1.trim();
// 初始化视图:将文本替换为data中的数据
this.textUpdater(node, this.$vm[key]);
// 创建Watcher:数据变化时更新文本
new Watcher(this.$vm, key, (newValue) => {
this.textUpdater(node, newValue);
});
}

// 文本更新函数:修改文本节点内容
textUpdater(node, value) {
node.textContent = value || '';
}

// v-model指令处理:实现双向绑定
modelUpdater(node, key) {
// 初始化视图:将输入框value设为data中的数据
node.value = this.$vm[key] || '';
// 数据→视图:创建Watcher,数据变化时更新输入框value
new Watcher(this.$vm, key, (newValue) => {
node.value = newValue || '';
});
// 视图→数据:监听输入事件,更新data中的数据
node.addEventListener('input', (e) => {
this.$vm[key] = e.target.value;
});
}
}

2. 测试使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- HTML结构 -->
<div id="app">
<p>{{message}}</p>
<input type="text" v-model="message">
</div>

<!-- JavaScript -->
<script>
new Vue({
el: '#app',
data() {
return {
message: 'Hello Vue双向绑定!'
};
}
});
</script>

运行效果:

  • 输入框输入内容时,p标签中的文本会实时同步(视图→数据→视图);
  • 代码中修改vm.message时,输入框和p标签会同时更新(数据→视图)。

四、总结

Vue双向绑定的本质,是MVVM架构下“响应式劫持+发布-订阅模式”的协同作用

  • 响应式劫持(Observer)让数据具备“感知变化”的能力;
  • 模板编译(Compiler)让视图具备“关联数据”的能力;
  • 依赖管理(Dep)与订阅者(Watcher)让数据与视图的联动具备“精准高效”的特性。

作为Vue框架中的核心内容,双向绑定机制不仅是面试中经常考察的内容,同时掌握其本质与工作原理对解读Vue源码也能够打下一个坚实的基础。

【往期精彩】


Vue中双向绑定是什么?它工作原理是什么?万字长文,一次讲透彻!
https://www.jvxiao.cn/posts/bidirection-data-binding.html
Author
jvxiao
Posted on
December 6, 2025
Licensed under