【无标题】

常用数组方法整理

最近写项目总是会处理一些很多的数据,而后端传过来的数据基本上都是数组的类型,所有避免不了我们去对这些数据进行处理,这里我列举了一些日常里常用的数组方法。

首先我们先来了解一下数组的一些常用方法

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

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。 (通常用它判断数组中有没有这个元素)

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

join() 方法将一个数组(或一个 类数组对象 ) 的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。

pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。

unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。

sort() 方法用 原地算法 对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的

常用的数组循环方法

forEach() 方法对数组的每个元素执行一次给定的函数

map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

reduce() 方法对数组中的每个元素执行一个由您提供的reducer 函数(升序执行),将其结果汇总为单个返回值。

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

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

some() 方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个 Boolean 类型的值。

以上所有注解均来自 MDN

先来对常用的数组循环方法进行解刨

1、Array.forEach()

forEach() 方法对数组的每个元素执行一次给定的函数。

参数:forEach()接收两个参数

参数1:callback 为数组中每个元素执行的函数,该函数接收一至三个参数:

item 数组中正在处理的当前元素
index 数组中正在处理的当前元素的索引
array forEach() 方法正在操作的数组
参数2:thisArg 可选

可选参数。当执行回调函数 callback 时,用作 this 的值。

返回值:undefined

问:forEach() 能直接修改原数组?

场景1

const arr = [1, 2, 3]

arr.forEach(item => {
  item = item + 1
  console.log(item);
});

console.log(arr);

可以看到并没有修改原数组的元素

场景2

const arr = [1, 2, 3]

arr.forEach((item, index) => {
  arr[index] = item + 1
  console.log(item);
});

console.log(arr);

回到问题:

可以看到forEach() 虽然无法直接修改原数组的元素,但forEach() 可以间接修改原数组的值,所以这个问题是个坑

面试回答:forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。

问:forEach() 能否修改内部 this 的指向

场景1

function Counter() {
  this.sum = 0;
  this.count = 0;
}

Counter.prototype.add = function (array) {
  array.forEach(function (entry) {
    console.log(this);
  })
}

const obj = new Counter();
obj.add([1]);

此时可以看到打印的是 Window

场景2

function Counter() {
  this.sum = 0;
  this.count = 0;
}
Counter.prototype.add = function (array) {
  array.forEach(function (entry) {
    this.sum += entry;
    ++this.count;
    console.log(this);
  }, this);
};

const obj = new Counter();
obj.add([2, 5, 9]);
console.log(obj.count);
// 3 === (1 + 1 + 1)
console.log(obj.sum);
16 === (2 + 5 + 9)

这里我们在 forEach() 中添加第二个参数,打印的结果为 Counter

如果 thisArg 参数有值,则每次 callback 函数被调用时,this 都会指向 thisArg 参数。如果省略了 thisArg 参数,或者其值为 null 或 undefined,this 则指向全局对象。按照函数观察到 this 的常用规则,callback 函数最终可观察到 this 值。

2、Array.map()

map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

参数:map()接收两个参数

参数1:callback 为数组中每个元素执行的函数,该函数接收一至三个参数:

item 数组中正在处理的当前元素
index 数组中正在处理的当前元素的索引
array forEach() 方法正在操作的数组
参数2:thisArg 可选

可选参数。当执行回调函数 callback 时,用作 this 的值。

返回值:一个由原数组每个元素执行回调函数的结果组成的新数组。

map() 的基本使用与 forEach 相同,只是 mpa 会返回一个新的数组

const arr = ["1", "2", "3"];

const newArr = arr.map(item => item = 'a')

console.log(arr);
console.log(newArr);

可以看到他并没有修改原数组,会返回一个新数组

如果map 可以返回一个新对象,那么我们不传任何值 mpa 会返回新数组吗?

const arr = ["1", "2", "3"];
console.log(arr.amp());

复制代码
来看打印

image.png

可以看到 map 的第一个参数的必传的,并且必须是函数

接下来我们来简单的实现一个map,来看看为什么 它可以返回一个新数组,而forEach不能

先来看 map

const Arr = ["1", "2", "3"];

// 在数组原型上挂载一个方法, Myap
Array.prototype.Myap = function (fn) {
  // 先判断参数是否为 function
  if (typeof fn !== "function") {
    // 如果不是 function 则抛出一个 错误信息
    throw new TypeError(`${fn} is not a function`);
  }
  // 创建一个空数组
  let newArr = [];
  // 利用 for 循环, 加上数组中的 push
  // psuh 将传入的回调函数(fn)执行的返回值追加到空数组中
  for (let i = 0; i < this.length; i++) {
    // 将 this 中的每一项 当做实参, 传入 fn 中
    // 这里的 this 指向的是 arr
    // newArr.push(fn(this[i], i, this))
    newArr.push(fn(this[i], i, this))
    //     第一个参数 this[i] 也就是 数组的每一项
    //     第二个参数 i 也就是数组的 索引
    //     第三个参数 this 指的就是 原数组
  };
  // 之后返回 数组
  return newArr;
}

