利用for循环实现可以break的filter、forEach

最近开发遇到一个比较复杂的数据处理,大概需求是有多个楼幢,楼幢下有多个单元,单元下有多个户室,可以单击选择户室,点击单元的多选框可以全选当前单元下的所有户室,点击楼幢的多选框可以全选当前楼幢下所有单元的户室,因此选中数组selectedIds中存放的是各个户室对象。数据模型是树形结构,由于样式要求,没有办法使用树状插件,只能自己来实现多选。效果图如下:

效果图

当写到某个楼幢的取消选择时发现了问题,因为去掉selectedIds中该单元下的所有户室,首先需要遍历所有楼幢,找到当前取消选择的楼幢,随后遍历该楼幢下的所有单元,然后再遍历各个单元下的户室,最后遍历selectedIds,找到其中相匹配的id,然后删除该匹配的户室对象。但是按照这样去写会出现两个问题:1、由于每次遍历时索引的变化,splice后下次遍历的索引可能就变成下下个了。2、由于是四重循环,一旦数据量很庞大,且处于最不理想情况下,数据的处理时间会很长,经过测试,一千七百多条数据处理了将近十几秒,这肯定不行的,需要优化。

解决问题1首先想到的是filter,将该楼幢下所有单元下的户室过滤掉,将其余的户室留在数组中,利用新数组接收过滤的值而不是删除,从而避免了索引变化产生的问题。

解决问题2的重点就在于优化循环遍历的次数,首先想到的就是break在完成目的之后跳出循环,但是由于解决问题1利用的filter,而filter、map、forEach这些是没法利用break来跳出循环的,虽然也考虑到可以使用some、every这些可以立即跳出循环的方法,但是需要加上更多的变量进行判断,决定还是自己利用for循环写个可以break的filter。代码如下:

//达成目标直接break的filter
    myFilter(arr, fn) {
      let newArr = [];
      for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (fn(item, i, arr)) {
          newArr.push(arr[i]);
        } else if (i !== arr.length - 1) {
          let afterArr = arr.slice(i + 1);
          newArr = newArr.concat(afterArr);
          break;
        }
      }
      return newArr;
    },
changeCheckBox(value, current) {
    if (this.loading) {
        return;
      }
      let selectedIds = this.selectedIds;
      this.loading = true;
      this.rangeObj.lzs.map(lz => {
        if (lz.lzdm === current.lzdm) {
          lz.dys.map(dy => {
            dy.checked = value;
            dy.fhs.map(fh => {
              if (value) {
                selectedIds.push(fh);
              } else {
                //调用myFilter
                selectedIds = this.myFilter(selectedIds, select => {
                  return select.fhdm !== fh.fhdm;
                });
              }
            });
          });
        }
      });
      this.selectedIds = this.distinct(selectedIds, "fhdm");
      this.loading = false;
},

使用时遍历到各个户室,然后根据value来进行判断,value为true时代表全选,由于selectedIds中包含多个楼幢的多个单元多个户室对象,所以如果再次判断是否已经在selectedIds中情况会非常复杂,这里选择直接push进去,并在最后执行distinct方法进行数组中对象的去重。value为false代表取消全选,则调用写好的myFilter方法,第一个参数arr为待遍历数组,第二个参数是一个回调方法,由myFilter函数传递当前对象item、当前索引index、当前数组arr,并且根据返回值是true,即在selectedIds中找不到与之相等的fhdm(id),则push到这个新数组中。后面的else if是为这里的需求单独加的一段代码,即如果返回值是false,且并非是最后一项,则代表后面的对象都不可能再有相等的fhdm(id)了(各单元下各户室在selectedIds中都是相邻的),直接将后面的对象concat到新数组中,并且break,最后return返回这个新数组,节省了大量无意义遍历时间。优化过后的代码执行效率比之前快了不止一个档次。同样的,后面的单元的取消全选也可以复用这个myFilter方法:

changeCheckBoxDy(value, current, lzObj) {
      if (this.loading) {
        return;
      }
      this.rangeObj.lzs[this.currentIndex].checked = lzObj.dys.every(dyItem => {
        return dyItem.checked;
      });
      let selectedIds = this.selectedIds;
      this.loading = true;
      this.rangeObj.lzs[this.currentIndex].dys.map(dy => {
        if (
          (typeof dy.dydm === "undefined" &&
            typeof current.dydm === "undefined") ||
          dy.dydm === current.dydm
        ) {
          dy.fhs.map(fh => {
            if (value) {
              selectedIds.push(fh);
            } else {
              selectedIds = this.myFilter(selectedIds, select => {
                return select.fhdm !== fh.fhdm;
              });
            }
          });
        }
      });
      this.selectedIds = this.distinct(selectedIds, "fhdm");
      this.loading = false;
},

顺便也附上数组中对象去重方法(arr为待去重数组,key为标识符,即根据哪个键来去重):

distinct(arr, key) {
      var newobj = {},
      newArr = [];
      for (var i = 0; i < arr.length; i++) {
        var item = arr[i];
        if (!newobj[item[key]]) {
          newobj[item[key]] = newArr.push(item);
        }
      }
      return newArr;
},

当然这里其实还可以继续优化,比如在点击多选框时,直接传当前楼幢下的单元数组,可以减少一次所有楼幢的遍历;各个map因为也是遍历所有次,也可以利用for循环重写map、forEach,并且在找到对象后break,都可以进一步优化代码。

各楼幢、单元的全选以及取消全选就完成了,最后还剩下单个户室的选中以及取消选中,由于需要遍历selectedIds选中数组,当找到当前选中/取消选中的对象后应当break,因此选择利用for循环写个可以break的forEach,代码如下:

//达成目标直接break的forEach
    myForEach(arr, fn) {
      for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (fn(item, i, arr)) {
          break;
        }
      }
},
selectFh(obj) {
      if (this.isEdit && !this.isDetail) {
        let selectedIds = this.selectedIds;
        let currentIndex = -1;
        this.myForEach(selectedIds, (item, index) => {
          if (item.fhdm === obj.fhdm) {
            currentIndex = index;
            return true;
          }
        });
        if (currentIndex !== -1) {
          selectedIds.splice(currentIndex, 1);
        } else {
          selectedIds.push(obj);
        }
      }
},

定义一个currentIndex变量,默认值为-1代表未找到,随后调用myForEach方法,同样第一个参数arr为待遍历数组,第二个参数fn为回调函数,由函数内部传递当前对象item、当前索引index、当前数组arr供外部使用,若返回值为true则break跳出循环。最后根据currentIndex的值来决定是新增还是删除。

至于各户室是否显示选中颜色则根据动态class来判断,代码如下:

<div class="lpb-dy-content-item-fh-box">
        <template v-for="(items, idx) in item.fhs">
              <span
                :class="judgeClass(items)"
                :title="'建筑面积:' + items.jzmj + 'm²'"
                :key="idx"
                @click="selectFh(items)">
                  {{ items.sh ? items.sh : "——" }}
              </span>
       </template>
</div>
judgeClass(items) {
      let flag = this.selectedIds.some(it => {
        return it.fhdm === items.fhdm;
      });
      return [
        "lpb-fh",
        {
          "lpb-fh-select": flag
        }
      ];
},

这样各功能都已经实现了,且代码执行效率也提升了很多,剩下的就是一些细节性的东西就不多加赘述啦~。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容