对于响应式原理,我们先了解vue是一个MVVM结构的框架;也就是 数据层、视图层、数据-视图层;响应式的原理就是实现当数据更新时,视图层也要相应的更新,基于响应式原理我们可以使数据驱动视图的实现变得简单而高效
对于Vue2中的响应式处理来说,他基于js的object.defineProperty()方法的。它的原理主要是如下几步实现的:
1、数据劫持:在vue中,当你把一个普通js对象传给vue实例作为data选项时,vue将遍历次对象所有的属性,并使用Object.defineProperty()把这些属性全部转为getter/setter。这样,vue能够追踪到属性的变化,并在属性被访问和修改时执行相应的操作
2、依赖追踪:vue内部维护了一个依赖收集的系统,每个响应式对象都有一个对应的依赖集合,当数据被访问时,会把当前的Watcher(观察者)记录下来。这样,当数据发生变化时,依赖于这个数据的所有Watcher都会被通知,进而更新相应的视图。
3、派发更新:当响应式数据发生变化时,vue会遍历依赖集合,通知相关的Watcher更新视图
响应式是为了构建动态的,交互式的用户页面而设计的。如果没有响应式那么我们就需要手动去监听数据的变化然后更新视图,这样会导致性能上消耗增加,并且用户的体验也不会良好,而响应式使得页面能够在数据变化时实时更新,提供了更好的用户体验。用户可以看到页面的实时变化,而无需手动刷新页面。
对于响应式来说,它基于发布订阅模式和数据劫持来实现的,即:
1、发布-订阅者模式:vue使用发布-订阅者模式来实现数据变动时的通知和更新
2、数据劫持:vue通过Object.defineProperty对数据进行劫持
实现方法:
// 定义Dep类,用于收集依赖和通知更新
class Dep {
constructor() {
this.subscribers = [];
}
// 添加订阅者
addSubscriber(sub) {
if (sub && typeof sub.update === 'function') {
this.subscribers.push(sub);
}
}
// 发布更新
notify() {
this.subscribers.forEach(sub => sub.update());
}
}
// 定义Watcher类,用于订阅数据变化
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.vm[this.key]; // 触发 getter,收集依赖
Dep.target = null;
}
// 更新视图
update() {
this.cb.call(this.vm, this.vm[this.key]);
}
}
// 定义Observer类,用于将对象转为响应式对象
class Observer {
constructor(data) {
this.data = data;
this.walk(data);
}
// 遍历对象属性,转为响应式
walk(data) {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
});
}
// 定义响应式属性
defineReactive(obj, key, value) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addSubscriber(Dep.target);
}
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
dep.notify(); // 数据变化,通知更新
}
}
});
}
}
// Vue 类,用于创建 Vue 实例
class Vue {
constructor(options) {
this.options = options;
this._data = options.data;
// 数据响应化
new Observer(this._data);
// 代理 data 到 Vue 实例上
this.proxyData(this._data);
// 创建 Watcher 实例,观察数据变化
options.created && options.created.call(this);
}
// 代理 data 到 Vue 实例上
proxyData(data) {
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return this._data[key];
},
set(newValue) {
this._data[key] = newValue;
}
});
});
}
}
可以从以上代码中看出:
Dep
类用于收集依赖和通知更新,每个响应式数据都有一个对应的Dep
实例Observer
类用于将对象转为响应式对象,遍历对象属性,通过object.defineProperty转为响应式属性因此通过以上代码我们就实现了一个响应式原理了
在vue3中,响应式原理采用了基于ES6 Proxy对象的方式来实现,相较于vue2中基于Object.defineProperty的方式,使用Proxy对象能够更灵活地拦截对对象的各种操作,从而实现更加高效和强大的响应式系统
通过以上内容我们可以看出,Object.defineProperty()允许我们定义对象的属性,并且能够通过拦截访问和修改这些属性的行为,实现对属性的监控和响应。这使得当对象的属性发生变化时,能够触发相应的更新操作,但是我们要注意的是,它实现数据劫持不是进行数据代理,而是数据拦截的。就是说在数据被访问和修改时进行拦截,并把相应的时机触发更新操作,从而实现数据与视图的同步更新。
具体来说,在vue的响应式系统中,当你把一个普通javaScript对象传给vue实例作为data选项时,vue会遍历这个对象的所有属性,并使用Object.defineProperty()方法把这些属性全部转为getter/setter。这样一来,vue能够追踪到属性的变化,并在属性被访问和修改时执行相应的操作。
缺点
Object.defineProperty()
在 IE8 及更早版本的浏览器中不被支持,这限制了 Vue 在一些旧版本浏览器中的应用范围,需要额外的兼容处理只能监听对象属性:Object.defineProperty()
只能劫持对象的属性访问和修改操作,无法监听对象的新增属性和删除属性的操作。这导致在 Vue 中对于新增属性和删除属性的响应式处理需要额外的操作,例如需要使用 Vue.set()
或者 $set()
方法来添加新属性
无法监听数组变化:Object.defineProperty()
无法直接监听数组的变化,因为数组的变化通常包括了数组的元素的添加、删除和重新排序等操作,这些操作不会触发数组的属性变化,从而无法被Object.defineProperty()
拦截。在 Vue 中对于数组的响应式处理是通过重写数组的一系列方法来实现的,如 push()
、pop()
、shift()
、unshift()
等,这种处理方式也增加了一定的复杂性
Object.defineProperty()
可能会带来一定的性能开销。因为每个被劫持的属性都需要一个对应的 getter 和 setter 函数来进行拦截和更新操作,当属性较多时,可能会影响到整体性能对于Proxy
它是 ES6 中新增的一种代理机制,用于定义基本操作的自定义行为(例如属性查找、赋值、枚举、函数调用等)。它提供了一种强大而灵活的方式来监视并对对象的操作进行拦截和定制。因此在 Vue 3 中,我们可以使用Proxy
被用于实现数据的响应式处理
Proxy 的特点:
Object.defineProperty()
相比,Proxy 的性能通常更好,特别是在处理大规模数据和数组变化时。总的来说,Proxy
是一个强大而灵活的工具,能够对对象的操作进行拦截和定制,为我们提供了更好的数据处理和操作控制的能力。
综上所述,我们可以得出以下结论:
Object.defineProperty()
实现的,它具有一定的局限性和缺点,比如无法监听数组变化和兼容性问题。而在 Vue 3 中,采用了基于Proxy
的方式,提供了更灵活、高效的响应式系统。权衡利弊:Object.defineProperty()
和Proxy
各有优缺点,需要根据项目的具体需求和浏览器兼容性要求进行选择。Proxy
在性能和灵活性上有一定优势,但在一些旧版本浏览器中可能存在兼容性问题;而 Object.defineProperty()
则在兼容性较好的情况下实现了一定的数据劫持能力
两种实现方式的区别:
Proxy
提供了更加灵活和强大的拦截能力,可以拦截对象的更多操作,包括属性的读取、赋值、删除、枚举等,以及数组的操作如 push
、pop
、shift
、unshift
等。而 Object.defineProperty()
只能劫持对象的属性访问和修改操作,无法直接监听数组的变化等Proxy
在 ES6 中被引入,因此对于支持 ES6 的现代浏览器和环境来说,兼容性较好。但是在一些旧版本的浏览器中,如 IE11 及更早版本,Proxy
并不被支持。而 Object.defineProperty()
在较早的 ES5 中就已经存在,兼容性较好,但也存在一些兼容性问题,如无法监听数组变化和对新增属性的处理等Proxy
相对于 Object.defineProperty()
在性能上可能会有所提升,特别是在处理大规模数据和数组变化时。Proxy
的拦截器函数在实现上更为底层,因此可能更加高效。而 Object.defineProperty()
的性能开销相对较大,特别是在属性较多时可能会影响到整体性能Proxy
是通过创建一个目标对象的代理对象来实现监听的,可以直接监听整个对象,包括对象的属性新增、删除和修改等操作。而Object.defineProperty()
是针对对象的每个属性进行劫持,无法直接监听对象的整体变化总的来说,深入理解vue的响应式原理和不同的实现方式,有助于我们更加深入地理解前端框架的底层原理,并能够更加灵活地应对各种开发场景和需求