0%

原生 JS 封装数组方法

数组基本操作方法

以下方法均改变原数组,需要操作数组的 length 属性

Push

  • 返回值:新数组的长度
  • 思路:数组的长度等于 arguments[i]
1
2
3
4
5
6
Array.prototype.myPush = function () {
for (let i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i];
}
return this.length;
};

Pop

  • 返回值:删除的项(如果空数组,返回 undefined)
  • 思路:让数组长度 -1
1
2
3
Array.prototype.myPop = function () {
return this.length==0?undefined:(this[this.length - 1],this.length--);
};

Shift

  • 返回值:删除的项
  • 思路:让数组前一个值 this[i] 等于后一个值 this[i + 1] ,之后把数组长度 -1
1
2
3
4
5
6
7
8
9
10
11
Array.prototype.myShift = function () {
if (this.length == 0) {
return;
}
let del = this[0];
for (let i = 0; i < this.length; i++) {
this[i] = this[i + 1];
}
this.length--;
return del;
};

Unshift

  • 返回值:新数组的长度
  • 思路:让数组后一个值 this[i] 等于前 n(n=arguments.length)个值 this[i - arguments.length] ,之后把前 n 个值填为 arguments[i]
1
2
3
4
5
6
7
8
9
10
11
Array.prototype.myUnshift = function () {
this.length += arguments.length;
for (let i = this.length - 1; i >= 0; i--) {
if (i >= arguments.length) {
this[i] = this[i - arguments.length];
} else {
this[i] = arguments[i];
}
}
return this.length;
};
  • ES6 方法实现能简单一些。其实就是拼接数组,之后把拼接的数组一项一项赋值给原数组
1
2
3
4
5
6
7
Array.prototype.myUnshift = function (...arg) {
var newArr = [...arg, ...this];
for (let i = 0; i < newArr.length; i++) {
this[i] = newArr[i];
}
return this.length;
};

数组操作方法

以下方法除splice(改变原数组)均不改变原数组

splice(特殊)

因为 push 实现比较简单,这里用到了 push 方法,能简写一两行

  • 返回值:删除的项(数组)

  • 思路:按参数数量分别进行判定

    • 参数小于等于1个,从 start 开始添加到新数组,并把添加那项删除(数组长度也减少)
    • 参数大于1个,先进行删除(跟上一步相同)再把数组分成三份,左边+中间被替换的项+右边,合并数组

    注意:

    1. 第一个参数为负数:如果转换为正数,大于数组长度,直接转换为0;小于等于数组长度,需加上数组长度
    2. 第二个参数:如果小于0,直接转换为0
    3. 第三个参数起:要添加的项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Array.prototype.mySplice = function (start, del) {
let arr = [];
if (start < 0) {
start = -start > this.length ? 0 : this.length + start;
}
if (arguments.length <= 1) {
for (let i = start; i < this.length; i++) {
arr.push(this[i]);
}
this.length = start;
} else {
del = del < 0 ? 0 : del;
// 删除数组这一步
for (let i = 0; i < del; i++) {
arr.push(this[start + i]);
this[start + i] = this[start + i + del];
}
this.length -= del;
let lArr = [];
for (let i = 0; i < start; i++) {
lArr.push(this[i]);
}
for (let i = 0; i < arguments.length - 2; i++) {
lArr.push(arguments[i + 2]);
}
for (let i = start; i < this.length; i++) {
lArr.push(this[i]);
}
for (let i = 0; i < lArr.length; i++) {
this[i] = lArr[i];
}
}
return arr;
};
  • 删除数组那一步,我第一时间想到的是冒泡(把删除项一次一次冒到最后一位),最后出来的代码是下面这样(非常麻烦)
1
2
3
4
5
6
7
8
9
10
11
12
let that = del;
for (let i = that; i > 0; i++) {
if (that <= 0) {
break;
}
for (let j = start; j < this.length - 1; j++) {
[this[j], this[j + 1]] = [this[j + 1], this[j]];
}
arr.push(this[this.length - 1]);
this.length--;
that--;
}
  • 大可不必这样,把删除项的下一位(不删除)往前挪一个一个覆盖要删除的项即可
1
2
3
4
5
for (let i = 0; i < del; i++) {
arr.push(this[start + i]);
this[start + i] = this[start + i + del];
}
this.length -= del;

concat

  • 返回值:拼接后的新数组
  • 思路:如果参数是数组需遍历后一个一个添加到新数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Array.prototype.myConcat = function () {
let arr = [];
for (let i = 0; i < this.length; i++) {
arr[i] = this[i];
}
for (let i = 0; i < arguments.length; i++) {
const el = arguments[i];
if (Array.isArray(el)) {
for (let i = 0; i < el.length; i++) {
arr[arr.length] = el[i];
}
} else {
arr[arr.length] = el;
}
}
return arr;
};

slice

  • 返回值:复制后的新数组
  • 思路:如果传参是负数索引,需对其长度进行判定。如果大于数组长度,将其改为0;小于数组长度,将其改为arr.length+(负数索引)
1
2
3
4
5
6
7
8
9
10
11
12
13
Array.prototype.mySlice = function (start = 0, end = this.length) {
var arr = [];
if (start < 0) {
start = -start > this.length ? 0 : this.length + start;
}
if (end < 0) {
end = -end > this.length ? 0 : this.length + end;
}
for (let i = start; i < end; i++) {
arr.push(this[i]);
}
return arr;
};

