Vue.js 的双向数据绑定是通过其响应式系统实现的。当 Vue 实例创建时,它会遍历 data 对象中的所有属性,并使用 Object.defineProperty 将它们转化为 getter/setter,使得 Vue 能够追踪每个属性的变化,并在变化时通知相关的依赖进行更新。
以下是 Vue.js 双向数据绑定的基本实现原理的简化版代码示例:
``function defineReactive(obj, key, val) {
// 创建一个唯一的 Dep 对象,用于存储该属性的所有依赖
const dep = new Dep();
// 获取属性的原始值
let value = val;
// 定义 getter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 如果存在依赖,则添加该依赖到当前属性的 Dep 对象中
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set: function reactiveSetter(newVal) {
// 当属性值发生变化时,通知所有依赖进行更新
if (newVal === value) return;
value = newVal;
dep.notify();
}
});
}
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
// 用来存储当前的 Watcher 对象,在 getter 中使用
Dep.target = null;
class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb;
this.vm = vm;
this.expOrFn = expOrFn;
this.value = this.get();
}
get() {
// 将当前的 Watcher 对象赋值给 Dep.target,以便在 getter 中添加依赖
Dep.target = this;
const value = this.vm[this.expOrFn];
// 执行完毕后,将 Dep.target 重置为 null
Dep.target = null;
return value;
}
update() {
this.run();
}
run() {
const value = this.get();
const oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
}
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
let obj = Array.isArray(value) ? new Object(value) : value;
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
function Vue(options) {
this.$data = options.data;
observe(this.$data, this);
new Watcher(this, 'message', function(value, oldValue) {
console.log(`message changed from ${oldValue} to ${value}`);
});
}
let vm = new Vue({
data: {
message: 'Hello, Vue!'
}
});
// 测试双向数据绑定
vm.message = 'Hello, World!'; // 控制台会打印出 "message changed from Hello, Vue! to Hello, World!"``
这个示例中,我们定义了一个 defineReactive
函数来将对象的属性转化为 getter/setter,Dep
类用于存储属性的依赖(即 Watcher 对象),Watcher
类用于观察数据的变化并在变化时执行回调函数。observe
函数用于遍历对象的所有属性并调用 defineReactive
进行转化。在 Vue 实例中,我们创建了一个 Watcher 对象来观察 message
属性的变化,并在其变化时打印出一条消息。最后,我们修改 vm.message
的值来测试双向数据绑定是否生效。
需要注意的是,这个示例仅仅是为了说明 Vue.js 双向数据绑定的基本原理,并没有包含 Vue.js 完整框架中的所有特性和优化。在实际的 Vue.js 框架中,还包括了模板编译、指令解析、虚拟 DOM、组件系统等更多复杂的功能。