内容简介:本文介绍了两种监听对象某个属性的变化的思路, 分别是利用私有属性以及利用观察者模式,建议有经验的读者直接阅读最后的实现部分 :)上一篇文章我们谈到了如何监听对象的变化。下面我们来探究如何用
本文介绍了两种监听对象某个属性的变化的思路, 分别是利用私有属性以及利用观察者模式,建议有经验的读者直接阅读最后的实现部分 :)
前景回顾
上一篇文章我们谈到了如何监听对象的变化。
下面我们来探究如何用 $watch
方法中的callback来替换 console.warn(newVal, oldVal)
, 以及如何只监听对象的某个属性的变化。
另外本文只讨论 Vue.prototype.$watch
keypath的写法,即 this.$watch('a.b.c', () => {});
。
function proxy(obj) {
const handler = {
get(target, prop) {
try {
return new Proxy(target[prop], handler);
} catch (error) {
return target[prop];
}
},
set(target, prop, newVal) {
const oldVal = target[prop];
if (oldVal !== newVal) {
// 如何替换这个强耦合的函数
console.warn(newVal, oldVal);
}
target[prop] = newVal;
return true;
},
};
return new Proxy(obj, handler);
}
const obj = proxy({
a: 'a',
b: 'b',
c: 'c',
});
// 以及如何做到当obj.a改变时只触发第一个callback
$watch(obj, 'a', (val, oldVal) => {
console.warn('watch obj.a: ', val, oldVal);
});
$watch(obj, 'b', (val, oldVal) => {
console.warn('watch obj.b: ', val, oldVal);
});
复制代码
思路
关于私有属性
有个十分简单的思路: 把callback和要监听的属性值, 作为被监听对象某一层级的私有属性注入
// 监听obj.a和obj.b
const obj = {
a: 'a',
b: 'b',
c: 'c',
// 因为我们需要监听两个属性,所以需要使用集合
__waters__: [{
key: 'a',
cb: () => {},
}, {
key: 'b',
cb: () => {},
}],
};
// 对于多层级的被监听对象, __watchers__挂载在不同的层级下
const obj = {
o: {
name: 'obj',
__watchers__: [{
key: 'name',
cb: () => {},
}],
},
odeep: {
path: {
name: 'obj deep',
__watchers__: [{
key: 'name',
cb: () => {},
}],
},
},
};
复制代码
关于观察者模式
先让我们看看维基百科是怎么说的:
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
也就是说subject用来维护依赖列表, 每个依赖都是一个observer。当依赖列表中的某一项发生了变化,就自动通知subject自身状态的变更。
function proxy(obj) {
const handler = {
get(target, prop) {
// 设置observer(依赖)
return target[prop]; // 不递归监听
},
set(target, prop, newVal) {
const val = target[prop];
if (newVal !== val) {
target[prop] = newVal;
// observer通知自身状态的改变, 即调用callback
}
return true;
},
};
return new Proxy(obj, handler);
}
复制代码
但是callback在 $watch
函数中, 如何传递给observer, 并在被监听对象变化时调用呢?
我们可以利用一个全局变量,在访问变量的时候设置为 $watch
函数的callback, 访问结束后置空。
let DepTarget = null;
function $watch(obj, path, cb) {
DepTarget = cb;
// 访问obj,自动调用get方法实现依赖注入
DepTarget = null;
}
class Dep {
constructor() {
this.subs = new Set();
}
add(sub) {
this.subs.add(sub);
}
notify() {
this.subs.forEach((sub) => {
// sub需要存储oldVal和newVal, 当且仅当oldVal不等于newVal时调用callback
sub.update();
});
}
}
复制代码
实现
关于私有属性
对于这个思路而言,我们只需要找出需要监听的属性的上一层级, 不难抽象出下面的函数:
function parseParentPath(path, obj) {
if (/[^\w.$]/.test(path)) {
return {};
}
let segs = path.split('.');
// 监听属性的上一层,所以是length - 1
segs = segs.slice(0, segs.length - 1);
for (let i = 0; i < segs.length; i += 1) {
if (!obj) {
return {};
}
obj = obj[segs[i]];
}
return obj;
}
复制代码
那么 $watch
也不难写出来了
function $watch(obj, path, cb) {
const parent = parseParentPath(path, obj);
// 限于篇幅,边界判断还请自行脑补 :)
const segs = path.split('.');
const key = segs[segs.length - 1];
if (!parent.__watchers__) {
Object.defineProperty(parent, '__watchers__', {
value: [],
configurable: true,
});
}
parent.__watchers__.push({ key, cb });
}
const handler = {
get(target, prop) {
try {
return new Proxy(target[prop], handler);
} catch (error) {
return target[prop];
}
},
set(target, prop, newVal) {
const oldVal = target[prop];
const { __watchers__ } = target;
if (__watchers__) {
const current = __watchers__.find(e => e.key === prop);
if (oldVal !== newVal && current && typeof current.cb === 'function') {
current.cb(newVal, oldVal);
}
}
target[prop] = newVal;
return true;
},
};
obj = new Proxy(obj, handler);
复制代码
好了,让我们来试试吧!
let obj = {
b: true,
o: { name: 'obj', age: 18 },
a: ['a', 'b', 'c'],
odeep: {
path: {
name: 'obj deep',
value: [],
},
},
};
$watch(obj, 'b', (newVal, oldVal) => {
console.error('watch b: ', newVal, oldVal);
});
$watch(obj, 'o.name', (newVal, oldVal) => {
console.error('watch o.name: ', newVal, oldVal);
});
$watch(obj, 'odeep.path.name', (newVal, oldVal) => {
console.error('watch odeep.path.name: ', newVal, oldVal);
});
setTimeout(() => {
// 当然不会有什么问题
obj.o.name = 'new obj';
obj.b = false;
obj.odeep.path.name = 'new obj deep';
}, 1000);
复制代码
但是这样的写法存在一些局限性
$watch(obj, 'odeep', (newVal, oldVal) => {
console.error('watch odeep: ', newVal, oldVal);
}, { deep: true });
setTimeout(() => {
// 对于{ deep: true }, 需要在对象的每个层级添加__watchers__属性,显然不太合适
obj.odeep.path.name = 'new obj deep';
}, 1000);
复制代码
关于观察者模式
我们利用sub来存储oldVal和newVal, 并将 $watch
的逻辑写入sub的 get
方法中
class Sub {
constructor(obj, path, cb) {
this.obj = obj;
this.path = path;
this.cb = cb;
this.value = this.get();
}
get() {
// 因为Dep的add方法传参为sub, 因此全局变量设置为当前sub
DepTarget = this;
// 访问obj
const value = parsePath(this.path)(this.obj);
DepTarget = null;
return value;
}
update() {
const value = this.get();
if (this.value !== value) {
const oldVal = this.value;
this.value = value;
this.cb.call(this.obj, value, oldVal);
}
}
}
复制代码
下面是完整的例子:
let DepTarget = null;
class Sub {
constructor(obj, path, cb) {
this.obj = obj;
this.path = path;
this.cb = cb;
this.value = this.get();
}
get() {
DepTarget = this;
// 访问obj
const value = parsePath(this.path)(this.obj);
DepTarget = null;
return value;
}
update() {
const value = this.get();
if (this.value !== value) {
const oldVal = this.value;
this.value = value;
this.cb.call(this.obj, value, oldVal);
}
}
}
class Dep {
constructor() {
this.subs = new Set();
}
add(sub) {
this.subs.add(sub);
}
notify() {
this.subs.forEach((sub) => {
// sub需要存储oldVal和newVal, 当且仅当oldVal不等于newVal时调用callback
sub.update();
});
}
}
function proxy(obj) {
const dep = new Dep();
const handler = {
get(target, prop) {
if (DepTarget) {
dep.add(DepTarget);
}
// 不递归监听
return target[prop];
},
set(target, prop, newVal) {
const val = target[prop];
if (newVal !== val) {
target[prop] = newVal;
dep.notify();
}
return true;
},
};
return new Proxy(obj, handler);
}
function parsePath(path) {
if (/[^\w.$]/.test(path)) {
return;
}
var segs = path.split('.');
return function(obj) {
for (let i = 0; i < segs.length; i += 1) {
if (!obj) {
return;
}
obj = obj[segs[i]];
}
return obj;
};
}
const obj = proxy({
a: 'a',
b: 'b',
o: { name: 'a', age: 18 },
arr: [1, 2],
});
function $watch(obj, path, cb) {
return new Sub(obj, path, cb);
}
$watch(obj, 'a', (val, newVal) => {
console.warn('watch a: ', val, newVal);
});
$watch(obj, 'b', (val, newVal) => {
console.warn('watch b: ', val, newVal);
});
$watch(obj, 'o.age', (val, newVal) => {
console.warn('watch o.age: ', val, newVal);
});
$watch(obj, 'arr', (val, newVal) => {
console.warn('watch arr: ', val, newVal);
});
setTimeout(() => {
obj.b = 'new b';
obj.o.age -= 1;
// vue会打印相同的值, 你会发现我们的实现不会打印
obj.arr.push(3);
obj.arr = [3];
}, 1000);
复制代码
细心的你应该发现了,我们没有实现 Vue.prototype.$watch
常用的 { deep: true }
参数, 限于篇幅, 笔者决定还是放在下一篇文章介绍 :)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 中的计算属性,方法,监听器
- Vue使用watch监听一个对象中的属性
- Vue2.0解决watch对象属性变化监听不到问题
- Laravel 给生产环境添加监听事件 - SQL日志监听
- Flutter事件监听
- 初始化监听端口
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
python 3标准库
道格·赫尔曼 / 机械工业出版社 / 2018-10 / 199
在本书中,你会看到用来处理文本、数据类型、算法、数学计算、文件系统、网络通信、Internet、XML、Email、加密、并发性、运行时和语言服务等各个方面的实用代码和解决方案。在内容安排上,每一节都会全面介绍一个模块,并提供一些很有价值的补充资源链接,这使得本书成为一本理想的Python标准库参考手册。一起来看看 《python 3标准库》 这本书的介绍吧!