# ES6 copyWithin

# 前言
;copyWithin (opens new window) 用于浅复制数组的一部分到另一位置。
[1, 2, 3, 4, 5].copyWithin(0, 2, 4) // [3, 4, 3, 4, 5]
其中参数分别为target(复制到的索引)、start(开始复制的索引)、end(结束复制的索引,不包括end位置的元素)。

# 参数
参数start默认值为0,end默认值为数组长度,若参数为负数,函数toAbsoluteIndex会将其转换为正数。
function toAbsoluteIndex(target, len) {
return target < 0 ? len + target : Math.min(target, len)
}
Array.prototype.copyWithin = function (target, start, end) {
var len = this.length
target = toAbsoluteIndex(target, len)
start = toAbsoluteIndex(start || 0, len)
end = end === undefined ? len : toAbsoluteIndex(end, len)
var count = Math.min(len - target, end - start)
var inc = 1
while (count--) {
if (start in this) {
this[target] = this[start]
}
target += inc
start += inc
}
return this
}
# 移动次数
;count为移动次数,取值分为两种情况,第一种由length和target决定。
[1, 2, 3, 4, 5].copyWithin(-2) // [1, 2, 3, 1, 2]
以上相当于copyWithin(3, 0, 5),其中length - target = 2,end - start = 5,移动次数为两者较小值2。

另外一种情况由end和start决定。
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
;length - target = 5,end - start = 1,移动次数为两者较小值1。

故移动次数为Math.min(length - target, end - start)。
# 类数组
;copyWithin对于类数组也是适用的。
[].copyWithin.call({length: 5, 3: 1}, 0, 3) // {0: 1, 3: 1, length: 5}
对象仅含有此属性时,才复制到对应位置。
if (start in this) {
this[target] = this[start]
}
注意只能用in操作符判断,不能用this[start] !== undefined来判断,原因在于可能属性值就是undefined。
const foo = {}
const bar = { key: undefined }
foo.key // undefined
'key' in foo // false
bar.key // undefined
'key' in bar // true
# 倒序移动
以上代码运行如下示例,输出[1, 2, 3, 3, 3, 6],并不是正确的结果。
[1, 2, 3, 4, 5, 6].copyWithin(3, 2, 4) // [1, 2, 3, 3, 4, 6]
我们将start和end固定为2和4,看看不同target值时的情况。

| start | end | count | target | start < target | target < start + count | 输出结果 | 正确结果 | 一致 |
|---|---|---|---|---|---|---|---|---|
2 | 4 | 2 | 0 | 否 | 是 | [3, 4, 3, 4, 5, 6] | [3, 4, 3, 4, 5, 6] | 是 |
2 | 4 | 2 | 1 | 否 | 是 | [1, 3, 4, 4, 5, 6] | [1, 3, 4, 4, 5, 6] | 是 |
2 | 4 | 2 | 2 | 否 | 是 | [1, 2, 3, 4, 5, 6] | [1, 2, 3, 4, 5, 6] | 是 |
2 | 4 | 2 | 3 | 是 | 是 | [1, 2, 3, 3, 3, 6] | [1, 2, 3, 3, 4, 6] | 否 |
2 | 4 | 2 | 4 | 是 | 否 | [1, 2, 3, 4, 3, 4] | [1, 2, 3, 4, 3, 4] | 是 |
2 | 4 | 2 | 5 | 是 | 否 | [1, 2, 3, 4, 5, 3] | [1, 2, 3, 4, 5, 3] | 是 |
可以发现,唯独运行copyWithin(3, 2, 4)时结果错误。
原因在于移动过程中前面的元素会覆盖后面的元素。

倒序移动问题将得到解决。

注意仅有start < target和target < start + count都满足时,才倒序移动,剩余情况还是正常移动。另外target应该从target + count - 1递减,例如以上target应该从索引为4(3 + 2 - 1)的位置开始,start也同理。
if (start < target && target < start + count) {
inc = -1
target += count - 1
start += count - 1
}
# ES5 兼容
function toAbsoluteIndex(target, len) {
return target < 0 ? len + target : Math.min(target, len)
}
Array.prototype.copyWithin = function (target, start, end) {
var len = this.length
target = toAbsoluteIndex(target, len)
start = toAbsoluteIndex(start || 0, len)
end = end === undefined ? len : toAbsoluteIndex(end, len)
var count = Math.min(len - target, end - start)
var inc = 1
if (start < target && target < start + count) {
inc = -1
target += count - 1
start += count - 1
}
while (count--) {
if (start in this) {
this[target] = this[start]
}
target += inc
start += inc
}
return this
}
# 🎉 写在最后
🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star (opens new window) ✨支持一下哦!
手动码字,如有错误,欢迎在评论区指正💬~
你的支持就是我更新的最大动力💪~
GitHub (opens new window) / Gitee (opens new window)、GitHub Pages (opens new window)、掘金 (opens new window)、CSDN (opens new window) 同步更新,欢迎关注😉~