跟着ECMAScript 规范,手写数组方法之map

不止于会用:跟着 ECMAScript 规范,手写 map

开宗明义:以传入的数组,回调函数 和 thisArg 加工成新数组。

Array.prototype.myMap 接受一个可调用的 callback 和可选的 thisArg,按从 0 到 ToLength(O.length)-1 的索引依次对传入的(类)数组元素调用 callback(参数为 currentValue, index, 原对象),把每次调用的返回值放到同一索引的新数组并返回该新数组;回调不会改变原数组,也不会为原数组中"空位"(hole)创建值。

主干

js 复制代码
Array.prototype.myMap = function(callback) {
    // 创建空数组
  const result = [];

  // this即传入数组
  for (let i = 0; i < this.length; i++) {
    result.push(callback(this[i], i, this));
  }

  return result;
};

树枝

考虑到边界的处理

处理 thisArg

如果想在回调函数里使用一个特定的 this 上下文

js 复制代码
Array.prototype.myMap = function(callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    result.push(callback.call(thisArg, this[i], i, this));
  }
  return result;
};

变化:

  1. 在函数签名中接收 thisArg 参数。
  2. 调用回调时,不用 callback(),而是用 callback.call(thisArg) 来手动指定 this

处理稀疏数组

原生的 map 会返回 [2, <1 empty item>, 4],它会跳过空位 。主干版本没有这个能力,它会把空位当成 undefined 来处理。

目标: 识别并跳过空位

js 复制代码
Array.prototype.customMap = function(callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      result.push(callback.call(thisArg, this[i], i, this));
    }
  }
  return result;
};

变化:

  1. 在循环中,访问元素前,先检查这个索引位置上是否真的有值。
  2. JavaScript 的 in 操作符:if (i in this)

防御性编程

  • callback 不是一个函数?
  • 在非数组上调用(比如 document.getElementsByTagName('div') 这种类数组)?
  • thisnullundefined
js 复制代码
Array.prototype.customMap = function(callback, thisArg) {
    // 修剪1:检查 callback
  if (typeof callback !== 'function') {
    throw new TypeError('callback is not a function');
  }
    // 修剪2: 包装类
  const O = Object(this);
    // 修剪3: 安全长度
  const len = O.length >>> 0;

  const result = [];
  for (let i = 0; i < len; i++) {
    if (i in O) {
      result.push(callback.call(thisArg, O[i], i, O));
    }
  }
  return result;
}

修剪:

  1. 检查 callback :在函数开头 if (typeof callback !== 'function'),如果不是函数就立刻报错。
  2. 转换 this :使用 const O = Object(this) 确保 this 总是一个对象,这样即使在字符串 hello 上调用,也能把它变成一个类数组对象 {0: 'h', 1: 'e', ...} 来处理。
  3. 安全获取长度 :使用 const len = O.length >>> 0 确保长度总是一个非负整数,避免负数或奇怪值导致循环出错。

性能优化

  • result.push() 可能会有性能开销,并且无法创建稀疏数组。
  • 规范中创建新数组的方式是 ArraySpeciesCreate,它更复杂但也更精确。
js 复制代码
Array.prototype.myMap = function(callback, thisArg) {
  if (typeof callback !== 'function') {
   throw new TypeError('callback is not a function');
  }
  const O = Object(this);
  const len = O.length >>> 0;

  // 修剪:预分配新数组
  const A = new Array(len);
  let k = 0;

  // 修剪:使用 while 循环
  while (k < len) {
    if (k in O) {
      const kValue = O[k];
      const mappedValue = callback.call(thisArg, kValue, k, O);
      // 修剪:直接赋值
      A[k] = mappedValue;
    }
    k++;
  }
  return A;
};

// 1. 基本功能测试
console.log([1, 2, 3].myMap(x => x * 2)); // [2, 4, 6]

// 2. 稀疏数组测试
const sparse = [1, , 3];
console.log(sparse.myMap(x => x + 1)); // [2, <1 empty item>, 4] (正确跳过空位)

// 3. thisArg 测试
const multiplier = { factor: 10 };
function multiply(item) {
  return item * this.factor;
}
console.log([1, 2, 3].myMap(multiply, multiplier)); // [10, 20, 30]

// 4. 类数组对象测试
function f() {
  return Array.prototype.myMap.call(arguments, x => x + 1);
}
console.log(f(4, 5, 6)); // [5, 6, 7]

// 5. 字符串测试
console.log(Array.prototype.myMap.call('abc', char => char.toUpperCase())); // ['A', 'B', 'C']

修剪:

  1. 预分配数组 :用 const A = new Array(len) 提前创建好一个固定长度的数组。
  2. 直接赋值 :在循环中用 A[k] = mappedValue 直接赋值,而不是 push。这样更快,并且如果跳过某个索引,新数组也会在对应位置留下空位。
  3. 使用 while 循环 :规范中用的是 while,我们也跟着用,保持风格一致。

收获总结

参考

文档地址:ECMAScript® 2023 Language Specification - Array.prototype.map

【JavaScript】大厂前端面试官最爱考的JS手写题整理出来了!你能答对几道题?_哔哩哔哩_bilibili

文档原文

相关推荐
风叶悠然8 分钟前
vue3中数据的pinia的使用
前端·javascript·数据库
Jyywww12121 分钟前
Uniapp+Vue3 使用父传子方法实现自定义tabBar
javascript·vue.js·uni-app
光影少年41 分钟前
React vs Next.js
前端·javascript·react.js
谢尔登42 分钟前
Vue3 响应式系统——ref 和 reactive
前端·javascript·vue.js
天若有情67343 分钟前
【JavaScript】React 实现 Vue 的 watch 和 computed 详解
javascript·vue.js·react.js
满栀5851 小时前
插件轮播图制作
开发语言·前端·javascript·jquery
切糕师学AI1 小时前
Vue 中的计算属性(computed)
前端·javascript·vue.js
程琬清君1 小时前
Vue3DraggableResizable可移动范围有问题
前端·javascript·vue.js
天天开心a1 小时前
Vue.js 基础教程笔记(一):Vue入门与环境搭建
前端·javascript·vue.js·笔记·前端框架
hzb666661 小时前
xd_day28js原生开发-day31 day41asp.net
开发语言·前端·javascript·安全·web安全