实现vue双向绑定
Vue双向绑定的实现原理
Vue的双向绑定主要通过数据劫持结合发布者-订阅者模式实现。核心是Object.defineProperty或ES6的Proxy来劫持数据属性的getter和setter。
使用Object.defineProperty实现
通过Object.defineProperty劫持对象属性的读写操作,在属性被访问或修改时触发相应的回调函数。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get value:', val);
return val;
},
set: function(newVal) {
console.log('set value:', newVal);
val = newVal;
}
});
}
const data = {};
defineReactive(data, 'text', 'hello world');
data.text; // 触发get
data.text = 'new value'; // 触发set
依赖收集与发布订阅
Vue通过Dep类收集依赖(Watcher实例),在数据变化时通知所有订阅者。

class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Watcher {
constructor() {
Dep.target = this;
}
update() {
console.log('视图更新');
}
}
Dep.target = null;
完整实现示例
结合数据劫持和依赖收集的简化实现:
function observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
function defineReactive(obj, key, val) {
const dep = new Dep();
observe(val); // 递归处理嵌套对象
Object.defineProperty(obj, key, {
get: function() {
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set: function(newVal) {
if (val === newVal) return;
val = newVal;
dep.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data);
new Watcher(); // 模拟渲染过程
}
}
使用Proxy实现
ES6的Proxy提供了更强大的拦截能力,可以解决Object.defineProperty的一些限制:

function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return Reflect.get(target, key);
},
set(target, key, value) {
Reflect.set(target, key, value);
trigger(target, key);
}
});
}
function track(target, key) {
// 收集依赖
}
function trigger(target, key) {
// 触发更新
}
模板编译过程
Vue模板编译将模板转换为渲染函数,过程中会解析指令和插值表达式,创建对应的Watcher:
- 解析模板生成AST
- 优化AST(标记静态节点)
- 生成渲染函数代码
- 执行渲染函数触发getter,完成依赖收集
数组的特殊处理
Object.defineProperty无法检测数组变化,Vue通过重写数组方法实现响应式:
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
const result = original.apply(this, args);
dep.notify(); // 通知更新
return result;
};
});






