我是靠谱客的博主 轻松机器猫,最近开发中收集的这篇文章主要介绍手写 JS 数组多个方法的底层实现手写 JS 数组多个方法的底层实现总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

手写 JS 数组多个方法的底层实现

  我们都知道,比较常用的数组方法有 pushpopslicemapreduce等,

围绕这几个常用方法,并结合 V8的源代码手写这些方法的底层实现。

为了方便更好地理解,在开始前先回想一下:

  1. reduce方法里面的参数都是什么作用?
  2. pushpop的底层逻辑是什么样的呢?

push 方法的底层实现

  ECMA 的官网关于 push的基本描述:

When the push method is called with zero or more arguments, the following steps are taken:
1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. Let argCount be the number of elements in items.
4. If len + argCount > 2^53 - 1, throw a TypeError exception.
5. For each element E of items, do
a. Perform ? Set(O, ! ToString(F(len)), E, true).
b. Set len to len + 1.
6. Perform ? Set(O, "length", F(len), true).
7. Return F(len).

  从上面的描述可以看到边界判断逻辑以及实现的思路,根据这段英文,将其转换为容易理解代码,如下所示。

Array.prototype.push = function(...items) {
let O = Object(this);
// ecma 中提到的先转换为对象
let len = this.length >>> 0;
let argCount = items.length >>> 0;
// 2 ^ 53 - 1 为JS能表示的最大正整数
if (len + argCount > 2 ** 53 - 1) {
throw new TypeError("The number of array is over the max value")
}
for(let i = 0; i < argCount; i++) {
O[len + i] = items[i];
}
let newLength = len + argCount;
O.length = newLength;
return newLength;
}

  从上面的代码可以看出,关键点就在于给数组本身循环添加新的元素 item,然后调整数组的长度 length为最新的长度,即可完成 push的底层实现。

pop 方法的底层实现

  ECMA 官网关于 pop的基本描述:

When the pop method is called, the following steps are taken:
1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. If len = 0, then
Perform ? Set(O, "length", +0F, true).
Return undefined.
4. Else,
Assert: len > 0.
Let newLen be F(len - 1).
Let index be ! ToString(newLen).
Let element be ? Get(O, index).
Perform ? DeletePropertyOrThrow(O, index).
Perform ? Set(O, "length", newLen, true).
Return element.

  从上面的描述可以看到边界判断逻辑以及实现的思路,根据上面的英文,我们同样将其转换为可以理解的代码,如下所示。

Array.prototype.pop = function() {
let O = Object(this);
let len = this.length >>> 0;
if (len === 0) {
O.length = 0;
return undefined;
}
len --;
let value = O[len];
delete O[len];
O.length = len;
return value;
}

  其核心思路还是在于删掉数组自身的最后一个元素,index 就是数组的 len长度,然后更新最新的长度,最后返回的元素的值,即可达到想要的效果。另外就是在当长度为 0 的时候,如果执行 pop操作,返回的是 undefined,需要做一下特殊处理。

map 方法的底层实现

  ECMA 的官网关于 map的基本描述:

When the map method is called with one or two arguments, the following steps are taken:
1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. If IsCallable(callbackfn) is false, throw a TypeError exception.
4. Let A be ? ArraySpeciesCreate(O, len).
5. Let k be 0.
6. Repeat, while k < len,
a. Let Pk be ! ToString(F(k)).
b. Let kPresent be ? HasProperty(O, Pk).
c. If kPresent is true, then
Let kValue be ? Get(O, Pk).
Let mappedValue be ? Call(callbackfn, thisArg, « kValue, F(k), O »).
Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
d. Set k to k + 1.
7. Return A.
Array.prototype.map = function(callbackFn, thisArg) {
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'map' of null");
}
if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
throw new TypeError(callbackfn + ' is not a function')
}
let O = Object(this);
let T = thisArg;
let len = O.length >>> 0;
let A = new Array(len);
for(let k = 0; k < len; k++) {
if (k in O) {
let kValue = O[k];
// 依次传入this, 当前项,当前索引,整个数组
let mappedValue = callbackfn.call(T, KValue, k, O);
A[k] = mappedValue;
}
}
return A;
}

  有了上面实现 pushpop的基础思路,map的实现也不会太难了,基本就是再多加一些判断,循环遍历实现 map的思路,将处理过后的 mappedValue赋给一个新定义的数组 A,最后返回这个新数组 A,并不改变原数组的值。

reduce 方法的底层实现

 ECMA 的官网关于 reduce的基本描述:

When the reduce method is called with one or two arguments, the following steps are taken:
1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. If IsCallable(callbackfn) is false, throw a TypeError exception.
4. If len = 0 and initialValue is not present, throw a TypeError exception.
5. Let k be 0.
6. Let accumulator be undefined.
7. If initialValue is present, then
Set accumulator to initialValue.
8. Else,
Let kPresent be false.
Repeat, while kPresent is false and k < len,
Let Pk be ! ToString(F(k)).
Set kPresent to ? HasProperty(O, Pk).
If kPresent is true, then
Set accumulator to ? Get(O, Pk).
Set k to k + 1.
If kPresent is false, throw a TypeError exception.
9. Repeat, while k < len,
Let Pk be ! ToString(F(k)).
Let kPresent be ? HasProperty(O, Pk).
If kPresent is true, then
Let kValue be ? Get(O, Pk).
Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, F(k), O »).
Set k to k + 1.
10. Return accumulator.
Array.prototype.reduce
= function(callbackfn, initialValue) {
// 异常处理,和 map 类似
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'reduce' of null");
}
// 处理回调类型异常
if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
throw new TypeError(callbackfn + ' is not a function')
}
let O = Object(this);
let len = O.length >>> 0;
let k = 0;
let accumulator = initialValue;
// reduce方法第二个参数作为累加器的初始值
if (accumulator === undefined) {
throw new Error('Each element of the array is empty');
// 初始值不传的处理
for(; k < len ; k++) {
if (k in O) {
accumulator = O[k];
k++;
break;
}
}
}
for(;k < len; k++) {
if (k in O) {
// 注意 reduce 的核心累加器
accumulator = callbackfn.call(undefined, accumulator, O[k], O);
}
}
return accumulator;
}

  根据上面的代码及注释,有几个关键点需要重点关注:

  1. 初始值默认值不传的特殊处理;
  2. 累加器以及 callbackfn的处理逻辑。

总结

  到这里,本文的内容就先告一段落了。这一篇内容虽少,但却是必须要掌握的内容。再贴一下 V8数组关于各种方法的实现源码地址,如下表所示。

数组方法V8 源码地址
popV8 源码 pop 的实现
pushV8 源码 push 的实现
mapV8 源码 map 的实现
sliceV8 源码 slice 的实现
filterV8 源码 filter 的实现

最后

以上就是轻松机器猫为你收集整理的手写 JS 数组多个方法的底层实现手写 JS 数组多个方法的底层实现总结的全部内容,希望文章能够帮你解决手写 JS 数组多个方法的底层实现手写 JS 数组多个方法的底层实现总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部