flat

  • 返回值:扁平后的新数组
  • 思路:递归(下面实现没有加上可以指定递归深度的参数,类似arr.flat(Infinity)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Array.prototype.myFlat = function () {
let arr = [];
fn(this);
function fn(ary) {
for (let i = 0; i < ary.length; i++) {
const item = ary[i];
if (Array.isArray(item)) {
fn(item);
} else {
arr.push(item);
}
}
}
return arr;
};

Array.prototype.myFlat = function () {
return this.toString().split(",").map((item) => Number(item));
};

数组重排方法

reverse

  • 返回值:倒序后的数组
  • 思路:第n个数和倒数第n个数两两对换
1
2
3
4
5
6
7
8
Array.prototype.myReverse = function () {
for (let i = 0, j = this.length - 1; j > i; i++, j--) {
var temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
};

sort

  • 返回值:排序后的数组
  • 思路:不传参的时候,两两比较 String(xxx) 的值;传参的时候判断 callBack(a-b) 是否大于 0 即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Array.prototype.mySort = function (callBack) {
if (this.length <= 1) {
return this;
}
if (typeof callBack === "function") {
for (let i = 0; i < this.length - 1; i++) {
for (let j = 0; j < this.length - 1 - i; j++) {
if (callBack(this[j], this[j + 1]) > 0) {
[this[j], this[j + 1]] = [this[j + 1], this[j]];
}
}
}
} else if (typeof callBack === "undefined") {
for (let i = 0; i < this.length - 1; i++) {
for (let j = 0; j < this.length - 1 - i; j++) {
if (String(this[j]) > String(this[j + 1])) {
[this[j], this[j + 1]] = [this[j + 1], this[j]];
}
}
}
} else {
return "参数异常";
}
return this;
};

数组位置方法

以下方法均不改变原数组

indexOf

  • 返回值:索引/-1
  • 思路:遍历数组,第二个参数是从哪个索引开始。如果不传参,从头查到尾部
1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.myIndexOf = function (val, index = 0) {
if (index < 0) {
index = -index > this.length ? 0 : index + this.length;
}
for (let i = index; i < this.length; i++) {
if (this[i] === val) {
return i;
}
}
return -1;
};

lastIndexOf

  • 返回值:索引/-1
  • 思路:遍历数组,第二个参数是到哪个索引结束(从0开始到这个索引结束)。如果不传参,从头查到尾部
1
2
3
4
5
6
7
8
9
10
11
Array.prototype.myLastIndexOf = function (val, index = this.length) {
if (index < 0) {
index = -index >= this.length ? 0 : index + this.length;
}
for (let i = index; i >= 0; i--) {
if (this[i] === val) {
return i;
}
}
return -1;
};

includes

  • 返回值:true/false
  • 思路:遍历数组
1
2
3
4
5
6
7
8
Array.prototype.myIncludes = function (val) {
for (let i = 0; i < this.length; i++) {
if (this[i] === val) {
return true;
}
}
return false;
};

数组迭代方法

以下方法均不改变原数组

forEach

回调函数内部 this 一般指向 window

  • 返回值:undefined
  • 思路:遍历数组
1
2
3
4
5
6
Array.prototype.myForEach = function (callBack) {
for (let i = 0; i < this.length; i++) {
let index = i, item = this[i];
callBack(item, index);
}
};

map

  • 返回值:映射后的新数组
  • 思路:遍历数组,把数组每以项经过运算后赋值给新数组
1
2
3
4
5
6
7
8
Array.prototype.myMap = function (callBack) {
let arr = [];
for (let i = 0; i < this.length; i++) {
let index = i, item = this[i];
arr[i] = callBack(item, index);
}
return arr;
};

reduce

  • 返回值:函数累计处理的结果
  • 思路:initial 返回值在数组的每次迭代中被记住,最后成为最终的结果值
1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.myReduce = function (callBack, initial) {
if (typeof callBack !== "function") throw new TypeError("callBack must be function");
let i = 0;
if (typeof initial === "undefined") {
initial = this[0];
i = 1;
}
for (; i < this.length; i++) {
initial = callBack(initial, this[i], i)
}
return initial;
};

find

  • 返回值:找到就返回符合的元素,没有返回 undefined
  • 思路:遍历数组
1
2
3
4
5
Array.prototype.myFind = function (callBack) {
for (let i = 0; i < this.length; i++) {
if (callBack(this[i], i)) return this[i]
}
}

every

  • 返回值:只要有一个不符合返回false,如果都符合返回 true
  • 思路:遍历数组,一假即假
1
2
3
4
5
6
Array.prototype.myEvery = function (callBack) {
for (let i = 0; i < this.length; i++) {
if (!callBack(this[i], i)) return false
}
return true
}

some

  • 返回值:只要有一个符合就返回 true,如果都符合返回 false
  • 思路:遍历数组,一真即真
1
2
3
4
5
6
Array.prototype.mySome = function (callBack) {
for (let i = 0; i < this.length; i++) {
if (callBack(this[i], i)) return true
}
return false
}

filter

  • 返回值:一个新数组,数组里面是符合条件的所有元素
  • 思路:遍历数组
1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.myFilter = function (callBack) {
if (!Array.isArray(this) || !this.length || typeof callback !== 'function') {
return []
}
let arr = [];
for (let i = 0; i < this.length; i++) {
if (!callBack(this[i], i)) {
arr.push(this[i]);
}
}
return arr
}