如何优化JavaScript状态管理中JSON路径订阅的监听效率?
优化状态管理库的订阅回调触发效率
我开发了一个简易状态管理库,包含基础store对象,希望仅在store的特定JSON路径发生变更时触发回调。比如订阅路径$subjects.maths.passingMarks,执行store.setState({subjects: {science: {passingMarks: 60}}})时不触发回调,执行store.setState({subjects: {maths: {passingMarks: 72}}})时才触发回调。
当前实现的publish方法会遍历全部回调列表,通过JSONPath获取并对比前后状态对应路径的值来触发回调,但当存在大量订阅时,单次状态变更需要遍历所有回调,效率很低,想找减少迭代或者更高效的实现方案。
初始状态与回调示例
const store = { subjects: {science: {passingMarks: 50}, maths: {passingMarks: 50}} } const callback = (state) => { console.log('callback :: '+state); }
状态变更示例
// 此操作不应触发订阅$subjects.maths.passingMarks的回调 store.setState({subjects: {science: {passingMarks: 60}}});
// 此操作应触发订阅$subjects.maths.passingMarks的回调 store.setState({subjects: {maths: {passingMarks: 72}}});
当前publish实现代码
const isObject = (val) => val != null && typeof val === "object" && Array.isArray(val) === false; const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); publish(currentState, nextState) { if (!isObject(currentState)) throw new Error("currentState should be an object"); if (!isObject(nextState)) throw new Error("nextState should be an object"); for (var key in this.callbackList) { const currentValue = JSONPath({ path: key, json: currentState }); const nextValue = JSONPath({ path: key, json: nextState }); if (!isEqual(currentValue, nextValue)) { const callbacks = this.callbackList[key]; callbacks.forEach((cl) => { cl(nextState); }); } } }
优化方案
1. 先定位变更路径,再匹配订阅
不用遍历所有订阅,先找出本次状态变更涉及的所有具体路径,再匹配对应的订阅回调,减少无效迭代:
// 递归找出新旧对象间所有变更的路径 function getChangedPaths(oldObj, newObj, parentPath = '') { const changedPaths = []; // 处理新对象的键 for (const key of Object.keys(newObj)) { const currentPath = parentPath ? `${parentPath}.${key}` : key; const oldVal = oldObj[key]; const newVal = newObj[key]; if (isObject(oldVal) && isObject(newVal)) { changedPaths.push(...getChangedPaths(oldVal, newVal, currentPath)); } else if (!deepEqual(oldVal, newVal)) { changedPaths.push(currentPath); } } // 处理旧对象有但新对象没有的键 for (const key of Object.keys(oldObj)) { if (!(key in newObj)) { const currentPath = parentPath ? `${parentPath}.${key}` : key; changedPaths.push(currentPath); } } return changedPaths; } // 替换原isEqual的深度对比函数 function deepEqual(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (typeof a !== 'object' || typeof b !== 'object') return false; const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; } return true; } // 优化后的publish方法 publish(currentState, nextState) { if (!isObject(currentState)) throw new Error("currentState should be an object"); if (!isObject(nextState)) throw new Error("nextState should be an object"); // 获取所有变更路径并转为JSONPath格式 const changedPaths = getChangedPaths(currentState, nextState); const jsonChangedPaths = changedPaths.map(path => `$.${path}`); // 只遍历与变更路径相关的订阅 for (const subPath of Object.keys(this.callbackList)) { const shouldTrigger = jsonChangedPaths.some(changedPath => { // 匹配逻辑:变更路径是订阅路径的子路径,或订阅路径是变更路径的父路径 return changedPath.startsWith(subPath) || subPath.startsWith(changedPath); }); if (shouldTrigger) { // 二次确认值是否真的变化,避免路径匹配误判 const currentValue = getValueByPath(currentState, subPath); const nextValue = getValueByPath(nextState, subPath); if (!deepEqual(currentValue, nextValue)) { this.callbackList[subPath].forEach(cl => cl(nextState)); } } } } // 轻量路径取值函数,替代JSONPath提升性能 function getValueByPath(obj, path) { const keys = path.replace(/^\$./, '').split('.'); let current = obj; for (const key of keys) { if (!current || typeof current !== 'object') return undefined; current = current[key]; } return current; }
2. 树形结构存储订阅路径
将订阅路径按层级拆分为树形结构,比如:
// 订阅存储示例 this.subTree = { "$": { "subjects": { "maths": { "passingMarks": [callback1] }, "science": { "passingMarks": [callback2] } } } }
当变更路径是$.subjects.maths.passingMarks时,可直接遍历树形结构定位到对应的回调,无需遍历所有订阅项,同时还能方便处理父路径变更触发子路径回调的场景。
3. 移除JSONPath依赖
JSONPath的解析和查询有额外性能开销,对于点分隔的简单路径,用上面的getValueByPath这类轻量函数替代,能显著提升取值速度。
内容的提问来源于stack exchange,提问作者Radhika Singal
相关产品推荐
相关产品推荐

