今天我们来手写一下js中遍历数组的方法,让你彻底搞懂这些方法的原理,我们再来使用它们时就得心应手了。建议收藏,反复阅读!字数写的最多的一次,整理不易,求个免费的赞!
1. forEach
首先第一个,我们来写一下forEach。既然要手写一个forEach,那就肯定得知道它是怎么用的,它有哪些特点。所以我们先来认识一下它。
let arr = ['a', 'b', 'c', 'd', 'e']
arr.forEach((item, index, arr) => {
console.log(item, index, arr);
})
我们有一个数组arr,我们使用forEach去遍历它。forEach会接受一个回调函数,回调函数接受3个参数,第一个参数item代表数组中的每一项,第二个参数代表数组的下标,第三个参数代表原数组。我们输出3个参数看一下。此时输出结果会是:
合情合理,一点毛病没有。我们发现输出了5个结果,那你说forEach的源代码中是不是一定有一个for循环遍历了整个数组,不然它是怎么输出5次的呢。所以forEach其实就是把for循环封装了一下给我们使用,然后在for循环的基础上增加了一些功能。
那forEach有返回值吗?我们来试一下:
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.forEach((item, index, arr) => {
console.log(item, index, arr);
})
console.log(res);
我们定义一个res为forEach的执行结果,输出看一下是否有值。
我们发现res是undefined,说明forEach是没有返回结果的,好,我们记住了。
forEach函数差不多就这几个特性,接下来,我们可以手写一个自己的forEach函数。
首先第一点,我们想让数组的实例对象能使用这个方法,那这个方法一定是写在构造函数Array的原型上的,所以我们在Array的原型上定义一个方法my_forEach,就作为我们自己的forEach方法。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_forEach = function (cb) {
}
forEach函数是会接收一个回调函数的,所以我们也在我们的my_forEach函数上接收一个回调函数,就叫cb。
接下来是不是应该用for循环去遍历arr数组啊,那我们能在my_forEach函数里直接写arr数组吗?那要是我们用过一个数组去调用这个my_forEach方法呢,那不就出问题了。那应该怎么拿到这个数组呢?你说将来这个my_forEach方法一定是会被某个数组调用的吧,那my_forEach中的this是不是就会指向调用它的数组啊,触发隐式绑定嘛。所以我们直接在my_forEach中遍历this就行了,它代表的就是调用它的数组。
所以接下来我们这样写:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_forEach = function (cb) {
for (let i = 0; i < this.length; i++) {
}
}
这样就会遍历这个数组中的每一项。那我们在循环里去做什么呢?那是不是就是去调用这个回调函数吧,将三个实参传给它,那传哪三个实参呢?我们定义这个回调函数的时候让它接收了3个形参,item、index和arr,分别代表什么你已经清楚了。所以我们只要把 this[ i ] , i 和 this 作为实参传给这个回调函数cb就行了。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_forEach = function (cb) {
for (let i = 0; i < this.length; i++) {
cb(this[i], i, this);
}
}
而因为原本的forEach函数就没有返回结果,所以我们的my_forEach也不写返回值。这样就写完了,还是比较简单的吧。现在我们来看看效果是否和原本的一样:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_forEach = function (cb) {
for (let i = 0; i < this.length; i++) {
cb(this[i], i, this);
}
}
const res = arr.my_forEach((item, index, arr) => {
console.log(item, index, arr);
})
console.log(res);
有5条输出结果并且函数最后没有返回值,和原本的效果一模一样。这样我们就手写了一个forEach出来。
2. map
写完了forEach,我们再来写一下map。
那同样的,我们想要手写一个map,那就得认识清楚一下map。
map和forEach一样,也会接收一个回调函数,回调函数会接受三个形参item、index和arr。
let arr = ['a', 'b', 'c', 'd', 'e']
arr.map((item, index, arr) => {
console.log(item, index, arr);
})
分别代表什么你已经知道了:
那它与forEach的不同之处是什么呢?map的回调函数要求我们写return,它允许我们对item进行一些操作,而且map是有返回结果的,它会返回一个新的数组,新的数组里存放的就是我们操作过的item。
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.map((item, index, arr) => {
return item + '1'
})
console.log(res, arr);
我们定义一个res为map的执行结果,在map的回调函数里,我们将item拼接一个字符串1,然后返回item。我们把res和arr都输出看一下,看看它是否会影响原数组。
得到了一个新的数组res,新数组中的每一项都是arr中每一项拼接了字符串1,并且原数组arr没有受影响。
所以map身上有这样一些特性。接下来我们就能来手写自己的map方法了,我们就叫它my_map吧。
my_map同样要写在Array的原型上,已经不需要再过多解释了,并且它也要接受一个回调函数cb。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_map = function (cb) {
}
在my_map里面我们同样要去进行for循环,在每次循环我们调用cb函数,给它传3个实参,和forEach时一样。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_map = function (cb) {
for (let i = 0; i < this.length; i++) {
cb(this[i], i, this)
}
}
而和forEach不一样的是,现在的回调函数是有返回结果的,它会返回我们操作过的item,并且my_map函数也是有返回结果的,它会返回一个新数组,新数组里存放的就是新item。所以我们要提前准备一个空数组,在每次循环时将cb的返回结果push到新数组里去,循环结束后返回这个新数组。
所以我们这样写:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_map = function (cb) {
let result = []
for (let i = 0; i < this.length; i++) {
result.push(cb(this[i], i, this))
}
return result
}
这样,我们就手写完了自己的map方法,我们来测试一下看看是否和原函数一样:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_map = function (cb) {
let result = []
for (let i = 0; i < this.length; i++) {
result.push(cb(this[i], i, this))
}
return result
}
const res = arr.my_map((item, index) => {
return item + '1'
})
console.log(res, arr);
没有毛病,输出结果一样。
3. filter
继续。下一个。我们来手写一个filter。
filter函数又有些什么样的特性呢?filter翻译过来应该是过滤的意思吧,所以它可以遍历数组把满足条件的item筛选出来。
它也会接受一个回调函数,回调函数接受3个形参,并且它的回调函数也要求我们写return,我们可以return 出来满足某个条件的item,并且filter函数也会有返回结果,它的返回结果也是一个新数组,新数组里放的就是满足条件的item。
let arr = [1, 2, 3, 4, 5, 6]
const res = arr.filter((item, index, arr) => {
return item > 3
})
console.log(res, arr);
我们在回调函数里return item > 3,看看它是否会把大于3的item筛选出来并存放到一个新数组里。我们同样输出一下res和arr。
看,res是一个新数组并且它的每一项都是大于3的,而原数组arr也没有受影响。
那我们来写一个自己的my_filter方法,你说它是不是和map函数不要太相似啊,map函数是将回调函数的返回结果push到一个新数组里,而在filter中,回调函数的执行结果会是什么?会是true or false吧。会发生隐式转换的,如果item小于3就是false,如果大于3就是true。所以我们可以利用这一点去做判断。如果条件为true,就将这一个item添加到新数组里去。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_filter = function (cb) {
let newArr = []
for (var i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
newArr.push(this[i])
}
}
return newArr
}
我们在for循环里增加一条if语句。并且同样去调用回调函数,回调函数cb(this[i], i, this)的执行结果是一个布尔类型,我们直接将它作为if语句的判断条件,如果是true说明是符合条件的,就将this[i]添加的新数组中,最后返回这个新数组。
这样我们就写完了my_filter函数,也是比较好理解的吧。我们也来测一下我们的my_filter函数。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_filter = function (cb) {
let newArr = []
for (var i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
newArr.push(this[i])
}
}
return newArr
}
const res = arr.my_filter((item, index, arr) => {
return item > 3
})
console.log(res, arr);
我们去把大于3的item筛选出来:
和map函数一样没毛病。
4. every
继续,下一个,我们再来手写一个erery函数。
那erery函数有些什么特性呢?它翻译过来是‘每一个’的意思吧,所以它是可以判断数组中的每一项是否都满足一个条件,它也接受一个回调函数,回调函数接收3个参数,回调函数也要求我们写return,并且erery函数也是有返回结果的。
我们来看一下:
let arr = [1, 2, 3, 4, 5, 6]
const res = arr.every((item, index, arr) => {
return item > 2
})
console.log(res);
我们return item > 2,意思就是看看数组中的每一项是否都大于2,那我们已经知道了,数组的第一项就小于2,所以它的输出结果是false。
那你说这个every函数中的for循环会不会每一项都遍历呢?不需要吧,只要找到了一个不满足条件的输出结果就是false了,那就不需要接着遍历了,所以every函数中的for循环执行次数不固定,是根据回调函数的结果决定的。
了解完了这些,我们就来写我们的my_every函数。这个函数虽然有返回结果但是是布尔类型,所以就不需要准备一个空数组了。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_every = function (cb) {
for (let i = 0; i < this.length; i++) {
}
}
那我们在for循环中也要去调用cb函数,cb函数的返回结果要么是true要么是false,我们也可以利用这一点,只要cb函数的执行结果出现了false,说明就有一项不满足条件,我们就直接返回false,不继续遍历了;如果for循环结束了cb函数的执行结果都是true,说明数组中的每一项都符合条件,我们就返回true。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_every = function (cb) {
for (let i = 0; i < this.length; i++) {
if (!cb(this[i], i, this)) {
return false;
}
}
return true;
}
这样就写完了,我们也来测一下:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_every = function (cb) {
for (let i = 0; i < this.length; i++) {
if (!cb(this[i], i, this)) {
return false;
}
}
return true;
}
const res = arr.my_every((item, index, arr) => {
return item > 0
})
console.log(res);
我们看看数组中的每一项是否都大于0,输出结果应该是true:
我们再来看看每一项是否都大于2:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_every = function (cb) {
for (let i = 0; i < this.length; i++) {
if (!cb(this[i], i, this)) {
return false;
}
}
return true;
}
const res = arr.my_every((item, index, arr) => {
return item > 2
})
console.log(res);
输出结果是false没错。
5. some
写完了every当然要来写它的兄弟some了,一个是every一个是some,应该猜都能猜到它的作用是什么吧。ervey函数是判断数组中每一项是否都满足条件,那some函数就是判断数组中是否有一项满足条件,只要有一项满足条件我就是true。
let arr = [1, 2, 3, 4, 5, 6]
const res = arr.some((item, index, arr) => {
return item > 3
})
console.log(res);
我们return item > 3,输出结果应该是什么呀?是true吧,只要数组中有大于3的就是true,除非你每一项都不满足条件。
所以它的代码简直和every一模一样吧,只要改一下判断条件,只要cb函数的执行结果出现了true,说明就有一项满足条件,直接返回true,就不用接着遍历了。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_some = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return true;
}
}
return false;
}
我们也来测一下:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_some = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return true;
}
}
return false;
}
const res = arr.my_some((item, index, arr) => {
return item > 3
})
console.log(res);
我们来判断一下数组中是否有大于3的,输出结果应该是true:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_some = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return true;
}
}
return false;
}
const res = arr.my_some((item, index, arr) => {
return item > 6
})
console.log(res);
再来判断一下数组中是否有大于6的,输出结果应该是false:
6. reduce
我们再来手写一下reduce,这个函数就比较特殊了,相对前面几个也比较复杂,我们一起来看一下。
首先,我们来看一下它是怎么用的,再来搞清楚它身上的一些特性,动起手来才会比较好写。
let arr = [1, 2, 3, 4, 5, 6]
arr.reduce((pre, item, index, arr) => {
console.log(pre, item, index, arr);
}, 0)
reduce函数它和前面几个不一样的是,它除了能接收一个回调函数外,它还能接收第二个参数,我们就随便写个0。并且它接收的回调函数中有4个参数,多了一个pre。这个pre是什么呢?pre的初始值会被赋值为你传进来的第二个参数。我们输出这四个参数看一下:
你看,第一条输出结果的pre是0吧,然后是item、index和arr。那为什么从第二条输出结果开始pre的值就变成undefined了呢?这是因为我们在回调函数里没有写return,reduce函数的回调函数也要求我们写return,并且return得到的结果会重新赋值给pre。
let arr = [1, 2, 3, 4, 5, 6]
arr.reduce((pre, item, index, arr) => {
console.log(pre, item, index, arr);
return pre + item;
}, 0)
我们return pre + item,再输出这四个参数看看:
你看,每一条输出结果的pre都有值,并且因为我们return的是pre + item,所以pre的值是上一次pre和item的和。所以当我们没有写return时,回调函数的执行结果就是undefined,又因为回调函数的执行结果会重新赋给pre,所以之后pre就变成undefined了。
并且reduce函数也是有返回结果的,我们可以输出一下看看:
let arr = [1, 2, 3, 4, 5, 6]
const res = arr.reduce((pre, item, index, arr) => {
console.log(pre, item, index, arr);
return pre + item;
}, 0)
console.log(res);
我们发现res的值是21,就是最后一次循环结束后pre的值吧,你看最后一条输出结果,pre是15,item是6,相加就是21,在最后一次循环重新赋给pre,所以最后pre的值是21,reduce函数的执行结果就是pre。
所以回调函数中 return 的值赋会给pre, 当数组遍历结束,返回最终的pre的值。
那要是我们不传第二个参数呢?
let arr = [1, 2, 3, 4, 5, 6]
arr.reduce((pre, item, index, arr) => {
console.log(pre, item, index, arr);
return pre + item;
})
我们没有传第二个参数,再输出这四个参数看看:
我们发现就只有5条输出结果了,并且你看第一条输出结果,pre的值为1,item的值为2。这说明什么?说明当我们没有传第二个参数时,pre的初始值被赋值为数组的第一个值了,然后少执行了一次循环,从数组的第二项开始循环了。ok,我们记下了,当时候写我们自己的reduce函数时得考虑这一点。
我们再来多探讨一下reduce函数的特性,你说当我们传一个undefined作为reduce函数的第二个参数时,它是会被当作传了一个参数呢,还是当作没传参数呢?如果它当作传了一个参数,那输出结果就会有6条;如果当作没传,那输出结果就会是5条。我们来试一下:
let arr = [1, 2, 3, 4, 5, 6]
arr.reduce((pre, item, index, arr) => {
console.log(pre, item, index, arr);
return pre + item;
}, undefined)
我们发现,输出结果是6条,说明当我们传一个undefined时,它也是被当作传了一个参数的,pre的初始值就是undefined了,所以第一条输出结果pre是undefined。那之后pre的值怎么变成NaN了呀,我们简单分析一下就能知道。
当我们传一个undefined时,pre的初始值就是undefined,所以第一条输出结果中的pre就是undefined。之后因为我们return pre + item,那undefined加上一个数字会是什么呢?会发生隐式转换吧,undefined会被转换成NaN,那NaN加一个数字自然就是NaN了,所以这个NaN又被重新赋给pre,所以之后pre的值就都是NaN了。
reduce函数身上的特性差不多就这些,有许多我们需要考虑的点。接下来,我们就来手写一个自己的my_reduce。
我们先来考虑当我们传了一个参数并且不是undefined时应该怎么写:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_reduce = function (cb, start) {
for (let i = 0; i < this.length; i++) {
}
}
my_reduce接收两个参数,一个回调函数,一个参数我们就叫start。然后去进行for循环,在for循环里我们去调用回调函数,给它传四个形参,start, this[i], i, this,这样pre的初始值就是start,没问题。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_reduce = function (cb, start) {
for (let i = 0; i < this.length; i++) {
cb(start, this[i], i, this)
}
}
然后回调函数是有返回结果的,这个返回结果会被重新赋给pre,所以我们将回调函数的返回结果赋给start,这样start的值就被更新了,start代表的就是pre嘛。最后循环结束我们返回start。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_reduce = function (cb, start) {
for (let i = 0; i < this.length; i++) {
start = cb(start, this[i], i, this);
}
return start;
}
这是当我们传了一个参数时,那要是我们没传参数呢?我们是不是就去判断start是否为undefined吧,但会有个问题,那要是我传的就是undefined呢?如果我们去判断start是否为undefined来判断传没传值,那当我真的传一个undefined的话,就会漏掉这种情况吧。
那应该怎么办呢?当我们没传值时不能使用undefined来判断,那怎么判断呢?你还记不记得这样一个东西:...args。它叫rest参数,当我们把它写在函数的括号里时,它会帮我们收集函数剩余的参数存放到args数组里。那如果我们没传参数,args是不是就是一个空数组;如果我们传了一个undefined,这个args数组里也会帮我们放一个undefined,args就不是空数组了。所以我们可以以args是否为空数组去判断是否传了一个值。
所以我们这样写:
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_reduce = function (cb, ...args) {
let start
if (args.length) {
start = args[0]
} else {
start = this[0];
}
for (let i = 0; i < this.length; i++) {
start = cb(start, this[i], i, this);
}
return start;
}
我们第二个参数放上 ...args,然后去判断args的length,如果有长度,说明传了一个值,就将args[0] 赋给start;如果没有长度,说明没有传值,就将this[0] 赋给start。
这样就写完了吗?还记不记得当我们没传参数时,循环只执行了5次,会从数组的第二项开始循环。所以我们还要准备一个index初始值为0,当我们没有传参数时,就将index改为1,然后让 i = index 就行了。
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.my_reduce = function (cb, ...args) {
let start, index = 0
if (args.length) {
start = args[0]
} else {
start = this[0];
index = 1
}
for (let i = index; i < this.length; i++) {
start = cb(start, this[i], i, this);
}
return start;
}
这样当我们没有传参数时,循环就会从第二项开始。
这样这个my_reduce函数我们就写完了,而且我们还考虑了各种情况。各位可以自行去测试一下,应该没有什么问题,在这里我就不去写测试结果了。
7. findIndex
我们继续下一个,findIndex。你看着它的字面意思猜一下它的作用,就是找下标吧。
所以它的作用很简单,就是去找一个数组里是否存在这个值,找到了就返回它的下标,没找到就返回-1。我们来看一下:
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.findIndex((item, i, arr) => {
return item === 'd'
})
console.log(res);
它也要求我们在回调函数里写return,并且findIndex有返回结果,你已经知道了返回结果会是什么,是我们要找到元素的下标。所以在这里我们找一下 'd' 。arr中是存在 'd' 的,所以它应该输出3。
没问题。我们再去找一下 ‘f’ 。arr中没有 ‘f’ ,所以它会返回-1。
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.findIndex((item, i, arr) => {
return item === 'f'
})
console.log(res);
返回-1没问题。我们来写一个自己的my_findIndex。应该很好写吧,去循环遍历数组,找到了就返回下标就行了。回调函数的返回结果会是一个布尔类型,所以我们将它作为if的判断条件。如果为true,就返回下标;如果循环结束了if都没有走进了就返回-1。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_findIndex = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return i
}
}
return -1
}
这样就行了。我们来测试一下:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_findIndex = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return i
}
}
return -1
}
const res = arr.my_findIndex((item, i, arr) => {
return item === 'a'
})
console.log(res);
我们去找 'a' ,应该返回0。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_findIndex = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return i
}
}
return -1
}
const res = arr.my_findIndex((item, i, arr) => {
return item === 'f'
})
console.log(res);
我们去找 'f' 。应该返回-1。
8. find
我们再来手写findIndex的兄弟 find,它和findIndex效果一样,都是去找数组中的某一个值。findIndex是返回下标,find就是返回这个要找的值。
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.find((item, i, arr) => {
return item === 'd'
})
console.log(res);
我们找 'd':
返回d。
我们要找'f':
直接返回undefined。
那这简直不要太好写吧,直接把my_findIndex的代码复制过来改一下就好了:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_find = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return this[i];
}
}
return undefined;
}
当if走进来了返回this[i]。循环结束都没有走进来返回undefined。很简单,各位可以自行测试一下。
9. includes
我们继续下一个includes。
includes是怎么用的呢?它就和前面几个都不一样了。它不接收一个回调,直接接收一个参数。它也是去判断数组中是否存在一个值,如果存在返回true,否则返回false。
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.my_includes('a')
console.log(res);
我们去找'a':
返回true。
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.my_includes('f')
console.log(res);
我们去找'f':
返回false。
它还能接收第二个参数,第二个参数代表要从哪个下标开始找:
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.includes('a', 0)
console.log(res);
我们从下标0开始找,应该返回true:
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.includes('a', 1)
console.log(res);
我们从下标1开始找,应该返回false:
那要是我们传一个字符串0呢?
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.includes('a', '0')
console.log(res);
也是true,说明它会将第二个参数转换为数字类型。那要是传一个字符串'hello'呢?
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.includes('a', 'hello')
console.log(res);
它也是true,'hello'转换成数字是NaN吧,那传一个NaN呢?
let arr = ['a', 'b', 'c', 'd', 'e']
const res = arr.includes('a', NaN)
console.log(res);
它也是true。说明当第二个参数没传或者转换成数字类型是NaN的话,它就默认从0开始找;当第二个参数传了一个数字或者转换成数字类型后是一个数字,就从这个数字下标开始找。
所以我们动手来写自己的my_includes。
这里我们也不能通过undefined去判断第二个参数传没传,所以也要用...args收集第二个参数。
所以我们这样写:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item, ...args) {
for (let i = args[0]; i < this.length; i++) {
if (this[i] === item) return true;
}
return false
}
当第二个参数传的是一个数字时,我们可以直接让i = args[0],然后去循环。但这样写显然考虑不够,如果没传或者传了一个字符串呢?所以我们要对args[0] 进行类型判断:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item, ...args) {
let index
if (args.length && !Number.isNaN(Number(args[0]))) {
index = Number(args[0])
} else {
index = 0;
}
for (let i = index; i < this.length; i++) {
if (this[i] === item) return true;
}
return false
}
我们将args[0] 先转换成数字类型,然后去判断它是不是NaN,如果不是NaN且数组长度存在,就说明传了第二个参数,且第二个参数要么就是数字要么转换成数字后是数字,我们就让index = Number(args[0]);否则就当作没传,让index = 0。然后让i = index就行了。
这样就写完了,我们可以来测试一下:
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item, ...args) {
let index
if (args.length && !Number.isNaN(Number(args[0]))) {
index = Number(args[0])
} else {
index = 0;
}
for (let i = index; i < this.length; i++) {
if (this[i] === item) return true;
}
return false
}
const res = arr.my_includes('a', '0')
console.log(res);
返回true,没问题。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item, ...args) {
let index
if (args.length && !Number.isNaN(Number(args[0]))) {
index = Number(args[0])
} else {
index = 0;
}
for (let i = index; i < this.length; i++) {
if (this[i] === item) return true;
}
return false
}
const res = arr.my_includes('a', 'hello')
console.log(res);
也返回true,因为'hello'被转换成数字是NaN,而NaN默认当作没传。没传就从下标0开始找,所以是true。
这样我们就写完了我们的my_includes方法。
10. splice
终于来到我们今天的最后一个函数了,splice。作为压轴出场,它是我们今天这些函数中最复杂的一个,我们一起来看一下。
splice函数既可以删除数组中的元素也可以增加一个元素到数组中。
当一个参数都不传时:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(), arr);
直接返回一个空数组,原数组arr不受影响。
当传一个参数时:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(1), arr);
我们只传了一个1,它就会从下标1开始切到数组最后一位,存放到一个新数组中返回,并且原数组也受影响。
当我们传两个参数时:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(1, 2), arr);
第一个参数代表从哪个下标开始切,第二个参数代表切几个。我们传了一个1一个2,它就会从下标1开始切两个存放到一个新数组返回,并且原数组受影响。如果第二个参数是0,那就代表切0个,就不会切,就会返回一个空数组。
它还可以传3个参数,那就是往数组中添加值了:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(1, 1, 'x', 'y'), arr);
从下标1开始切1个,并将'x', 'y'添加到原数组的下标1身上。
如果第一个参数传undefined:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(undefined, 1), arr);
那就会默认从下标0开始切。
如果第二个参数传undefined:
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.splice(1, undefined), arr);
那就是切0个。
好,开始写我们自己的my_splice。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
}
我们定义三个形参,start就代表从哪个下标开始切,len就代表切几个,...args用来收集要添加的元素。
首先,当一个参数都没传时,我们不切,直接返回空数组。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
// 如果没有任何参数传递,直接返回空数组
if (arguments.length === 0) {
return [];
}
}
arguments可以用来获取函数接收的所有参数,存放到arguments数组里。所以我们去判断它的长度,长度为0时代表一个参数都没传,直接返回空数组。
当start传的是undefined时,会默认从0开始切,所以当start为undefined时,我们设为0。当len传的是undefined时,代表切0个,所以我们也设为0。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
// 如果没有任何参数传递,直接返回空数组
if (arguments.length === 0) {
return [];
}
// 如果 start 是 undefined,设置为 0
if (start === undefined) {
start = 0;
}
// 如果 len 是 undefined,设置为 0
if (len === undefined) {
len = 0;
}
}
接下来就开始循环遍历数组了。我们来想一想怎么从数组身上切值下来,并且原数组也要受影响。我们可以提前准备两个数组res和newArr。当 i 循环 到start 和 start+len(左闭右开) 中的值时,我们就push到res中作为返回的新数组,当 i 不在这里面说明不是要切的值,我们就push到newArr中作为原数组。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
// 如果没有任何参数传递,直接返回空数组
if (arguments.length === 0) {
return [];
}
// 如果 start 是 undefined,设置为 0
if (start === undefined) {
start = 0;
}
// 如果 len 是 undefined,设置为 0
if (len === undefined) {
len = 0;
}
const res = [], newArr = []
for (let i = 0; i < this.length; i++) {
if (i < start || i >= start + len) {
newArr.push(this[i])
} else {
res.push(this[i])
}
}
}
还有要添加的值是存放在args里的,所以我们还要把args中的值添加到原数组中去。那什么时候添加呢?因为start是我们要切的第一个值,所以我们要从 tart-1 后面开始往原数组中添加值。所以当i === start - 1我们把args中的所有值都push到newArr中。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
// 如果没有任何参数传递,直接返回空数组
if (arguments.length === 0) {
return [];
}
// 如果 start 是 undefined,设置为 0
if (start === undefined) {
start = 0;
}
// 如果 len 是 undefined,设置为 0
if (len === undefined) {
len = 0;
}
const res = [], newArr = []
for (let i = 0; i < this.length; i++) {
if (i < start || i >= start + len) {
newArr.push(this[i])
} else {
res.push(this[i])
}
if (i === start - 1) {
newArr.push(...args)
}
}
}
那接下来就是把newArr赋会给原数组,再返回新数组。
let arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_splice = function (start, len, ...args) {
// 如果没有任何参数传递,直接返回空数组
if (arguments.length === 0) {
return [];
}
// 如果 start 是 undefined,设置为 0
if (start === undefined) {
start = 0;
}
// 如果 len 是 undefined,设置为 0
if (len === undefined) {
len = 0;
}
const res = [], newArr = []
for (let i = 0; i < this.length; i++) {
if (i < start || i >= start + len) {
newArr.push(this[i])
} else {
res.push(this[i])
}
if (i === start - 1) {
newArr.push(...args)
}
}
while (this.length) {
this.pop()
}
this.push(...newArr)
return res
}
这样就写完了,应该没有什么大问题。各位可以自行去测试一下,如果还有没考虑到的情况,各位可以指正一下。写完这篇文章我已经浑身无力了,如果对你有帮助的话请点个赞吧,感谢!