首先我们先要了解循环展开(Loop unwinding)

for (i = 1; i <= 60; i++){
   a[i] = a[i] * b + c;
}

可以如此循环展开:

for (i = 1; i <= 60; i+=3){
  a[i] = a[i] * b + c;
  a[i+1] = a[i+1] * b + c;
  a[i+2] = a[i+2] * b + c;
}

这被称为展开了两次。虽然看起来程序优化之后比以前还长了,但是循环的次数却减少了,同时循环中的条件判断(i <= 60)也相应减少了。

但很明显,如果总循环次数无法被循环增量整除时会有剩余,所以就有了达夫设备。

let testVal = 0;
let n = iterations / 8;
let caseTest = iterations % 8;

do {
 switch (caseTest) {
 case 0:
  testVal++;
 case 7:
  testVal++;
 case 6:
  testVal++;
 case 5:
  testVal++;
 case 4:
  testVal++;
 case 3:
  testVal++;
 case 2:
  testVal++;
 case 1:
  testVal++;
 }
 caseTest = 0;
}
while (--n > 0);

iterations无法为8整除,则仍有iterations%8项未处理;所以加入了switch/case语句,但这里没有使用break关键字,我们通过fall through特性来处理剩余数据(注:C#强制break,golang默认break)。

Duff's Device.png

Duff's device
这是在jsperf上的测试,可以看到循环展开是普通循环的三倍,不过要注意然各个版本的浏览器也存在一定差异。

我认为在前端开发中,大多数情况下没有必要进行这种优化的,除非数据量很大,或者你在写的是组件库,并且做过测试证明真的需要它。