这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战
前言
数组可以说是我们写JS的老朋友了,也是所有编程语言中最最最基本的数据结构,JS中常用的数组方法有push、pop、slice、map 和 reduce,今天我们就根据ECMA标准来手撕Push实现。
手写实现
先来看ECMA标准描述:ECMAScript® 2022 Language Specification (tc39.es),它里面概括了调用该方法会执行的过程。
总结下来就是:
- 先将当前的
this转换成Object对象,使用Object()进行显示转换,其实this和转换后的对象就是数组本身,转换只是为了方便后续操作。 - 初始化
length值,使用无符号右移运算符右移0位,作用就是对length取整,丢弃其小数位。 - 统计参数的个数,参数存放在
items中 - 判断当前数组元素项个数与要添加的元素个数之和是否超过了JS能表示的最大安全有效整数范围
Number.MAX_SAFE_INTEGER,由于JS采用IEEE-754标准来表示二进制浮点数,因此最大有效范围是2^53-1,如果超过了就报一个类型异常TypeError。 - 经过上面的预处理,就可以添加数据了,用一个
for循环,从数组尾部开始添加数据。 - 数据添加完后,计算新的
length值,并更新到数组对象上。 - 最后,放回新的数组长度
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源码
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实现。