从V8源码角度来手撕JS数组方法底层实现(一):Push

798 阅读2分钟

这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战

前言

数组可以说是我们写JS的老朋友了,也是所有编程语言中最最最基本的数据结构,JS中常用的数组方法有pushpopslicemapreduce,今天我们就根据ECMA标准来手撕Push实现。

手写实现

先来看ECMA标准描述:ECMAScript® 2022 Language Specification (tc39.es),它里面概括了调用该方法会执行的过程。

image.png

总结下来就是:

  1. 先将当前的this转换成Object对象,使用Object()进行显示转换,其实this和转换后的对象就是数组本身,转换只是为了方便后续操作。
  2. 初始化length值,使用无符号右移运算符右移0位,作用就是对length取整,丢弃其小数位。
  3. 统计参数的个数,参数存放在items
  4. 判断当前数组元素项个数与要添加的元素个数之和是否超过了JS能表示的最大安全有效整数范围Number.MAX_SAFE_INTEGER,由于JS采用IEEE-754标准来表示二进制浮点数,因此最大有效范围是2^53-1,如果超过了就报一个类型异常TypeError
  5. 经过上面的预处理,就可以添加数据了,用一个for循环,从数组尾部开始添加数据。
  6. 数据添加完后,计算新的length值,并更新到数组对象上。
  7. 最后,放回新的数组长度length

好!有了上面分析就很容易实现了,请看👇👇👇

Array.prototype.push = function(...items) {
  // 按照标准,先将this转换成对象
  let O = Object(this); 
  // 对length值进行合法化清洗
  let len = this.length >>> 0;
  let argCount = items.length >>> 0;
  // 判断数组长度是否超过最大能表示的长度
  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];
  }
  // 更新length值
  let newLength = len + argCount;
  O.length = newLength;
  // 返回新的长度值
  return newLength;
}

v8源码

V8 源码 push 的实现

function ArrayPush() {
  // CheckObjectCoercible用于判断当前方法中的this是否能转换成一个对象,如果不行会抛出一个异常
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
  
  // 将当前this转换成一个对象
  var array = TO_OBJECT(this);
  // length合法性清洗
  var n = TO_LENGTH(array.length);
  var m = arguments.length;

  // Subtract n from kMaxSafeInteger rather than testing m + n >
  // kMaxSafeInteger. n may already be kMaxSafeInteger. In that case adding
  // e.g., 1 would not be safe.
  //这里和我们实现不一样,不使用 m+n > kMaxSafeInteger来判断,而是使用m > kMaxSafeInteger - n
  if (m > kMaxSafeInteger - n) throw %make_type_error(kPushPastSafeLength, m, n);
    
  // 添加数据
  for (var i = 0; i < m; i++) {
    array[i+n] = arguments[i];
  }
  // 更新length
  var new_length = n + m;
  array.length = new_length;
  // 返回length
  return new_length;
}

可以看到v8的实现除了判断范围外与我们不一样外,其他都一致。

总结

通过手撕数组方法的底层实现,可以帮助我们更好地了解它们,使得开发更游刃有余,下篇文章再来手撕reduce实现。