多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
![](https://box.kancloud.cn/f77524e076dba5244cdcb58aa8fcad5f_497x544.jpeg) ``` let averageAge = 0; let totalSalary = 0; for (const p of people) {  averageAge += p.age;  totalSalary += p.salary; } averageAge = averageAge / people.length; ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` let totalSalary = 0; for (const p of people) {  totalSalary += p.salary; } let averageAge = 0; for (const p of people) {  averageAge += p.age; } averageAge = averageAge / people.length; ``` ### 动机 你常常能见到一些身兼多职的循环,它们一次做了两三件事情,不为别的,就因为这样可以只循环一次。但如果你在一次循环中做了两件不同的事,那么每当需要修改循环时,你都得同时理解这两件事情。如果能够将循环拆分,让一个循环只做一件事情,那就能确保每次修改时你只需要理解要修改的那块代码的行为就可以了。 拆分循环还能让每个循环更容易使用。如果一个循环只计算一个值,那么它直接返回该值即可;但如果循环做了太多件事,那就只得返回结构型数据或者通过局部变量传值了。因此,一般拆分循环后,我还会紧接着对拆分得到的循环应用提炼函数(106)。 这项重构手法可能让许多程序员感到不安,因为它会迫使你执行两次循环。对此,我一贯的建议也与2.8节里所明确指出的一致:先进行重构,然后再进行性能优化。我得先让代码结构变得清晰,才能做进一步优化;如果重构之后该循环确实成了性能的瓶颈,届时再把拆开的循环合到一起也很容易。但实际情况是,即使处理的列表数据更多一些,循环本身也很少成为性能瓶颈,更何况拆分出循环来通常还使一些更强大的优化手段变得可能。 ### 做法 - 复制一遍循环代码。 - 识别并移除循环中的重复代码,使每个循环只做一件事。 - 测试。 完成循环拆分后,考虑对得到的每个循环应用提炼函数(106)。 ### 范例 下面我以一段循环代码开始。该循环会计算需要支付给所有员工的总薪水(total salary),并计算出最年轻(youngest)员工的年龄。 ``` let youngest = people[0] ? people[0].age : Infinity; let totalSalary = 0; for (const p of people) {  if (p.age < youngest) youngest = p.age;  totalSalary += p.salary; } return `youngestAge: ${youngest}, totalSalary: ${totalSalary}`; ``` 该循环十分简单,但仍然做了两种不同的计算。要拆分这两种计算,我要先复制一遍循环代码。 ``` let youngest = people[0] ? people[0].age : Infinity; let totalSalary = 0; for (const p of people) {  if (p.age < youngest) youngest = p.age;  totalSalary += p.salary; } for (const p of people) {  if (p.age < youngest) youngest = p.age;  totalSalary += p.salary; } return `youngestAge: ${youngest}, totalSalary: ${totalSalary}`; ``` 复制过后,我需要将循环中重复的计算逻辑删除,否则就会累加出错误的结果。如果循环中的代码没有副作用,那便可以先留着它们不删除,可惜上述例子并不符合这种情况。 ``` let youngest = people[0] ? people[0].age : Infinity; let totalSalary = 0; for (const p of people) {  if (p.age < youngest) youngest = p.age;  totalSalary += p.salary; } for (const p of people) {  if (p.age < youngest) youngest = p.age;  totalSalary += p.salary; } return `youngestAge: ${youngest}, totalSalary: ${totalSalary}`; ``` 至此,拆分循环这个手法本身的内容就结束了。但本手法的意义不仅在于拆分出循环本身,而且在于它为进一步优化提供了良好的起点——下一步我通常会寻求将每个循环提炼到独立的函数中。在做提炼之前,我得先用移动语句(223)微调一下代码顺序,将与循环相关的变量先搬移到一起: ``` let totalSalary = 0; for (const p of people) {  totalSalary += p.salary; } let youngest = people[0] ? people[0].age : Infinity; for (const p of people) {  if (p.age < youngest) youngest = p.age; } return `youngestAge: ${youngest}, totalSalary: ${totalSalary}`; ``` 然后,我就可以顺利地应用提炼函数(106)了。 ``` return `youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`; function totalSalary() {  let totalSalary = 0;  for (const p of people) {   totalSalary += p.salary;  }  return totalSalary; } function youngestAge() {  let youngest = people[0] ? people[0].age : Infinity;  for (const p of people) {   if (p.age < youngest) youngest = p.age;  }  return youngest; } ``` 对于像`totalSalary`这样的累加计算,我绝少能抵挡得住进一步使用以管道取代循环(231)重构它的诱惑;而对于`youngestAge`的计算,显然可以用替换算法(195)替之以更好的算法。 ``` return `youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`; function totalSalary() {  return people.reduce((total,p) => total + p.salary, 0); } function youngestAge() {  return Math.min(...people.map(p => p.age)); } ```