内容简介:本文介绍了两种监听对象某个属性的变化的思路, 分别是利用私有属性以及利用观察者模式,建议有经验的读者直接阅读最后的实现部分 :)上一篇文章我们谈到了如何监听对象的变化。下面我们来探究如何用
本文介绍了两种监听对象某个属性的变化的思路, 分别是利用私有属性以及利用观察者模式,建议有经验的读者直接阅读最后的实现部分 :)
前景回顾
上一篇文章我们谈到了如何监听对象的变化。
下面我们来探究如何用 $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事件监听
- 初始化监听端口
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
最优化导论
Edwin K. P. Chong、Stanislaw H. Zak / 孙志强、白圣建、郑永斌、刘伟 / 电子工业出版社 / 2015-10 / 89.00
本书是一本关于最优化技术的入门教材,全书共分为四部分。第一部分是预备知识。第二部分主要介绍无约束的优化问题,并介绍线性方程的求解方法、神经网络方法和全局搜索方法。第三部分介绍线性优化问题,包括线性优化问题的模型、单纯形法、对偶理论以及一些非单纯形法,简单介绍了整数线性优化问题。第四部分介绍有约束非线性优化问题,包括纯等式约束下和不等式约束下的优化问题的最优性条件、凸优化问题、有约束非线性优化问题的......一起来看看 《最优化导论》 这本书的介绍吧!