目录
1 Array.at()
1.1基本介绍
接收一个整数值,返回对应索引的元素,允许正数和负数。负数从-1为数组最后一个元素开始倒数,语法如下:
Array.at(index)
边界条件与输入输出、注意事项如下所示:
输入:输入index,首先被转换为整数。
输出:如果index > length 或者 -index < -length 返回undefined,正数就返回对应下标元素,负数返回length +index对应下标元素。
注意事项:at()方法是通用的。
1.2 手写实现
手写实现代码如下所示:
// 1. 手写at
// 处理空槽时返回undefined
class MyArray extends Array {
}
MyArray.prototype.at = function(index){
index = Number(index)
const length = this.length
if(index >= length || -index < -length) return undefined
if(index < 0) return this[index + length]
else return this[index]
}
const arr_1 = new MyArray(1,2,3)
console.log(arr_1) // output: [ 1, 2, 3 ]
console.log(arr_1.at(1)) // output: 2
console.log(arr_1.at(-1)) // output: 3
console.log(arr_1.at(3)) // output: undefined
console.log(arr_1.at(-4)) // output: undefined
console.log(arr_1.at("-1")) // output: 3
const arr_3 = new MyArray(4)
console.log(arr_3.at(1)) // output: undefined
const arr_2 = new Array(5)
console.log(arr_2.at(1)) // output: undefined
- 将索引转换为数组
- 判断索引范围的合法性
- 根据索引正负返回对应下标元素
2 Array.concat()
2.1 基本介绍
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
concat()
concat(value0)
concat(value0, value1)
concat(value0, value1, /* … ,*/ valueN)
输入: 数组/值,类数组(含有类数组索引和length属性)
输出:返回一个新的数组实例。
注意事项:通用的,是一种复制方法(不改变原数组),返回的是原始参数的浅拷贝。面对[Symbol.isConcatSpreadable]属性设置为真的数组或类数组对象,参数的每个元素将会独立的添加到最终数组当中。普通值将会直接放入新数组当中。如果原数组是稀疏数组将会保留空槽。
2.2 手写实现-获取构造函数与concat实现
因为concat需要返回新的数组实例,所以编写一个函数专门用于获取数组的构造函数,其基本逻辑为下:
- 首先检查[Symbol.species]属性返回的构造函数是否满足条件,满足则返回,否则进入下一步判断。
- 其次检查默认的构造函数,满足则返回,否则进入下一步判断。
- 最后都不满足就返回默认的构造器Array。
const getConstructor = (obj) => {
const defaultConstructor = Array
if(obj[Symbol.species] !== undefined){
if(typeof obj[Symbol.species] === "function"){
return obj[Symbol.species]
}else{
throw new TypeError("Symbol.species must be a constructor")
}
}else if(obj.constructor !== undefined){
if(typeof obj.constructor === "function"){
return obj.constructor
}else{
return defaultConstructor
}
}
}
实现了获取构造函数的函数之后就可以编写concat函数了,如下所示:
MyArray.prototype.concat = function(...args){
// 通过Symbol.species获取当前类的构造函数
const con = getConstructor(this)
const res = new con([])
// 将原始数组元素加入新数组
for(let i = 0; i < this.length; i ++){
if(i in this){
res.push(this[i])
}else{
// 如果原始数组有空槽,新数组长度加1
res.length ++
}
}
// 遍历数组元素
for(let i = 0; i < args.length; i ++){
// 如果是concat可展开(类)数组对象或者数组
if((typeof args[i] === "object" && args[i][Symbol.isConcatSpreadable]) || Array.isArray(args[i])){
// 遍历展开添加元素
for(let j = 0; j < args[i].length; j ++){
if(j in args[i]){
res.push(args[i][j])
}else{
res.length ++
}
}
}else{
res.push(args[i])
}
}
return res
}
const notArray = {
0: "hello",
1: "world",
length: 2,
[Symbol.isConcatSpreadable]: true
}
const arr_4 = new MyArray(1,2,3)
const arr_5 = new MyArray(4,5,6)
const arr_6 = arr_4.concat(arr_5, 7, [8, ,[9]], notArray)
console.log(arr_6) // output: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 'hello', 'world'
console.log(arr_6 instanceof MyArray) // output: true
注意以下几点:
- 注意返回实例类型与调用实例类型的一致性,通过getConstructor函数实现。
- 空槽的处理,通过直接增加结果数组的长度来添加空槽。
- 需要展开处理的数组或者类数组的判断条件,如果目标是对象且[Symbol.isConca tSpreadable]设置为true或者是数组的条件下都要进行展开操作。
3 Array.copyWithin()
3.1 基本介绍
copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它。语法如下所示:
copyWithin(target)
copyWithin(target, start?)
copyWithin(target, start?, end?)
输入:target序列开始替换的下标位置。 start是要复制元素的其实位置,默认为0。end为要复制元素的结束位置(不包含end)。
输出:修改后的数组。
注意事项:
- copyWithin方法是修改方法(修改原数组内容,不改变长度),保留空槽,通用的。
- target的有效性,首先将被转换为整数,负数索引需要加上length,如果还是小于0那么使用0。如果正数索引大于length不会拷贝任何内容。复制不会扩展数组,即使target在start后面的情况下。
- start的有效性,首先将被转换为整数,默认为0。索引处理与target一致。
- end的有效性,首先将被转换为整数,负数索引的处理与target一致。如果end大于length或者省略默认为length。end在start之前不会拷贝任何内容。
3.2 手写实现
如下所示是首先copyWithin的代码示例:
// 3. 手写copyWithin
MyArray.prototype.copyWithin = function(target, start = 0, end = this.length){
// 索引转换为数字
target = Number(target)
start = Number(start)
end = Number(end)
const handleIndex = (index, length) => {
if(index < 0) return Math.max(length + index, 0)
else return Math.min(index, length)
}
const length = this.length
// 统一处理索引
target = handleIndex(target, length)
start = handleIndex(start, length)
end = handleIndex(end, length)
// 处理不需要修改的边界情况
if(target >= length || start >= length || end < start) return this
// 开始进行copyWithin, 首先创建副本
const copy = this.slice(start, end)
for(let i = 0; i < copy.length && (target + i < length); i ++){
this[target + i] = copy[i]
}
return this
}
// 测试样例
const arr_7 = [1, 2, , , 5]
console.log(arr_7) // output: [ 1, 2, <2 empty items>, 5 ]
console.log(arr_7.copyWithin(0, 4)) // output: [ 5, 2, <2 empty items>, 5 ]
console.log(arr_7.copyWithin(1, 4)) // output: [ 5, 5, <2 empty items>, 5 ]
console.log(arr_7.copyWithin(2, "4", 5)) // output: [ 5, 5, 5, <1 empty item>, 5 ]
// 类数组对象copyWithin
const notArray_1 = {
0: "hello",
1: "world",
length: 2
}
console.log(Array.prototype.copyWithin.call(notArray_1, 0, 1)) // output: { '0': 'world', '1': 'world', length: 2 }
// 边界条件测试
console.log(arr_7.copyWithin(0, 5)) // output: [ 5, 5, 5, <1 empty item>, 5 ]
需要注意:
- 三种索引都使用统一的处理方法
- 边界情况在处理索引完成后一次进行
- 在进行copy过程中需要使用slice方法来支持稀疏数组
- 判断条件还要包含是否原数组不越界
4 Array.entries()
4.1 基本介绍
entries() 方法返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对。语法如下所示:
entries()
输入值:空
输出值:一个新的包含可迭代器对象,该对象返回value为数组的键值对。
注意事项:
- 稀疏数组的空槽处理为undefined
- 通用的方法,非数组对象满足条件即可调用
4.2 手写实现
4.2.1 手写实现返回迭代器对象
entries方法要求返沪一个迭代器对象,迭代器对象要求实现迭代器协议[Symbol.iterator],该属性下有迭代器方法,返回一个对象,该对象有next方法,每次调用返回一个对象,包含value和done属性,首先不使用generator实现,如下所示;
MyArray.prototype.entries = function(){
const arr = this
let index = 0
return {
[Symbol.iterator]: ()=>{
return {
next(){
if(index < arr.length){
const res = {value:[index, arr[index]], done:false}
index ++
return res
}else{
return {done:true}
}
}
}
}
}
}
- 使用闭包计数,index是外层函数的变量。
- 迭代器协议下的方法需要返回一个对象,对象拥有next方法,每次调用但会含有value和done属性的对象。
4.2.2 使用generator实现迭代器对象
使用generator函数能够更简洁的实现上述功能,如下所示:
// 4. 手写entries-使用generator
MyArray.prototype.entries = function(){
const arr = this
return {
[Symbol.iterator]: () =>{
function* gen(){
for(let i = 0; i < arr.length; i ++){
yield [i, arr[i]]
}
}
return gen()
}
}
}
// 测试样例
const a = new MyArray("a", "b", "c");
for (const [index, element] of a.entries()) {
console.log(index, element);
}
// 0 'a'
// 1 'b'
// 2 'c'
// 空槽处理
let arr_8 = new MyArray("a")
arr_8 = arr_8.concat([,"b"])
for (const element of arr_8.entries()) {
console.log(element);
}
// [ 0, 'a' ]
// [ 1, undefined ]
// [ 2, 'b' ]
// 类数组对象entries
const arrayLike = {
length: 3,
0: "a",
1: "b",
2: "c",
};
for (const entry of MyArray.prototype.entries.call(arrayLike)) {
console.log(entry);
}
// [ 0, 'a' ]
// [ 1, 'b' ]
// [ 2, 'c' ]
注意:
- 使用generator时done属性由generator管理不需要我们去返回一个含done和value属性的对象
5 Array.every()
5.1 基本介绍
every() 方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。语法如下所示:
every(callbackFn)
every(callbackFn, thisArg)
输入:callFn(element, index, array)用于测试元素的执行的函数,应当返回一个真值或假值。thisArg指定回调函数中的this行为。
输出:如果每个元素对象通过测试返回真值则为true,只要有一个不通过则为false。
注意事项:
- 遇到一个测试对象为假值则停止测试
- 对于空数字的测试将始终为真
- 对于稀疏数组,空槽不会调用callFn
- callFn的操作会改变数组元素,并影响后续测试
- 这是一个通用方法
5.2 手写实现
手写实现代码如下所示:
// 手写实现every
// 难点处理稀疏数组
// thisArg处理
MyArray.prototype.every = function(callFn, thisArg = this){
if(typeof callFn !== "function"){
throw new TypeError(callFn + ' is not a function');
}
for(let i = 0; i < this.length; i ++){
// 检查当前索引是否存在于数组中,跳过空槽
if (!(i in this)) continue;
if(!callFn.call(thisArg, this[i], i, this)) return false
}
return true
}
function isBigEnough(element, index, array) {
return element >= 10;
}
const arr_9 = new MyArray(12, 5, 8, 130, 44)
const arr_10 = new MyArray(12, 54, 18, 130, 44)
console.log(arr_9.every(isBigEnough)) // false
console.log(arr_10.every(isBigEnough)) // true
console.log(MyArray.prototype.every.call([1, , 3], (x) => x !== undefined)) //true
console.log(MyArray.prototype.every.call([2, , 2], (x) => x === 2)) //true
const arrayLike_1 = {
length: 3,
0: "a",
1: "b",
2: "c",
};
console.log(
MyArray.prototype.every.call(arrayLike_1, (x) => typeof x === "string"),
); // true
难点总结如下:
- thisArg理解为改变callFn(不能为箭头函数,箭头函数没有自己的this)中的this行为,所以使用call,apply都可以
- 理解如何处理空槽,使用 in 操作符来判断,如果为false则直接跳过该轮回调函数调用