美文网首页
js中push,pop,map,reduce底层实现

js中push,pop,map,reduce底层实现

作者: 浅忆_0810 | 来源:发表于2021-09-18 13:58 被阅读0次

1. push 方法的底层实现

ECMA 的官网关于 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 的底层实现


2. pop 方法的底层实现

ECMA 的官网关于 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;
}

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


3. map 方法的底层实现

ECMA 的官网关于 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;
}

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


4. reduce 方法的底层实现

ECMA 官网关于 reduce 的基本描述(链接:ECMA 数组的 pop 标准

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 的处理逻辑

5. 总结

数组方法 V8 源码地址
pop V8 源码 pop 的实现
push V8 源码 push 的实现
map V8 源码 map 的实现
slice V8 源码 slice 的实现
filter V8 源码 filter 的实现

相关文章

网友评论

      本文标题:js中push,pop,map,reduce底层实现

      本文链接:https://www.haomeiwen.com/subject/xmoegltx.html