js 优化循环,DUFF装置实现展开迭代

前言

在日常的开发中,一些数组和对象组成的数据结构随处可见,而随着业务功能越来越复杂,简单的对象数组也将不能再满足需求。

案例

假设现在有一个这样的需求:

  1. 给定一个表格,表格每行为一个对象,其中有一个 id 属性为当前行的唯一标示
  2. 支持多选,点击每行数据前的多选按钮,保存进一个数组 idArr,取消时删除数组中该项
  3. 点击保存按钮后将该行数据的 isChecked属性改为 true

思路大概同上(具体业务代码代码不在此实现)

此处为每行的数据及已勾选行的 id 数组

// 假设数据为 data
const data = [
 {id: 1, name: 'row1', isChecked: false}, 
 {id: 2, name: 'row2', isChecked: false}, 
 {id: 3, name: 'row3', isChecked: false},
 {id: 4, name: 'row4', isChecked: false},
 {id: 5, name: 'row5', isChecked: false},
 {id: 6, name: 'row6', isChecked: false},
 {id: 7, name: 'row7', isChecked: false}
]

// 假定 idArr
const idArr = [1,2,3,4,5,6]

像这样的两个数组,若要修改每个对应 id 行的数据,自然会想到使用两层循环遍历,但问题是当数据量大这样会非常耗性能

实现方式

  • 两层循环实现如下
// 假设数据为 data
const data = [
 {id: 1, name: 'row1', isChecked: false}, 
 {id: 2, name: 'row2', isChecked: false}, 
 {id: 3, name: 'row3', isChecked: false},
 {id: 4, name: 'row4', isChecked: false},
 {id: 5, name: 'row5', isChecked: false},
 {id: 6, name: 'row6', isChecked: false},
 {id: 7, name: 'row7', isChecked: false}
]

// 假定 idArr
const idArr = [1,2,3,4,5,6]

for(let i = idArr.length - 1; i >= 0 ; i--) {
	for(let j = data.length - 1; j >= 0; j--) {
		if(data[i].id !== idArr[i]) continue
		data[i].isChecked = true
	}
}

几十几百条如上数据,这样写问题倒不是特别凸显,但当数据量达到万条以上且数据层级过深的时候就有可能造成页面卡死

  • 两次循环改为一层
// 假设数据为 data
const data = [
 {id: 1, name: 'row1', isChecked: false}, 
 {id: 2, name: 'row2', isChecked: false}, 
 {id: 3, name: 'row3', isChecked: false},
 {id: 4, name: 'row4', isChecked: false},
 {id: 5, name: 'row5', isChecked: false},
 {id: 6, name: 'row6', isChecked: false},
 {id: 7, name: 'row7', isChecked: false}
]

// 假定 idArr
const idArr = [1,2,3,4,5,6]

// 定义一个 id 映射表
const dataMap = {}
for(let i = data.length - 1; i >= 0; i--) {
	// 以行的 id 为 key,引用地址为 value,组成一个对象(引用类型赋值是传地址)
	dataMap[data[i].id] = data[i]
}

// 遍历 idArr 对,行数据重新赋值
for(let i = idArr.length - 1; i >= 0; i--) {
	dataMap[idArr[i]].isChecked = true
}

console.log(data)

执行结果如下,可见 1-6 的 isChecked 已被修改
在这里插入图片描述
该方法将两层嵌套循环改为两个同层循环,降低了时间复杂度(n^2 ==> 2n)

思考:那还有没有办法继续优化呢?若数据有一百万条,以上方法依然需要遍历一百万此,那有没有办法降低循环的次数呢?

  • DUFF
    红宝书中有提到一种 DUFF装置 ,可以很大限度的降低循环次数,书中源码如下:
var doSomething = (num) => console.log(num);

var values = [];
// 假设有 50w 条数据
for (var i = 0; i < 500000; i++) {
  values.push(i);
}

var count = Math.ceil(values.length / 8);
var start = values.length % 8;
var j = 0;

do {
  switch (start) {
    case 0: doSomething(j++);
    case 7: doSomething(j++);
    case 6: doSomething(j++);
    case 5: doSomething(j++);
    case 4: doSomething(j++);
    case 3: doSomething(j++);
    case 2: doSomething(j++);
    case 1: doSomething(j++);
  }
  start = 0;
} while (--count > 0);

上述算法若遍历一个长度为 10 的数组,仅仅只需要遍历两次。大概实现思路是,以元素个数除以8,计算循环次数,ceil 向上取整,是为保证迭代次数一定为正数,但完全以此进行迭代,可能会多执行几次 doSomething,因为n / 8很有可能是除不尽,所以排除可整除部分以外,第一次迭代需要将 length % 8次 doSomething 函数,先执行完。

若 length 为10,执行次数就是2,余8就为2,那第一次迭代就得从 case 2 开始执行,第一次会执行两次 doSomething,第一次执行完后将 start 归零,那么第二次就会执行8次 doSomething,加起来就是 10

坑:为什么一定是得除以8,9不是可以最大限度的降低迭代次数吗?
这个地方我也是想了很多也没有答案,大佬们有想法的话可以交流一下

既然 DUFF 可以大幅度减少迭代次数,那么我想上述第二种算法进行优化,以下是具体实现

// 假设数据为 data
const data = [
 {id: 1, name: 'row1', isChecked: false}, 
 {id: 2, name: 'row2', isChecked: false}, 
 {id: 3, name: 'row3', isChecked: false},
 {id: 4, name: 'row4', isChecked: false},
 {id: 5, name: 'row5', isChecked: false},
 {id: 6, name: 'row6', isChecked: false},
 {id: 7, name: 'row7', isChecked: false}
]

// 假定 idArr
const idArr = [1,2,3,4,5,6]

// 定义 DUFF 函数
Array.prototype.duffFunc = function(callback){
  console.log(this)
	if(!Array.isArray(this)) {
		console.error('must be a array')
		return
	}
	let iteration = Math.ceil(this.length / 8)
	let start = this.length % 8
	let index = 0

	do{
		switch(start) {
			case 0: callback(this[index], index++)
			case 7: callback(this[index], index++)
			case 6: callback(this[index], index++)
			case 5: callback(this[index], index++)
			case 4: callback(this[index], index++)
			case 3: callback(this[index], index++)
			case 2: callback(this[index], index++)
			case 1: callback(this[index], index++)
		}
		start = 0
	}while (--iteration > 0)
}

// 将 for 循环全部改为 duff 实现
const dataMap = {}
data.duffFunc((item, index) => {
	dataMap[item.id] = item
})

idArr.duffFunc((item, index) => {
	dataMap[item].isChecked = true
})
console.log(data)

掘金

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值