对比两个套餐对象,套了九层循环,有什么好的优化方法?

马格马利
  • 20

如题,要对比两个列表,列表里面是商品套餐对象,套餐里面有tag分类,分类里面有商品,商品中有不同的口味组,口味组中有不同的子口味,做了9个循环,怕被组长砍死,特来求助各路大神

// 遍历第一组套餐
for (var x = 0; x < packageInfoList.length;x++){
    // 拿到第二组中的的套餐
    var newPackage = ConfigData.getGoods(true, package.id);
    // 遍历第一组中的tag分类
    for (var y = 0; y < package.packageTag.length;y++){
        var oldTag = package.packageTag[y];
        for (var z = 0; z < newPackage.packageTag.length;z++){
            var newTag = newPackage.packageTag[z]; // 第二组中的tag分类
            if (package.packageTag.id == newPackage.packageTag.id){
                // 遍历第一组的商品中的tag分类中的 商品
                for (var m = 0; m < oldTag.goods.length;m++){
                     // 遍历第二组的商品中的tag分类中的 商品
                    for (var n = 0; n < newTag.goods.length;n++){
                        if (oldTag.goods[m].id === newTag.goods[n].id){
                            var flag = updateGoodAttr(oldTag.goods[m], newTag.goods[n])
                        }
                    }
                }
            }
        }
    }
}

代码基本如上图

