Vue源码学习笔记之实现双向数据绑定(一)--- 发布订阅模式
程序员文章站
2022-05-21 13:48:53
...
首先看下vue官方文档中对于响应式原理说明的配图:
Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式来实现双向数据绑定的。当把一个普通 Js 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,通过用Object.defineProperty 设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,通过setter在数据变更的时候通知订阅者更新视图。
发布—订阅模式
最近又看了一遍《javaScript设计模式与开发实践》中的发布订阅模式,正好结合Vue源码做个总结。
概念:发布订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
优点:
1. 时间上的解耦:发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。
2. 对象之间的解耦:发布-订阅模式让两个对象松耦合地联系在一起,相互通信不再需要显示的调用另外一个对象的接口。
简单的发布-订阅模式的实现:
1. 首先要指定好谁充当发布者;
2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数;
发布订阅模式的通用实现:
var event = {
clientList: [],
listen: function (key, fn) {
// 初始化特定key属性的缓存队列
if (!this.clientList[key]) {
this.clientList[key] = []
}
// 订阅的消息添加进缓存列表
this.clientList[key].push(fn)
},
trigger: function () {
var key = Array.prototype.shift.call(arguments)
var fns = this.clientList[key]
// 如果没有绑定对应的消息
if (!fns || fns.length === 0) {
return false
}
for (var i = 0, fn; fn = fns[i++]; ){
fn.apply(this, arguments) // arguments 是 trigger 时带上的参数
}
},
remove: function (key, fn) {
var fns = this.clientList[key]
// 如果key对应的消息没有被人订阅,则直接返回
if (!fns) {
return false
}
// 如果没有传入具体的回调函数,则表示需要取消key对应消息的所有订阅
if (!fn) {
fns && (fns.length = 0)
} else {
// 反向遍历订阅的回调函数列表
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l]
if (_fn === fn) { // 删除订阅者的回调函数
fns.splice(l, 1)
}
}
}
}
}
// installEvent函数:给所有的对象都动态添加发布—订阅功能
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ]
}
}
// 测试:
var salesOffices = {}
installEvent(salesOffices)
salesOffices.listen('squereMeter88', fn1 = function(price) {
console.log('价格=' + price)
})
salesOffices.listen('squereMeter100', fn2 = function(price) {
console.log('价格=' + price)
})
salesOffices.remove('squereMeter88', fn1)
salesOffices.trigger('squereMeter100', 200)
全局的发布-订阅对象的实现:
订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会发送给哪些订阅者,Event 作为一个类似“中介者”的角色,把订阅者和发布者联系起来。
var Event = (function(){
var clientList = {},
listen,
trigger,
remove;
listen = function( key, fn ){
if ( !clientList[ key ] ){
clientList[ key ] = [];
}
clientList[ key ].push( fn );
};
trigger = function(){
var key = Array.prototype.shift.call( arguments ),
fns = clientList[ key ];
if ( !fns || fns.length === 0 ){
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments );
}
};
remove = function( key, fn ){
var fns = clientList[ key ];
if ( !fns ){
return false;
}
if ( !fn ){
fns && ( fns.length = 0 );
} else {
for ( var l = fns.length - 1; l >=0; l-- ){
var _fn = fns[ l ];
if(_fn === fn){
fns.splice( l, 1 );
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
发布订阅模式实现EventEmitter:
通过 on 方法注册事件,trigger 方法触发事件,once 方法注册只触发一次的事件, off 方法注销事件。
class EventEmitter {
constructor() {
this.subs = {}
}
on(event, cb) {
(this.subs[event] || this.subs[event] = []).push(cb)
}
trigger(event, ...args) {
this.subs[event] && this.subs[event].forEach(cb => {
cb(...args)
})
}
once(event, cbOnce) {
let cb = (...args) => {
cbOnce(...args)
this.off(event, cbOnce)
}
this.on(event, cb)
}
off(event, cbOff) {
if (this.subs[event]) {
let index = this.subs[event].findIndex(cb => cb === cbOff)
this.subs[event].splice(index, 1)
if (!this.subs[event].length) delete this.subs[event]
}
}
}