当前位置: 首页 > news >正文

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.lengthif(index >= length || -index < -length) return undefinedif(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 = Arrayif(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{// 如果原始数组有空槽,新数组长度加1res.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 = thislet index = 0return {[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 = thisreturn {[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)) // trueconsole.log(MyArray.prototype.every.call([1, , 3], (x) => x !== undefined)) //true
console.log(MyArray.prototype.every.call([2, , 2], (x) => x === 2)) //trueconst 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则直接跳过该轮回调函数调用

http://www.mrgr.cn/news/42110.html

相关文章:

  • 风格迁移项目一:如何使用
  • JQuery基本介绍和使用方法
  • CF687D Dividing Kingdom II 题解
  • 简历制作面试篇
  • pdb个人记录
  • 【算法系列-链表】交换链表节点(反转 + 交换)
  • Spring MVC的运行流程详解
  • Netty:高性能异步网络编程框架全解析
  • Python中重载操作符实现管道操作
  • python全栈学习记录(二十一)类的继承、派生、组合
  • 【C++】vector类的模拟实现
  • QT入门知识----2024.8.21-9.28
  • 如何降低接口的响应时间(RT)
  • 滚雪球学Oracle[5.2讲]:数据库备份与恢复基础
  • Servlet的生命周期及用户提交表单页面的实现(实验报告)
  • 【c++】反证法证明为什么c++不能像JavaScript的typeof那样自动判断数据类型
  • [题解] [SDOI2011] 消防
  • Prometheus之Pushgateway使用
  • 【洛谷】AT_dp_m Candies 的题解
  • 复杂问题分析思维训练