经历过前端面试的同学,大部分被问过:Vue双向数据绑定原理。
回答:双向数据绑定的原理,vue2中采用“数据劫持”结合“发布者-订阅者”模式的方式,通过“Object.defineProperty()”方法来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。vue3 则通过Proxy代理实现。
这个回答相信你早已烂熟于心,如果你只是记着这段话没真正理解其中意思,面试官结合设计模式换一个问法,很多同学就会翻车呢。
vue双向数据绑定原理图
01设计模式
作为工程师的我们,对于设计模式还是有必要掌握的,什么是设计模式呢?
设计模式是软件工程中用于解决特定问题的一系列最佳实践。设计模式不是完整的设计解决方案,而是在特定上下文中解决特定问题的模板。
简单点说,设计模式是以前的程序员在开发解决特定问题时,积累的一套优雅的代码写法。
设计模式可以分为三大类:创建型模式、结构型模式和行为型模式。
创建型模式(Creational Patterns)
创建型模式关注对象的创建过程,隐藏创建逻辑,并且通过使用对象的接口来控制对象的创建。
典型的有单例模式、工厂模式、原型模式
结构型模式(Structural Patterns)
结构型模式关注对象的组合,通过组合简单对象形成复杂的结构。
典型的有代理模式、装饰器模式、适配器模式、组合模式
行为型模式(Behavioral Patterns)
行为型模式专注于对象间的通信,即对象如何相互协作以完成复杂的任务。
典型的有策略模式、观察者模式、迭代器模式
设计模式是软件开发中的一种重要工具,它们帮助开发者编写可维护、可扩展且容易理解的代码。每种设计模式都有其特定的应用场景和优缺点,选择合适的模式可以提高软件设计的质量。
02、观察者模式
阅读vue源码不难发现,观察者模式的身影。观察者模式(Observer Pattern)是一种行为设计模式,它研究的是对象间的关系。它定义了对象间的一种一对多的依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。在JavaScript中,观察者模式可以用于实现事件监听和通知机制。
以下是使用JavaScript实现观察者模式的一个简单示例:
1. 定义观察者接口
class Observer {
update() {}
}
2. 定义具体观察者
class ConcreteObserver extends Observer {
constructor(name) {
super();
this.name = name;
}
update(state) {
console.log(`${this.name} received state: ${state}`);
}
}
3. 定义主题接口
class Subject {
constructor() {
this._observers = [];
}
attach(observer) {
this._observers.push(observer);
}
detach(observer) {
this._observers = this._observers.filter(obs => obs !== observer);
}
notify(state) {
this._observers.forEach(observer => observer.update(state));
}
}
4. 定义具体主题
class ConcreteSubject extends Subject {
constructor(state) {
super();
this._state = state;
}
get state() {
return this._state;
}
set state(value) {
this._state = value;
this.notify(this._state);
}
}
5. 使用观察者模式
// 创建具体主题
const subject = new ConcreteSubject();
// 创建具体观察者
const observerA = new ConcreteObserver('Observer A');
const observerB = new ConcreteObserver('Observer B');
// 注册观察者
subject.attach(observerA);
subject.attach(observerB);
// 改变主题状态,观察者将收到通知
subject.state = 'active';
// 注销一个观察者
subject.detach(observerA);
// 再次改变主题状态,只有Observer B会收到通知
subject.state = 'inactive';
在这个示例中,ConcreteSubject 类代表被观察的主题,它持有一个状态并在状态改变时通知所有注册的观察者。ConcreteObserver 类代表观察者,它们实现了 Observer 接口的 update 方法来响应状态变化。
JavaScript 中的观察者模式通常与事件监听机制紧密相关,实际上,JavaScript 的 EventTarget 和 addEventListener、removeEventListener、dispatchEvent 等方法已经提供了观察者模式的实现。在实际开发中,你可能会使用这些内置方法来实现事件驱动的交互,而不是从头开始实现观察者模式。
03、发布订阅者模式
Vue中也使用发布订阅者模式实现双向数据绑定原理,发布订阅者模式(Publish-Subscribe Pattern),又称为观察者模式的一种变体,它允许对象(订阅者)订阅消息或事件,并在这些消息或事件发生时得到通知。这种模式解耦了事件的发送者和接收者,使得发送者不必知道谁是接收者,接收者也不必知道谁是发送者。
在发布订阅者模式中,有三个主要的组件:
-
主题(Topic):一个消息的分类,订阅者可以订阅一个或多个主题。
-
发布者(Publisher):创建消息并发布到特定主题的对象。
-
订阅者(Subscriber):对特定主题感兴趣的对象,当主题有消息发布时,订阅者会收到通知。
以下是使用JavaScript实现发布订阅者模式的一个示例:
1. 创建发布订阅者系统
class EventSystem {
constructor() {
this.events = {};
}
subscribe(topic, callback) {
if (!this.events[topic]) {
this.events[topic] = [];
}
this.events[topic].push(callback);
}
unsubscribe(topic, callback) {
if (!this.events[topic]) {
return;
}
this.events[topic] = this.events[topic].filter(cb => cb !== callback);
}
publish(topic, data) {
if (this.events[topic]) {
this.events[topic].forEach(callback => callback(data));
}
}
}
2. 使用发布订阅者系统
const eventSystem = new EventSystem();
// 定义订阅者
function subscriberA(data) {
console.log('Subscriber A received:', data);
}
function subscriberB(data) {
console.log('Subscriber B received:', data);
}
// 订阅主题
eventSystem.subscribe('message', subscriberA);
eventSystem.subscribe('message', subscriberB);
// 发布消息
eventSystem.publish('message', 'Hello, subscribers!');
// 取消订阅
eventSystem.unsubscribe('message', subscriberA);
// 再次发布消息,只有subscriberB会收到
eventSystem.publish('message', 'Hello again, remaining subscribers!');
在这个示例中,EventSystem 类提供了订阅、取消订阅和发布消息的功能。订阅者通过 subscribe 方法注册到特定的主题,当 publish 方法被调用时,所有订阅了该主题的订阅者都会收到消息。
发布订阅者模式非常适合于实现松耦合的系统,其中组件之间的交互不需要直接依赖对方。这种模式在事件驱动的架构中非常常见,例如在Web开发中的事件监听、WebSocket通信、消息队列等场景。
04、发布订阅者和观察者模式区别
观察者模式是由具体目标调度,比如当事件触发,就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。
05、总结
文章通过"vue双向数据绑定原理"面试题,引入设计模式相关知识。详细了设计模式中的观察者模式和其变体发布订阅模式,并分析了两者区别。
目前整理的精典设计模式有23种,分别应用于不同场景,解决特定问题,在开发中有意识积累、运行,能提高代码质量,在代码可读性,可维护性方面更上层楼。