一句话解释
他定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化的时候,所有依赖于他的对象都将得到通知
class Observer {
/**
* 三件事
* 提供订阅功能
* 提供触发功能
* 提供取消订阅功能
*/
obj = {};
on(subscriberName, callback) {
// 进行一个订阅,将所有传入的回调函数放到数组中去
if (!this.obj[subscriberName]) {
this.obj[subscriberName] = [callback];
} else {
this.obj[subscriberName].push(callback);
}
}
trigger(subscriberName) {
// 触发时将所有相关的函数运行一遍
const subscribers = this.obj[subscriberName] || [];
subscribers.forEach((sub) => sub());
}
remove(subscriberName, subscriber) {
const subscribers = this.obj[subscriberName] || [];
const index = subscribers.findIndex((el) => el === subscriber);
if (index > -1) {
subscribers.splice(index, 1);
}
}
}
// 创建Observer的实例
const bookStore = new Observer();
//定义一个回调函数
const listener = () => {
console.log("用户1要去买书");
};
//绑定回调函数
bookStore.on("book", listener);
//其他实体的绑定
bookStore.on("book", () => {
console.log("用户2要去买书");
});
//触发
bookStore.trigger("book");
//用户1要去买书
//用户2要去买书
//移除订阅
bookStore.remove("book", listener);
bookStore.trigger("book");
//用户2要去买书
vue的响应式使用发布订阅模式
首先vue想实现的功能是:
- 某些数据改变的时候,使用该数据的函数全部自动运行一遍
这样的话,数据变化,就给dom树上的结点重新赋值,页面显示的数据就会自动更新
重写属性的get和set函数:
- 所谓数据变化,就是发生了重新赋值,而我们在给函数重新赋值的时候实际上调用了set函数,
- 所以只需要在set函数里顺便运行所有相关的函数就行了
现在的问题是: 我们如何让set函数知道有这些相关函数需要运行
相关函数收集
- 我们可以使用get函数,考虑到每一个使用该属性的相关函数都至少会调用get函数,所以我们可以在它调用get函数的时候进行收集,也就是把这个函数的名字存起来,放进一个数组
- 等到数据变化的时候对数组进行遍历运行,也就是在set函数中运行所有相关函数就行了
现在问题就只有一个:
- 我们怎么让get函数知道谁在调用它好让它进行收集
- 可以使用一个全局变量,在调用某个函数之前先把全局变量的值设置为这个函数名
- 接下来运行函数,在函数运行过程中,函数中用到该属性的时候会调用get函数,其get函数这时候访问全局变量就可以得到是谁在调用它了
- 这时候在get里进行函数收集就大功告成
- 运行函数结束之后要将全局变量置为空
那么基于Object.defineProperty的响应式就得以实现
以下是observer函数的实现
用于观察对象的所有属性
/**
* 观察某个对象的所有属性
* @param {Object} obj
*/
function observe(obj) {
for (const key in obj) {
let internalValue = obj[key];
let funcs = [];
Object.defineProperty(obj, key, {
get: function () {
// 依赖收集,记录:是哪个函数在用我
if (window.__func && !funcs.includes(window.__func)) {
funcs.push(window.__func);
}
return internalValue;
},
set: function (val) {
internalValue = val;
// 派发更新,运行:执行用我的函数
for (var i = 0; i < funcs.length; i++) {
funcs[i]();
}
},
});
}
}
function autorun(fn) {
window.__func = fn;
fn();
window.__func = null;
}
这是调用的示例
var user = {
name: '杨柳',
birth: '2002-5-7',
};
//调用obsever进行观察
observe(user); // 观察
// 显示姓氏
function showFirstName() {
document.querySelector('#firstName').textContent = '姓:' + user.name[0];
}
// 显示名字
function showLastName() {
document.querySelector('#lastName').textContent = '名:' + user.name.slice(1);
}
// autorun里面改变全局变量的值,方便get函数进行相关函数收集
autorun(showFirstName);
autorun(showLastName);