复制代码
测试结果

const MyapArr = Arr.Myap((item, index, arr) => {
  console.log(item, index, arr);
  return item
})
console.log('Myap', MyapArr);

console.log('---------------------');

const mapArr = Arr.map((item, index, arr) => {
  console.log(item, index, arr);
  return item
})
console.log('map', mapArr);

我们来看 forEach

const Arr = ["1", "2", "3"];
Array.prototype.for_Each = function (fn) {
  for (var i = 0; i < this.length; i++) {
    fn(this[i], i, this)
  }
}

console.log(Arr.for_Each((item, i, arr) => {
  item = 'a'
  console.log(item, i, arr);
}));

复制代码
测试结果

console.log(Arr.for_Each((item, i, arr) => {
  item = 'a'
  console.log(item, i, arr);
}));

console.log('for_Each', Arr);

console.log('---------------------');

console.log(Arr.forEach((item, i, arr) => {
  item = 'a'
  console.log(item, i, arr);
}));

console.log('forEach', Arr);

是不是与原方法完全一样。不过我们实现的也只是第一个参数,第二个参数感兴趣的可以去了解了解

3、Array.reduce()

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

我们先来看 reduce() 方法的基本使用

const array = [1, 2, 3, 4];
const reducer = (totalizer, item, index, arr) => totalizer + item;

// 1 + 2 + 3 + 4
console.log(array.reduce(reducer));

// 5 + 1 + 2 + 3 + 4
console.log(array.reduce(reducer, 5));

复制代码
参数1:

callback 执行数组中每个值 (如果没有提供 totalizer 则第一个值除外)的函数,包含四个参数:

totalizer 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或 totalizer。
item 数组中正在处理的元素。
index 数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。
arr 调用reduce()的数组
参数2:

totalizer 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

我们之直接上源码分析

// 在数组的原型上挂载一个 MyReduce 方法 接收两个形参
Array.prototype.MyReduce = function (reducer, initVal) {
  // 先判断 第一个 参数是否为 function
  if (typeof reducer !== "function") {
    // 如果不是 function 则抛出一个 错误信息
    throw new TypeError(`${reducer} is not a function`);
  }
  // 利用循环调用 reducer 并将返回值赋值给 initVal
  // 这样也就不难明白为啥, 需要一个累计器, 以及在 reducer 一定要有返回值
  for (let i = 0; i < this.length; i++) {
    initVal = reducer(initVal, this[i], i, this);
    // 这里的四个参数分别为
    //     第一个参数 initVal 累计器
    //     第二个参数 this[i] 也就是 数组的每一项
    //     第三个参数 i 也就是数组的 索引
    //     第四个参数 this 指的就是 原数组
  }
  // 最后返回 累计器 initVal
  return initVal
};

复制代码
测试结果

array.MyReduce((acc, item, index, arr) => {
  console.log(acc, item, index, arr);
  return acc + item
}, 0)

console.log('---------------');

array.reduce((acc, item, index, arr) => {
  console.log(acc, item, index, arr);
  return acc + item
}, 0)

这里总结一句

对于所有的循环方法的都 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。 every() 、filter() 、some() 这三个方法实现的逻辑很相似,与上面的三种方法原理差不多,感兴趣的可以的 MDN 了解了解

这里我们来对常用数组方法进行解刨
1、Array.concat()
01、 我们知道源数组调用 concat , 传入一个数组或者多个 它可以合并数组那,我们不传值或者 传入非数组类型的数据 Object、 undefined 、null … 会是什么?

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
console.log(array1.concat());
const obj = { a: '对象' }
console.log(array1.concat(obj, undefined, null, 1, '2', () => { }));

可以看到 当我们不传入任何值 会返回原数组 , 当传入非数组类型的数据 会作为数组的元素追加到原数组中。

02、concat() 的返回值 还是刚刚是数组我们来做测试

const arr = array1.concat()
arr[0] = 1
console.log(arr);
console.log(array1.concat());

这里我们可以看到 concat 会返回一个新数组 并且不会对原数组造成影响

2、Array.find()
01、 它可以返回数组中满足条件的第一个元素,如果 都不满足则返回 undefined 这里就不多概述了,我们来看看他的参数,首先它接收一个实参,该实参为 函数 ,函数中有三个形参,分别为 当前遍历到的元素、当前遍历到的索引、数组本身

