跟着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

文档原文

相关推荐
不爱吃糖的程序媛3 小时前
Electron 智能文件分析器开发实战适配鸿蒙
前端·javascript·electron
flashlight_hi3 小时前
LeetCode 分类刷题:3217. 从链表中移除在数组中存在的节点
javascript·数据结构·leetcode·链表
Java追光着4 小时前
React Native 自建 JS Bundle OTA 更新系统:从零到一的完整实现与踩坑记录
javascript·react native·react.js
努力往上爬de蜗牛4 小时前
react native 运行问题和调试 --持续更新
javascript·react native·react.js
Achieve前端实验室5 小时前
JavaScript 原型/原型链
前端·javascript
LXA08095 小时前
vue3开发使用框架推荐
前端·javascript·vue.js
用户90443816324605 小时前
React 5 个 “隐形坑”:上线前没注意,debug 到凌晨 3 点
前端·javascript·react.js
AAA阿giao5 小时前
Promise:让 JavaScript 异步任务“同步化”的利器
前端·javascript·promise
sg_knight7 小时前
微信小程序中 WebView 组件的使用与应用场景
前端·javascript·微信·微信小程序·小程序·web·weapp