[ // 套餐列表
    { // 套餐对象
       tag: [ // tag 分类列表
            { // tag对象
                goods:[ // goods 列表
                    { // good 对象
                        attr: [ // 口味组列表(如辣度)
                            { // 口味组
                                option:[ // 口味列表
                                    taste1: {},
                                    taste2: {},
                                    taste3: {},
                                    ......
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
]

列表数据结构如上
而且方法updateGoodAttr中对比两个商品,也跑了4层的循环,加起来就9层了,有没有什么好的解决方案,
由于每个属性都存在绑定关系(这个口味就是绑定到了这个商品,这个上商品就是绑定了这个套餐),所以不能把所有的口味taste提取出来对比,这样9层循环,到了后面毫无维护性,而且性能爆炸
求助,来份神代码 揪揪 me,
白鞋

回复
阅读 2.1k
5 个回答
✓ 已被采纳

使用 ES5 内置数组函数 + 箭头函数隐藏 for 循环细节

packageInfoList.forEach(package => {
    var newPackage = ConfigData.getGoods(true, package.id);
    package.packageTag.forEach(tag => {
        var newTag = newPackage.packageTag.find(t => t.id === tag.id);
        if(!newTag) return;
        tag.goods.forEach(goods => {
            var newGoods = newTag.goods.find(g => g.id === goods.id);
            if(!newGoods) return;
            var flag = updateGoodAttr(goods, newGoods);
        });
    });
});

updateGoodAttr = (goods, newGoods) => {
    goods.attr.forEach(attr => {
        var newAttr = newGoods.attr.find(a => a.id === attr.id)
        attr.option.filter(taste => !!taste.select).foreach(taste => {
            var newTasteOption = newAttr.option.find(t => t.id === taste.id);
            if(newTasteOption) {
                newTasteOption.select = taste.select;
            }
        });
    });
}

提取重复的数组比对逻辑

已经省去里很多迭代的下角标处理里,但是代码还是很啰嗦,频繁重复对两个数组进行元素比较。干脆把这个逻辑提取出来:

var match = (arr1, arr2, identifier, process) => arr1.forEach(item1 => 
    arr2.forEach(item2 => identifier(item1) === identifier(item2) && process(item1, item2))
);

传入两个数组,对数组元素的每个元素使用 identifier 获取 id,如果比较成功,则调用 process 函数处理这两个元素。例子中 identifier 都一样,即 item => item.id.

var ID = item => item.id;

var processPackage = (p1, p2) => match(p1.packageTag, p2.packageTag, ID, processTag);

var processTag = (tag1, tag2) => match(tag1.goods, tag2.goods, ID, processGoods);

var processGoods = (goods1, goods2) => match(goods1,attr, goods2.attr, ID, processAttr);

var processAttr = (attr1, attr2) => match(attr1.options.filter(taste => !!taste.select), attr2.options, ID, processTaste);

var processTaste = (taste1, taste2) => taste2.select = taste1.select


var packageInfoList = [...], configPackageList = [...];

match(packageInfoList, configPackageList, ID , processPackage);

消除重复调用

目前代码中还存在重复地调用 match 函数,调用逻辑相似而重复,如果能把这块抽出来就好了。我们再提取一个函数,用来构造 processXXXprocessAttr比较特殊,对两边取子属性的逻辑不一样,所以提取的这个函数需要考虑 processAttr 的需求。

var subProcess = ([s1, s2], id, process) => (i1, i2) => match(s1(i1), s2(i2), id, process)

var fixSelector = f => Array.isArray(f) ? f : [f, f]
var processTaste = (taste1, taste2) => taste2.select = taste1.select
var processAttr = subProcess(
    fixSelector([item => item.options.filter(taste => !!taste.select), item => item.options]), 
    ID,
    processTaste
)
var processGoods = subProcess(fixSelector(item => item.attr), ID, processAttr)
var processTag = subProcess(fixSelector(item => item.goods), ID, processGoods)
var processPackage = subProcess(fixSelector(item => item.tag), ID, processTag)


var ID = item => item.id
match(
    packageInfoList,
    configPackageList,
    ID,
    processPackage
);

最后一步调用其实等价于:

subProcess(fixSelector([() => packageInfoList, () => configPackageList], ID, 
    subProcess( fixSelector(package => package.tag), ID
        subProcess( fixSelector(tag => tag.goods), ID,
            subProcess( fixSelector(goods => goods.attr), ID
                subProcess( fixSelector([attr => attr.options.filter(taste => !!taste.select), attr => attr.options]), ID
                    processTaste
                )
            )   
        )          
    )
)()

把掉套函数调用拉平试试?试想如下代码:

f(a, f(b, f(c, f(d, e)))))

// 等价于

[a,b,c,d,e].reduceRight((prev, item) => f(item, prev))

于是有:

var match = (arr1, arr2, identifier, process) => arr1.forEach(item1 => 
    arr2.forEach(item2 => identifier(item1) === identifier(item2) && process(item1, item2))
)
var subProcess = ([s1, s2], id, process) => (i1, i2) => match(s1(i1), s2(i2), id, process)
// 为了传递 selector 的时候可以单独给一个函数
var fixSelector = f => Array.isArray(f) ? f : [f, f]
var reducer = (prev, [selector, identifier]) => subProcess(fixSelector(selector), identifier, prev)
var process = (...items) => items.reduceRight(reducer)()


// 调用
process(
    [ [() => packageInfoList, () => configPackageList], ID], // 初始数据
    [ package => package.tag, ID], // 根据上面一项的元素,返回需要继续比对的下一层数据
    [ tag => tag.goods, ID], // 把ID当参数在每一层传进来是为了支持不同层取 ID 的方式不同
    [ goods => goods.attr, ID], // 再深也不怕,无非多一个参数
    // 支持两边不同的取值方法
    [ [attr => attr.options.filter(taste => !!taste.select), attr => attr.options], ID], 
    // 最后一个函数就是你处理深处数据的地方啦
    (taste1, taste2) => taste2.select = taste1.select 
)

虽然调用的时候挺漂亮的,但是有点绕。。。

把对比方法封装到对象里。

比如:

套餐.compare(另一个套餐)
  tag.compare(another tag)
    good.compare(another good)
      attr.compare(another attr)
        option.compare(another option)
          taste.compare(another taste)

组长没砍死你的话,让他写.没法优化就这样吧.

能力不够,等待大神来优化!


不过可以先优化细节,比如保存 arr[i],缓存 length

比这个循环长的我见过,写的那个人就是以前组长,他估计到现在也没有优化,,,,

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