26 Array.shift()
26.1 基本介绍
shift()
方法从数组中删除第一个元素,并返回该元素的值。此方法会更改数组的长度。
array.shift()
输入参数:无。
输出:被移除的第一个元素。如果数组为空,则返回 undefined
。
注意事项:
shift()
方法会修改原数组,是一个修改方法。- 删除第一个元素后,后续元素的索引会依次减一。
26.2 手写实现
MyArray.prototype.shift = function() {
if (this.length === 0) {
return undefined;
}
let firstElement = this[0];
for (let i = 1; i < this.length; i++) {
// 整个数组索引1到length - 1 需要向前移动
if (i in this) {
// 空槽不可以以这种方式移动,会变成undefined
this[i - 1] = this[i];
} else {
// 正确处理空槽德移动方式,delete对象德属性
delete this[i - 1];
}
}
// 删除最后一个元素,减少length
delete this[this.length - 1];
this.length--;
return firstElement;
};
// 测试用例
let arr = new MyArray(1, 2, 3).concat([,,4]);
let shiftedElement = arr.shift();
console.log(shiftedElement); // 1
console.log(arr); // [2, 3]
let emptyArr = new MyArray();
let shiftedFromEmpty = emptyArr.shift();
console.log(shiftedFromEmpty); // undefined
console.log(emptyArr); // []
难点总结:
- 处理稀疏数组:需要正确处理空槽,避免错误地复制
undefined
,需要使用delete this[i - 1]
相当于移动了空槽。 - 更新
length
属性:需要手动更新数组的length
属性,删除delete
数组德最后一个元素。
27 Array.slice()
27.1 基本介绍
slice()
方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象中。原始数组不会被修改。
array.slice()
array.slice(start)
array.slice(start, end)
输入参数:
start
(可选):开始提取的索引,默认为0
。负索引表示从末尾开始计算。end
(可选):结束提取的索引(不包括该索引),默认为数组长度。负索引表示从末尾开始计算。
输出:一个新的数组,包含从 start
到 end
(不包括 end
)的元素。
注意事项:
slice()
方法不会修改原数组,是一个复制方法。- 对于稀疏数组,返回的新数组也会保留对应位置的空槽。
slice()
方法是通用方法,可用于类数组对象。
27.2 手写实现
MyArray.prototype.slice = function(start = 0, end = this.length) {
let length = this.length >>> 0;
// 处理负索引
let relativeStart = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
let relativeEnd = end < 0 ? Math.max(length + end, 0) : Math.min(end, length);
let count = Math.max(relativeEnd - relativeStart, 0);
let result = new MyArray();
for (let i = 0; i < count; i++) {
let index = relativeStart + i;
// 只有在不是空槽的情况下才会赋值
// 所以空槽其实就是不存在的对象属性
if (index in this) {
result[i] = this[index];
}
}
// 不要忘记处理结果的数组长度
result.length = count;
return result;
};
// 测试用例
let arr_1 = new MyArray(1, 2, 3, 4, 5);
let slicedArr = arr_1.slice(1, 3);
console.log(slicedArr); // [2, 3]
let arr_2 = new MyArray().concat([1, , 3, , 5]);
let slicedArr2 = arr_2.slice(1, 4);
console.log(slicedArr2); // [empty, 3, empty]
console.log(slicedArr2.length); // 3
难点总结:
- 处理稀疏数组:返回的新数组需要保留空槽位置,空槽实际上就是不存在数组列表对象属性。
- 计算长度:确保新数组的
length
属性设置正确。
28 Array.some()
28.1 基本介绍
some()
方法测试数组中是否至少有一个元素通过了由提供的函数实现的测试。如果在数组中找到一个元素使得提供的函数返回 true,则返回 true;否则返回 false。它不会修改数组。
array.some(callbackFn)
array.some(callbackFn, thisArg)
输入参数:
callbackFn(element, index, array)
:用于测试每个元素的函数。thisArg
(可选):执行callbackFn
时使用的this
值。
输出:如果数组中至少有一个元素通过了测试,则返回 true
;否则返回 false
。
注意事项:
some()
方法不会修改原数组。- 一旦找到使
callbackFn
返回真值的元素,方法将立即返回true
,不会继续遍历。 - 空数组会返回false
- 对于稀疏数组,
some()
会跳过空槽。 some()
方法是通用方法。
28.2 手写实现
MyArray.prototype.some = function(callbackFn, thisArg) {
if (typeof callbackFn !== 'function') {
throw new TypeError(callbackFn + ' is not a function');
}
for (let i = 0; i < this.length; i++) {
if (!(i in this)) continue; // 跳过空槽
// 使用.call调用函数,绑定thisArg
if (callbackFn.call(thisArg, this[i], i, this)) {
return true;
}
}
return false;
};
// 测试用例
let arr_3 = new MyArray(1, 2, 3, 4);
let hasEven = arr_3.some(x => x % 2 === 0);
console.log(hasEven); // true
let arr_4 = new MyArray().concat([1, , 3]);
let hasUndefined = arr_4.some(x => x === undefined);
console.log(hasUndefined); // false
难点总结:
- 提前终止遍历:一旦
callbackFn
返回真值,some()
方法立即返回true
。 - 处理稀疏数组:跳过空槽,不调用
callbackFn
。 - 正确绑定
thisArg
:在调用callbackFn
时需要正确绑定thisArg
。
29 Array.sort()
29.1 基本介绍
sort()
方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的。
array.sort()
array.sort(compareFunction)
输入参数:
compareFunction(a, b)
(可选):用于定义排序顺序的函数。返回值为负数表示a
在b
之前,正数表示a
在b
之后,零表示相等。
输出:排序后的数组。原数组已被修改。
注意事项:
sort()
方法会修改原数组,是一个修改方法。- 如果没有提供
compareFunction
,元素会被转换为字符串,然后按字典顺序排序。 - 对于undefined,会放在数组的结尾
- 对于稀疏数组,
sort()
会将空槽排在所有undefined的后面
。
29.2 手写实现
MyArray.prototype.sort = function(compareFunction) {
let length = this.length >>> 0;
// 分离数组元素
let elements = [];
let undefinedElements = [];
let holesCount = 0;
for (let i = 0; i < length; i++) {
if (i in this) {
let value = this[i];
if (value === undefined) {
undefinedElements.push(value);
} else {
elements.push(value);
}
} else {
holesCount++; // 统计空槽数量
}
}
// 默认比较函数
if (typeof compareFunction !== 'function') {
compareFunction = function(a, b) {
let strA = String(a);
let strB = String(b);
if (strA < strB) return -1;
if (strA > strB) return 1;
return 0;
};
}
// 手写排序算法(快速排序)
function quickSort(arr, left, right) {
if (left >= right) return;
let pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
function partition(arr, left, right) {
let pivot = arr[right];
let i = left - 1;
for (let j = left; j < right; j++) {
if (compareFunction(arr[j], pivot) <= 0) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[right]] = [arr[right], arr[i + 1]];
return i + 1;
}
quickSort(elements, 0, elements.length - 1);
// 清空原数组
for (let i = 0; i < length; i++) {
delete this[i];
}
// 重新赋值排序后的元素
let index = 0;
for (let i = 0; i < elements.length; i++) {
this[index++] = elements[i];
}
// 添加 undefined 元素
for (let i = 0; i < undefinedElements.length; i++) {
this[index++] = undefined;
}
// 保留空槽(holes)
this.length = index + holesCount;
return this;
};
// 测试用例 1:数字排序(未提供 compareFunction)
let arr_5 = new MyArray(3, undefined, 80, 9, undefined, 10);
arr_5[10] = 100; // 在索引 10 处创建一个元素,索引 6-9 为空槽
arr_5.sort();
console.log(arr_5); // [10, 100, 3, 80, 9, undefined, undefined, <4 empty items>]
// 测试用例 2:字符串排序(未提供 compareFunction)
let arr_6 = new MyArray().concat(['banana', undefined, 'apple', , 'cherry']);
arr_6.sort();
console.log(arr_6); // ['apple', 'banana', 'cherry', undefined, <1 empty item>]
// 测试用例 3:使用自定义比较函数(不区分大小写)
let arr_7 = new MyArray('Banana', 'apple', 'Cherry', undefined);
arr_7.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(arr_7); // ['apple', 'Banana', 'Cherry', undefined]
// 测试用例 4:稀疏数组
let arr_8 = new MyArray();
arr_8[2] = 'c';
arr_8[5] = 'a';
arr_8[7] = undefined;
arr_8[9] = 'b';
arr_8.sort();
console.log(arr_8); // ['a', 'b', 'c', undefined, <6 empty items>]
难点总结:
- 实现排序算法:实现一个完整的排序算法(如快速排序)较为复杂。
- 处理
compareFunction
:需要正确处理用户提供的比较函数。 - 处理稀疏数组及undefined:需要正确处理空槽和undefined,排序前将其转换为密集数组。
- 原地修改:需要确保对原数组进行修改,而不是返回新数组。
30 Array.splice()
30.1 基本介绍
splice()
方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1, item2, ...)
输入参数:
start
:指定修改的开始位置(从 0 开始计数)。如果超出数组长度,则从数组末尾开始添加内容。如果为负值,表示从数组末尾开始计数。deleteCount
(可选):整数,表示要移除的数组元素的个数。- 如果
deleteCount
为 0,则不删除元素。 - 如果
deleteCount
大于剩余元素的数量,则删除到数组末尾。 - 如果未指定
deleteCount
,则删除从start
开始的所有元素。
- 如果
item1, item2, ...
(可选):要添加进数组的元素,从start
位置开始插入。
输出:一个由被删除的元素组成的数组。如果没有删除元素,则返回空数组。
注意事项:
splice()
方法会修改原数组,是一个修改方法。- 如果仅添加元素,不删除,则
deleteCount
应为 0。 - 如果仅删除元素,不添加,则不需要提供
item
参数。 - 如果删除的部分是稀疏的,则
splice()
返回的数组也是稀疏的,对应的索引为空槽。
30.2 手写实现
MyArray.prototype.splice = function(start, deleteCount, ...items) {
let length = this.length >>> 0;
// 处理索引
let actualStart = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
let actualDeleteCount;
if (arguments.length === 1) {
// 只有start的情况下,会删除从start开始到结尾的元素
actualDeleteCount = length - actualStart;
} else if (arguments.length >= 2) {
// 确保删除数目的合法性,最多只能到数组结尾
actualDeleteCount = Math.min(Math.max(Number(deleteCount), 0), length - actualStart);
} else {
actualDeleteCount = 0;
}
let removedElements = new MyArray();
// 取出要删除的元素-保留空槽
for (let i = 0; i < actualDeleteCount; i++) {
let fromIndex = actualStart + i;
if (fromIndex in this) {
removedElements[i] = this[fromIndex];
}
}
removedElements.length = actualDeleteCount;
// 添加和删除元素的数量差
let itemCount = items.length;
let shiftCount = itemCount - actualDeleteCount;
if (shiftCount > 0) {
// 需要后移元素
for (let i = length - 1; i >= actualStart + actualDeleteCount; i--) {
let fromIndex = i;
let toIndex = i + shiftCount;
if (fromIndex in this) {
this[toIndex] = this[fromIndex];
} else {
delete this[toIndex];
}
}
} else if (shiftCount < 0) {
// 需要前移元素
for (let i = actualStart + actualDeleteCount; i < length; i++) {
let fromIndex = i;
let toIndex = i + shiftCount;
if (fromIndex in this) {
this[toIndex] = this[fromIndex];
} else {
delete this[toIndex];
}
}
// 删除多余的元素
for (let i = length - 1; i >= length + shiftCount; i--) {
delete this[i];
}
}
// 插入新元素
for (let i = 0; i < itemCount; i++) {
this[actualStart + i] = items[i];
}
// 更新 length
this.length = length + shiftCount;
return removedElements;
};
// 测试用例
let arr_9 = new MyArray(1, 2, 3, 4, 5);
let removed = arr_9.splice(2, 2, 'a', 'b');
console.log(arr_9); // [1, 2, 'a', 'b', 5]
console.log(removed); // [3, 4]
let arr_10 = new MyArray(1, 2, 3);
let removed2 = arr_10.splice(1);
console.log(arr_10); // [1]
console.log(removed2); // [2, 3]
let arr_11 = new MyArray(1, 2, 3);
let removed3 = arr_11.splice(-1, 0, 4, 5);
console.log(arr_11); // [1, 2, 4, 5, 3]
console.log(removed3); // []
难点总结:
- 计算索引及计算合法删除数量:需要正确处理
start
和deleteCount
,特别是负索引的情况。还要注意删除元素个数不能超过数组长度。 - 元素移动与空槽处理:在添加或删除元素时,需要根据增加的元素和删除的元素正确移动数组中的其他元素,注意空槽的移动。
- 更新
length
属性:操作完成后,需要更新数组的length
属性。