观察者(Observer)模式的定义是指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,目前主流的对数据监听的方案是基于观察者模式进行的。在知名前端框架Vue中,Vue2实现观察者模式的一个核心方法则是Javascript的Object。defineProperty方法,进一步实现数据的监听。
Object.defineProperty方法传入三个参数,第一个参数为要添加或改变属性的对象,第二个参数为属性的名称,第三个参数为要定义或修改属性的函数,其中最为重要的是get函数和set函数,其中get函数用于表示该属性被访问时的回调函数,set函数用于表示该属性被修改时的回调函数。
let obj = {
key1: 1,
key2: 2,
key3: 3,
};
Object.defineProperty(obj, "key1", {
get() {
console.log(`obj对象的key1属性被访问了`);
},
set(newVal) {
if (newVal !== val) {
val = newVal;
console.log(`obj对象的key1属性被更新了`);
update(); //此处填写更新后的操作
}
},
});
在Vue3版本,对于复杂类型数据的监听主要采用Object.Proxy方法来实现观察者模式,当使用watch函数时,Vue3会创建一个Proxy对象来包裹被观察的对象。
let p = new Proxy(target, {
get(target, property, receiver) {
console.log(`${target}的${property}属性被访问了`)
},
set(target, property, value, receiver) {
console.log(`${target}的${property}属性被设置为了${value}`)
}
});
在Vue中使用watch能够实现对于数据的监听功能,但是有些场景下,仅通过watch函数并不能完成我们想要的功能。例如如果想在一个函数内部实现对一个变量的监听,当变量未发生变化时,程序要求在当前位置阻塞,直到变量发生变化时,程序才能往下进行,这种情景下,使用watch函数并不能满足要求,我们就需要自己写一个监听函数来实现我们当前的场景。
为了达到在函数内部阻塞的要求,需要使用Promise对象,而为了达到监听的效果,可以使用setInterval启用定时器来循环监视变量的值是否变化。
const watchPromise = (variable, oldVal) => {
return new Promise((resolve, reject) => {
try {
const intervalId = setInterval(() => {
if (variable !== oldVal) {
//监听对象的值变化后,取消定时器,并且将目前的值resolve出去
clearInterval(intervalId);
resolve(variable);
}
}, 100);
} catch (err) {
reject(err);
}
});
};
该函数传入两个参数,第一个参数为要监听的变量,第二个参数为变量的当前值。在函数内部启用了Promise和定时器,每间隔100ms对变量进行一次监听(间隔时间可以自定义),当监听到对象的值变化后,将定时器取消,并将修改后的值resolve出去,使外部函数接收。
但是上述函数有个问题,就是只支持基本数据类型的监听,如果要监听引用类型,如对象、数组等,需要将函数改造一下。loadsh库提供了一个isEqual方法判断两个变量是否相等,该方法不仅支持基本类型,也支持引用类型的判断因此将上述监听函数进行一下改造,使用isEqual来判断变量前后的变化。
import _ from "loadsh"
const watchPromise = (variable, oldVal) => {
return new Promise((resolve, reject) => {
try {
const intervalId = setInterval(() => {
if (!_.isEqual(variable, oldVal)) {
//监听对象的值变化后,取消定时器,并且将目前的值resolve出去
clearInterval(intervalId);
resolve(variable);
}
}, 100);
} catch (err) {
reject(err);
}
});
};
监听函数写完后,下面来看一个实际例子。在项目中,经常会遇到确认框来确认用户的操作,确认框有两个状态,分别是"确认"和"取消",当一个函数内部需要打开确认框来确认函数是否执行的时候,就需要采用上述的监听函数。
let confirmDialogVisible = false;
let confirmTag = ""; //确认框状态,点击"确认"为true,点击"取消"或关闭窗体为false,什么都不操作为空字符串
const showConfirmDialog = () => {
confirmDialogVisible = true;
confirmTag = "";
};
//实际运行的函数
const run =async () => {
showConfirmDialog() //打开对话框
let canGoNext=""
try{
const res= await watchPromise(confirmTag,"") //监听confirmTag状态变为true或false(用户操作确认框)
confirmTag="" //重置确认框状态
canGoNext = res //将确认框状态返回
}catch(err){
return err
}
if(canGoNext){
nextFunc() //用户点击确认后要执行的函数,点击取消或不操作不进入该函数
}
};
其中confirmTag表示确认框状态,点击"确认"为true,点击"取消"或关闭窗体为false,什么都不操作为空字符串。run为实际运行的函数,打开对话框后调用watchPromise方法,对confirmTag变量进行持续监听,当弹出确认框并且用户进行确认后(即confirmTag状态变为true),才能执行后续的代码。