前言
使用过vue的开发小伙伴,面试可能经常会问到vue如何实现双向绑定的?
通常回答就是,使用Object.defineProperty来劫持属性的setter/getter,从而在属性变化时触发对应的监听逻辑,vue的这种双向绑定的实现,本质上属于一种发布/订阅者模式(观察者模式)。
本文是为了更加清晰的了解观察者模式,以及其实现原理。
一.什么是发布/订阅模式?
发布/订阅模式,也被称为观察者模式。它定义了一种一对多的依赖关系,当发布者对象的一个状态发生变化时,所有订阅它的对象,都将获得通知。
发布/订阅模式中,通常有两个角色,发布者和订阅者
发布者指的是包含特定状态的对象,当状态发生变化时,会发送通知给订阅者
订阅者指的的订阅了发布者状态的一个对象,包含一个对发布者状态变化的监听回调方法
一个发布者可以对应多个订阅者
二.如何实现一个简易的发布/订阅模式
第一步,实现是一个发布者的对象
发布者被命名为一个Observer的类
包含的属性有: name: 当前发布者的名称 state: 发布者的状态
包含的方法有: attach: 添加订阅者 setState: 用来修改对应状态并且通知给订阅者
代码实现:
class Observer {
constructor(name, state) {
this.name = name
this.state = state
this.subjects = []
}
// 给当前发布者添加订阅者
attach(subject) {
this.subjects.push(subject)
}
// 修改发布者的状态
setState(newState) {
let prevState = this.state
this.state = newState
this.subjects.forEach(item => item.watch(newState, prevState))
}
}
第二步,实现一个订阅者
包含的属性有: name: 订阅者的名称 target: 订阅的发布者实例
包含的方法有: watch: 接受发布者通知的回调方法
代码实现:
class Subject {
constructor(name, target) {
this.name = name
this.target = target
}
// 发布者状态变化是,订阅者触发的回调方法
watch(newState, prevState) {
console.log(`${this.name}监听到${this.target.name},从${prevState}变成了${newState}`)
}
}
第三步,如何调用
如何启用我们前面实现的发布/订阅者模式?
- 创建一个发布者实例和多个订阅者实例
- 将订阅者的实例通过attach方法绑定到发布者上
- 使用setState方法修改发布者状态,此时会触发订阅者的watch方法
代码实现:
const observerInstance = new Observer('发布者', '很开心')
const subjectInstance1 = new Subject('订阅者1', observerInstance)
const subjectInstance2 = new Subject('订阅者2', observerInstance)
observerInstance.attach(subjectInstance1)
observerInstance.attach(subjectInstance2)
observerInstance.setState('不开心')
// 打印结果:
// 订阅者1监听到发布者,从很开心变成了不开心
// 订阅者2监听到发布者,从很开心变成了不开心
以上例子中,我们就实现了一个简单的发布/订阅者模式。
三、使用发布/订阅者模式实现一个vue中input双向绑定
vue的实现逻辑:
vue里的双向绑定,是通过object.defineProperty监听属性的变化,从而属性值改变时会触发dom元素的值来改变
// 初始化状态
let state = {
value: 0
}
// 通过`defineProperty`来绑定状态和input元素
function initWatch(state, key, element) {
Object.defineProperty(state, key, {
set: value => {
element.value = value
},
get: () => {
return element.value
}
})
}
// 执行state和元素的绑定
let element = document.querySelector('.input')
initWatch(state, 'value', element)
自己使用发布/订阅模式来实现
我们自己可以先实现一个发布/订阅者,然后通过发布者的状态改动,来通知订阅者修改dom元素的值
// 发布者
class Observer {
constructor(state) {
this.state = state
this.observerArr = []
}
// 给当前发布者添加订阅者
attach(subjectTarget) {
this.observerArr.push(subjectTarget)
}
// 触发发布者状态变化
setState(newState) {
let prevState = this.state
this.state = newState
this.observerArr.forEach(subjectTarget => subjectTarget.watch(newState, prevState))
}
}
// 订阅者
class Subject {
constructor(element, target) {
this.element = element
this.target = target
}
watch(newState, prevState) {
this.element.value = newState.value
}
}
// 调用逻辑
let state = {
value: 0
}
let element = document.querySelector('.input')
const observerInstance = new Observer(state)
const subjectInstance1 = new Subject(element, observerInstance)
// 给发布者添加订阅着实例
observerInstance.attach(subjectInstance1)
// 修改状态
observerInstance.setState({
value: 2
})
以上代码实现思路仅供参考,还有大量的异常情况和真实业务场景未处理。不过这种发布/订阅者模式的思路,可以引申到很多的场景上。
总结
- 发布/订阅者模式,是一种一对多的关系,即一个发布者对应多个订阅者。
- 发布者通常情况下会包含多种状态,当发布者的某个状态发生变化时,会通知给订阅了该状态个状态的订阅者,触发订阅者的回调方法
- vue中类似使用了发布/订阅者模式的功能有:双向绑定,
state监听,$emit和$on等常见场景