Vue.js 实现双向数据绑定的核心技术在于它对数据对象进行观察(Observer)模式,并结合发布订阅模式(Publisher-Subscriber pattern)来完成自动更新视图的功能。以下是 Vue.js 如何实现双向数据绑定的基本步骤:
数据劫持
当 Vue 实例创建时,会递归遍历初始化的数据对象(data 对象),将所有属性转换为 getter/setter。这个过程被称为数据劫持或数据代理。通过这种机制,Vue 能够追踪到数据的变化,并在变化发生时触发相应的回调。
Observer 模块
当一个新的 Vue 实例被创建时,Vue 的初始化流程会调用 data 的 observer 方法来创建一个 Observer 实例。这个实例会对 data 对象进行递归遍历,把每个属性都转换成 getter/setter。
Dep 类
Dep 类负责维护订阅者列表,并提供一个方法来通知所有的订阅者更新。每当数据发生变化时,都会通过 setter 触发 Dep 实例中的 notify 方法,从而通知所有订阅者更新视图。
Watcher 类
Watcher 类负责更新视图。当一个属性被访问时,会创建一个 Watcher 实例,这个实例会自动订阅相关联的 Dep。当数据发生变化时,Dep 将通知所有相关的 Watcher 进行更新。
模板编译
Vue 使用模板编译策略来解析模板字符串中的表达式。当模板被编译时,Vue 会识别出绑定表达式并创建 Watcher 实例来监听这些表达式的依赖。
更新机制
当数据发生变化时,Vue 的响应系统会触发视图的更新。这个过程通常是通过重新渲染组件来实现的,具体来说就是重新计算模板表达式并更新 DOM。
示例代码
这里是一个简化的示例,展示了 Vue 如何处理数据变化:
app.js
// 简化版的 Vue 实现
function Vue(options) {
this.$options = options;
this._init();
}
Vue.prototype._init = function() {
this.$data = this.$options.data;
observe(this.$data, this);
this.$mount(this.$options.el);
};
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
new Observer(value, vm);
}
function defineReactive(obj, key, val, vm) {
let dep = new Dep();
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
vm.$forceUpdate(); // 强制更新视图
}
});
}
function Observer(value, vm) {
if (typeof value !== 'object' || value instanceof Array) {
return;
}
Object.keys(value).forEach(key => {
defineReactive(value, key, value[key], vm);
});
}
function Dep() {
this.subs_ = [];
}
Dep.prototype.addSub = function(sub) {
this.subs_.push(sub);
};
Dep.prototype.notify = function() {
this.subs_.forEach(sub => sub.update());
};
function Watcher(vm, expression) {
this.vm = vm;
this.expression = expression;
this.cb = null;
this.value = this.get();
}
Watcher.prototype.get = function() {
Dep.target = this;
let value = eval(`this.vm.${this.expression}`);
Dep.target = null;
return value;
};
Watcher.prototype.update = function() {
let value = this.get();
if (value !== this.value) {
this.value = value;
this.vm.$forceUpdate();
}
};
Vue.prototype.$forceUpdate = function() {
let template = this.$options.template;
let compiledTemplate = compile(template, this);
this.$el.innerHTML = compiledTemplate;
};
function compile(template, vm) {
let reg = /\{\{(.*)\}\}|v-(\w+)/g;
return template.replace(reg, (match, exp, dir) => {
if (exp) {
return `<span>${vm.$data[exp]}</span>`;
} else if (dir) {
let directive = dir.split('-')[1];
switch (directive) {
case 'model':
return `<input type="text" @input="updateInput('${dir.split('-')[1]}')" value="${vm.$data[dir.split('-')[1]]}">`;
}
}
});
}
Vue.prototype.updateInput = function(key) {
let value = event.target.value;
this.$data[key] = value;
};
// 创建 Vue 实例
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
template: '<div><input type="text" v-model="message"><p>{{ message }}</p></div>'
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Simplified Example with Two-way Binding</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
<p>{{ message }}</p>
</div>
<script src="app.js"></script>
</body>
</html>
以上是一个非常简化的版本,实际 Vue.js 的实现要复杂得多,并且包含了很多优化措施和错误处理。实际使用时,Vue 还提供了很多其他的特性,如计算属性、侦听器等,以帮助开发者更好地管理应用的状态。
2258

被折叠的 条评论
为什么被折叠?



