在处理预约时间段时,需要将一组可能存在连续时间段的数组进行整理,合并相邻的连续时间范围。时间连续的定义是:一个时间段的结束时间等于下一个时间段的开始时间。最终结果是一个新的数组,其中每个元素表示一个合并后的连续时间范围。
1 2 3 4 5 6
| [ { startTime: '09:00', endTime: '09:30' }, { startTime: '09:30', endTime: '10:00' }, { startTime: '10:00', endTime: '10:30' }, { startTime: '10:30', endTime: '11:00' } ]
|
输出:
1 2 3
| [ { startTime: '09:00', endTime: '11:00' } ]
|
刚开始有这样一段函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function mergeTimeIntervals(intervals) { if (intervals.length === 0) return []; const mergedIntervals = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const lastMerged = mergedIntervals[mergedIntervals.length - 1]; const current = intervals[i]; if (lastMerged.endTime === current.startTime) { lastMerged.endTime = current.endTime; } else { mergedIntervals.push(current); } } return mergedIntervals; }
|
该函数诈一看没有问题,要说问题的话,也就是[intervals[0]]
地址和传入进来的地址一样,但是对我来说无所谓嘛,反正我用的是返回结果。并不关心之前的值。但是你别说,不关心地址的话,在使用中还真出现了问题。
问题原因
[intervals[0]]
是引用: mergedIntervals
中的第一个元素直接引用了 intervals[0]
,因此对 mergedIntervals
的修改实际上也会影响 intervals
的数据。
- 原数据的隐式修改: 尽管你只使用返回值,但因为
intervals
被间接修改了,可能会导致调用 mergeTimeIntervals
后的数据状态变得不可控,特别是在 Vue 或 React 这样的框架中,数据的修改会触发不必要的重渲染或其他副作用。
下面就是一个错误的场景(VUE3+ElementPlus):
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <el-table :data="tableData" style="width: 100%"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column prop="name" label="Name" width="180" /> <el-table-column prop="address" label="Address" /> <el-table-column label="时间范围"> <template #default="{ row }"> {{mergeTimeIntervals(row.intervals)}} </template> </el-table-column> </el-table> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <script lang="ts" setup> const tableData = [ { date: '2016-05-03', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', intervals: [ { startTime: '09:00', endTime: '9:30', bookingFlag: 0 }, { startTime: '9:30', endTime: '10:00', bookingFlag: 0 }, { startTime: '10:00', endTime: '10:30', bookingFlag: 0 }, { startTime: '10:30', endTime: '11:00', bookingFlag: 0 } ] } ]
function mergeTimeIntervals(intervals) { if (intervals.length === 0) return []; const mergedIntervals = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const lastMerged = mergedIntervals[mergedIntervals.length - 1]; const current = intervals[i]; if (lastMerged.endTime === current.startTime) { lastMerged.endTime = current.endTime; } else { mergedIntervals.push(current); } } console.log(mergedIntervals, 'mergedIntervals') return mergedIntervals; }
</script>
|
按道理说,这种才是正常的效果9:00 - 11:00

但是执行完成总是不尽人意:

展示的数据多了好多,而且在仔细看看自己的代码,感觉逻辑上没有问题啊,怎么会出现这么多的数据呢?

总的来说,在函数中,const mergedIntervals = [intervals[0]];
初始化时,mergedIntervals
中的第一个元素直接引用了原始数据 intervals[0]
。由于 JavaScript 的对象是引用类型,这导致 mergedIntervals
和 intervals
共享同一个内存地址。
当代码执行到 lastMerged.endTime = current.endTime;
时,mergedIntervals
的内容被修改,同时也直接影响了 intervals
的原始数据。对于表格组件来说,这种数据变化会触发视图重新渲染,而每次渲染都会重新调用该函数,形成了意想不到的循环执行。
下面是对代码进行的更改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function mergeTimeIntervals(intervals) { if (intervals.length === 0) return []; const mergedIntervals = [{...intervals[0]}]; for (let i = 1; i < intervals.length; i++) { const lastMerged = mergedIntervals[mergedIntervals.length - 1]; const current = intervals[i]; if (lastMerged.endTime === current.startTime) { lastMerged.endTime = current.endTime; } else { mergedIntervals.push({ ...current }); } } return mergedIntervals; }
|
- 不修改原始数据: 所有的区间在合并时都经过浅拷贝,返回的新数组和输入的
intervals
互不干扰。
- 安全性提高: 在任何框架中使用都不会导致原始数据的意外污染,避免了隐式副作用。