如何实现子选择器值向上汇总至父选择器且不受顺序影响?
修复子选择器向父选择器汇总数据的顺序依赖问题
我需要将子选择器的revenue和sessions值汇总到对应的父选择器中,当前仅用两个选择器做测试,实际场景中选择器数量更多、类型也可能不同。现有JavaScript代码在父选择器位于对象前、子选择器在后时能正常工作,但交换两者在对象中的顺序后,汇总算法失效,请问有什么修复方法?
正常运行的代码示例
const ecommerceObj = { 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a': { revenue: 4, sessions: 3, }, 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a > span': { revenue: 6, sessions: 8, }, }; function generateRollUpValues(ecommerceObj) { const getParent = (str) => { const idx = String(str).lastIndexOf(' > '); if (idx > 0) { return str.substr(0, idx); } return false; }; const rollup = {}; const rev = []; const keys = Object.keys(ecommerceObj); // eslint-disable-next-line no-plusplus for (let i = 0; i < keys.length; i++) { rev.push(keys[i]); } for (let t = rev.length - 1; t >= 0; t -= 1) { const key = rev[t]; const current = ecommerceObj[key]; const p = getParent(key); // console.log(key + ' -> ' + p); if (p && rollup[p] === undefined) { rollup[p] = { revenue: 0, sessions: 0, }; } if (rollup[key] != undefined) { ecommerceObj[key].revenue += rollup[key].revenue; ecommerceObj[key].sessions += rollup[key].sessions; } if (p) { rollup[p].revenue += ecommerceObj[key].revenue; rollup[p].sessions += ecommerceObj[key].sessions; } } return ecommerceObj; } console.log(generateRollUpValues(ecommerceObj));
失效的代码示例(仅交换选择器顺序)
const ecommerceObj = { 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a > span': { revenue: 6, sessions: 8, }, 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a': { revenue: 4, sessions: 3, }, };
问题原因
原代码的遍历逻辑依赖选择器在对象中的顺序:通过反转keys数组从后往前处理,只有当子选择器在对象中排在父选择器之后时,反转后子选择器才会先被处理,其数值才能被正确累加到父选择器的rollup缓存中。如果子选择器排在父选择器前面,反转后父选择器会先被处理,此时子选择器的汇总值还未计算,导致父选择器无法获取子选择器的数值。
修复方案
核心思路是按选择器的层级深度排序:层级越深(被>分割的片段数量越多)的选择器越先处理,确保子选择器始终在父选择器之前被处理,彻底摆脱对对象key顺序的依赖。
修改后的完整代码
function generateRollUpValues(ecommerceObj) { const getParent = (str) => { const idx = String(str).lastIndexOf(' > '); if (idx > 0) { return str.substr(0, idx); } return false; }; const rollup = {}; // 按选择器层级深度排序,层级深的先处理 const sortedKeys = Object.keys(ecommerceObj).sort((a, b) => { const depthA = a.split(' > ').length; const depthB = b.split(' > ').length; return depthB - depthA; }); for (const key of sortedKeys) { const current = ecommerceObj[key]; const p = getParent(key); if (p && rollup[p] === undefined) { rollup[p] = { revenue: 0, sessions: 0, }; } if (rollup[key] != undefined) { ecommerceObj[key].revenue += rollup[key].revenue; ecommerceObj[key].sessions += rollup[key].sessions; } if (p) { rollup[p].revenue += ecommerceObj[key].revenue; rollup[p].sessions += ecommerceObj[key].sessions; } } return ecommerceObj; } // 测试顺序颠倒的对象 const ecommerceObj = { 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a > span': { revenue: 6, sessions: 8 }, 'header#section-header > div > div.Header__FlexItem.Header__FlexItem--fill.hidden-phone > nav > ul > li:nth-child(6) > a': { revenue: 4, sessions: 3 }, }; console.log(generateRollUpValues(ecommerceObj)); // 输出结果中父选择器的revenue应为10,sessions应为11
关键修改点
- 新增排序逻辑:通过
split(' > ')计算选择器的层级深度,排序后确保子选择器(层级更深)优先处理 - 简化遍历逻辑:直接遍历排序后的keys数组,去掉原有的反转数组操作
- 保持核心汇总逻辑:原有的
rollup缓存累加逻辑不变,确保子选择器的数值能正确传递给父选择器
内容的提问来源于stack exchange,提问作者Moro Owusu Afriyie
相关产品推荐
相关产品推荐