const array = ['a', 'b', 'a'];
array.find((item, index, arr) => {
  console.log(item, index, arr);
})

方法示例

const array = ['a', 'b', 'a'];
const res = array.find(item => item === 'a')
console.log(res);

测试

const array = ['a', 'b', 'a', 'b'];
const res = array.find(item => item = 'b')
console.log(res);
console.log(array);

这里进行赋值操作他会返回数组的第一个值?(大神求解)

这里应该可以判断出该方法不会直接修改原数组值

02、 那么他的参数是必传的吗?如果该函数返回一个 undefined 会是什么?

const array = ['a', 'b', 'a'];
console.log(array.find(() => { }));
console.log(array.find());

可以看到该参数为必传不传则会报错,如果该函数返回一个 undefined 则结果也为 undefined

3、Array.findIndex()

01、 该方法与 上一个 find 有类似的效果,它会返回数组中满足条件的第一个元素的索引,否则返回 -1,该方法的参数与上一个一样这里不在概述。

这里我们直接来测试

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.findIndex(item => item === 'c')
const res2 = array.findIndex(item => item === 'd')
console.log(res1);
console.log(res2);
console.log(array);

正常输出没有问题

来测试赋值操作

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.findIndex(item => item = 'c')
console.log(res1);
console.log(array);

可以看到它会返回第一个元素索引,也不能直接修改原数组的值

4、Array.includes()

01、 该方法有两个参数

第一个参数 :需要查找的元素值 查看数组中是否包含该参数,如果有返回 true 否则 false,

第二个参数 :为你要从第几个元素查找的索引值 , 如果传入一个负数、则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0。

方法示例

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.includes('c', -1)
console.log(res1);
console.log(array);

我们来看另一种情况

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.includes('b', -1)
console.log(res1);
console.log(array);

打印居然为 falss ???

不慌我们来捋一捋

我们先想一想 第一次查找的是 c ,传入的索引为 -1 ,那问题来了我们是从第几个开始查找的呢?

在上面介绍过,如果传入的是一个负数,则按升序从 array.length + fromIndex 的索引开始搜

回到问题:数组 array 的 length 为 5,加上 -1 也就是从 索引为 4 的开始查找

正好 array[4] === c,而 在 c 的后面已经没有值了,所以 b 就找不到了

所以会返回 false

我们来测试一下

const array = ['a', 'b', 'a', 'b', 'c', 'b'];
const res1 = array.includes('b', -1)
console.log(res1);
console.log(array);

可以看到我们的理论是正确的

5、Array.indexOf()

01、 该方法与 findIndex 有些许类似,findIndex 是返回第一个满足条件的的元素,接收的一个函数。indexOf 是返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1

方法示例

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.indexOf('a', -1)
console.log(res1);
console.log(array);

同样来看另一种情况

const array = ['a', 'b', 'a', 'b', 'c'];
const res1 = array.indexOf('b', -1)
console.log(res1);
console.log(array);

这里与 includes 的原理一样,我们之间看验证结果

const array = ['a', 'b', 'a', 'b', 'c', 'b'];
const res1 = array.indexOf('b', -1)
console.log(res1);
console.log(array);

6、Array.join()

01、 简单的说他可以将数组以某个字符拼接成字符串。该方法如果不传入参数则会以逗号拼接 如果数组只有一个项目,那么将返回该项目而不使用分隔符

方法示例

const array = ['a', 'b', 'a', 'b', 'c', 'b'];
const res1 = array.join('=')
console.log(res1);
console.log(array);

02、 如果数组中有复杂数据类型

const array = ['a', 'b', 'a', 'b', 'c', 'b', {}];
const res1 = array.join('=')
console.log(res1);
console.log(array);

可以看到该参数是复杂数据类型是无法分隔的

7、Array.pop()

01、 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

该方法会直接修改原数组

方法示例

const array = ['a', 'b', 'c'];
const res1 = array.pop()
console.log(res1);
console.log(array);

02、 同样来看另一种场景

const array = ['a', 'b', 'c'];
const obj = {
  '0': '1',
  '1': '2',
  '2': '3',
  length: 3
}
console.log(array.pop.call(obj));
console.log(obj);
console.log(array);

MDN 的描述

pop 方法从一个数组中删除并返回最后一个元素。

pop 方法有意具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。pop方法根据 length属性来确定最后一个元素的位置。如果不包含length属性或length属性不能被转成一个数值,会将length置为0,并返回undefined。

如果你在一个空数组上调用 pop(),它返回 undefined

回到问题:

首先 pop 是 Array 原型上的方法, Object 类型的是无法调用的,cal() 方法是 Function 原型上的方法,array.pop.call(obj) 这里就是 数组调用 pop ,pop 调用 call() ,这里通过 call 改变了 pop 的 this 指向,将 pop 的 this 指向了 obj 。

