自学内容网 自学内容网

Javascript数组研究02_手写实现_at_concat_copyWithin_entries_every

目录

1 Array.at()

1.1基本介绍

1.2 手写实现

2 Array.concat()

2.1 基本介绍

2.2 手写实现-获取构造函数与concat实现

3 Array.copyWithin()

3.1 基本介绍

3.2 手写实现

4 Array.entries()

4.1 基本介绍

4.2 手写实现

4.2.1 手写实现返回迭代器对象

4.2.2 使用generator实现迭代器对象

5 Array.every()

5.1 基本介绍

5.2 手写实现


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
  1.  将索引转换为数组
  2.  判断索引范围的合法性
  3. 根据索引正负返回对应下标元素

2 Array.concat()

2.1 基本介绍

        concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

concat()
concat(value0)
concat(value0, value1)
concat(value0, value1, /* … ,*/ valueN)

输入: 数组/值,类数组(含有类数组索引和length属性)

输出:返回一个新的数组实例。

注意事项:通用的,是一种复制方法(不改变原数组),返回的是原始参数的浅拷贝。面对[Symbol.isConcatSpreadable]属性设置为真的数组或类数组对象,参数的每个元素将会独立的添加到最终数组当中。普通值将会直接放入新数组当中。如果原数组是稀疏数组将会保留空槽。

2.2 手写实现-获取构造函数与concat实现

        因为concat需要返回新的数组实例,所以编写一个函数专门用于获取数组的构造函数,其基本逻辑为下:

  1. 首先检查[Symbol.species]属性返回的构造函数是否满足条件,满足则返回,否则进入下一步判断。
  2. 其次检查默认的构造函数,满足则返回,否则进入下一步判断。
  3. 最后都不满足就返回默认的构造器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

        注意以下几点:

  1.  注意返回实例类型与调用实例类型的一致性,通过getConstructor函数实现。
  2. 空槽的处理,通过直接增加结果数组的长度来添加空槽。
  3.  需要展开处理的数组或者类数组的判断条件,如果目标是对象且[Symbol.isConca tSpreadable]设置为true或者是数组的条件下都要进行展开操作。

3 Array.copyWithin()

3.1 基本介绍

        copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它。语法如下所示:

copyWithin(target)
copyWithin(target, start?)
copyWithin(target, start?, end?)

输入:target序列开始替换的下标位置。 start是要复制元素的其实位置,默认为0。end为要复制元素的结束位置(不包含end)。

输出:修改后的数组。

注意事项:

  1. copyWithin方法是修改方法(修改原数组内容,不改变长度),保留空槽通用的
  2. target的有效性,首先将被转换为整数,负数索引需要加上length,如果还是小于0那么使用0。如果正数索引大于length不会拷贝任何内容。复制不会扩展数组,即使target在start后面的情况下。
  3. start的有效性,首先将被转换为整数,默认为0。索引处理与target一致。
  4. 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 ]

需要注意:

  1.  三种索引都使用统一的处理方法
  2. 边界情况在处理索引完成后一次进行
  3. 在进行copy过程中需要使用slice方法来支持稀疏数组
  4. 判断条件还要包含是否原数组不越界

4 Array.entries()

4.1 基本介绍

        entries() 方法返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对。语法如下所示:

entries()

输入值:空

输出值:一个新的包含可迭代器对象,该对象返回value为数组的键值对。

注意事项:

  1. 稀疏数组的空槽处理为undefined
  2. 通用的方法,非数组对象满足条件即可调用

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}
                    }
                }
            }
        }
    }
}
  1. 使用闭包计数,index是外层函数的变量。
  2. 迭代器协议下的方法需要返回一个对象,对象拥有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' ]

 注意:

  1. 使用generator时done属性由generator管理不需要我们去返回一个含done和value属性的对象

5 Array.every()

5.1 基本介绍

        every() 方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。语法如下所示:

every(callbackFn)
every(callbackFn, thisArg)

输入:callFn(element, index, array)用于测试元素的执行的函数,应当返回一个真值或假值。thisArg指定回调函数中的this行为。

输出:如果每个元素对象通过测试返回真值则为true,只要有一个不通过则为false。

注意事项: 

  1. 遇到一个测试对象为假值则停止测试
  2. 对于空数字的测试将始终为真
  3. 对于稀疏数组,空槽不会调用callFn
  4. callFn的操作会改变数组元素,并影响后续测试
  5. 这是一个通用方法

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

        难点总结如下:

  1. thisArg理解为改变callFn(不能为箭头函数,箭头函数没有自己的this)中的this行为,所以使用call,apply都可以
  2. 理解如何处理空槽,使用 in 操作符来判断,如果为false则直接跳过该轮回调函数调用

原文地址:https://blog.csdn.net/qq_33546823/article/details/142686214

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!