8、Array.shift()

01、 shift 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。

从数组中删除的元素; 如果数组为空则返回 undefined

该方法会直接修改原数组

方法示例

const array = ['a', 'b', 'c'];
const res1 = array.shift()
console.log(res1);
console.log(array);

02、 同样来看另一种场景

const array = ['a', 'b', 'c'];
const obj = {
  '0': '1',
  '1': '2',
  '2': '3',
  length: 3
}
const res1 = array.shift.call(obj)
console.log(res1);
console.log(obj);
console.log(array);

MDN 的描述

shift 方法并不局限于数组:这个方法能够通过 call 或 apply 方法作用于类似数组的对象上。但是对于没有 length 属性(从0开始的一系列连续的数字属性的最后一个)的对象,调用该方法可能没有任何意义。

原理与 pop 一样

9、Array.push()

01、 push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

该方法会直接修改原数组

方法示例

const array = ['a', 'b', 'c'];
const res1 = array.push('d', 'e')
console.log(res1);
console.log(array);

02、 同样来看另一种场景

const obj = {
  length: 0,

  addElem: function addElem(elem) {
    // obj.length 自动递增
    // 每次添加一个元素。
    [].push.call(this, elem);
  }
};

// 让我们添加一些空对象来说明。
obj.addElem('a');
obj.addElem({});
obj.addElem({});
console.log(obj.length);
console.log(obj);

MDN 的描述

push 方法具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。

实现的原理与 pop 类似,这里我们可以看到他给对象obj添加了一个属性方法addElem,该方法通过数组身上的push方法通过call来调用改变 push 内部的 this 指向了该对象obj的属性方法addElem身上。所以当我们调用 obj.addElem() 向里面传值时,也就是调用了push函数内部的代码。

10、Array.unshift()

01、 unshift 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组 ) 。

该方法会直接修改原数组

方法示例

const array = ['a', 'b', 'c'];

const res = array.unshift('1', '2')
console.log(res);
console.log(array);

02、 同样来看另一种场景

这里我通过 [].unshift.call() 来直接调用,并没有使用 push 方法描述中的把方法挂载到对象身上,其实原理都是一样的

const obj = {
  '0': '1',
  '1': '2',
  '2': '3',
  length: 3
}
const res1 = [].unshift.call(obj, 'a', 'b', {})

console.log(res1);
console.log(obj);

MDN 的描述

unshift 特意被设计成具有通用性;这个方法能够通过 call 或 apply 方法作用于类数组对象上。不过对于没有 length 属性(代表从0开始的一系列连续的数字属性的最后一个)的对象,调用该方法可能没有任何意义。

从这我们可以得出 unshift 与 push 实现的原理是一样的

11、Array.splice()

01、 splice 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

该方法会直接修改原数组

该方法接受三个参数:

start指定修改的开始位置
指定修改的开始位置(从0计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);如果负数的绝对值大于数组的长度,则表示开始位置为第0位。
deleteCount整数,表示要移除的数组元素的个数
如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。

如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。

如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。

item1, item2, …
要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素
返回值:

由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。

代码实例

const array = ['a', 'b', 'c'];

const res = array.splice(0, 2)
console.log(res);
console.log(array);

02、 同样来看另一种场景

场景1

const array = ['a', 'b', 'c'];

const res = array.splice(0, 2, 'd')
console.log(res);
console.log(array);

这里首先是把原数索引为 0 开始删除了 2 个,然后在原数组的第 0 项添加了 d

场景2

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];

const res1 = array1.splice(-1, 2, '新增的1')
const res2 = array2.splice(-10, 2, '新增的2')

console.log(res1);
console.log(array1);
console.log('--------------------');
console.log(res2);
console.log(array2);

当指定修改位置为 负数时候,则表示从数组末位开始的第几位(也就是倒数第一位),所以会从最后一个 ‘c’ 开始删除 ,删除 2个 ,应为 c 已经是最后一个元素了 所以只删除了一个,之后在倒数第一位的位置添加了 一个新元素 新增的1

这里可以看到,当前指定修改的位置的值为 -10 ,根据 res1 打印的理论那么应该从 倒数第 10 为开始删除,但原数组的长度只有 3 个 ,所以这里就直接从第 0 个开始删除了 ,同样在第 0 位添加了一个元素 新增的2

12、Array.reveres()

reveres() 方法将数组中元素的位置颠倒,并返回改数组,数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。

该方法会直接修改原数组

方法示例

const array = [1, 2, 3, 4, 5]
const res = array.reverse()
console.log(res);
console.log(array);

返回值:返回翻转后的数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值