Skip to content
medium
Polyfill迭代协议

实现一个名为 myFrom 的函数

请你实现一个名为 myFrom 的函数,该函数模仿原生 ES6 中 Array.from 的行为。myFrom 接收三个参数:

  • arrayLike:一个类似数组的对象或可迭代对象(例如:具有 length 属性的对象、字符串等)。
  • mapFn (可选):若提供,则应为一个函数,用于对每个元素进行映射,如果存在则对每个元素执行 mapFn(element, index),并将返回结果插入新数组。
  • thisArg (可选):若 mapFn 存在且 thisArg 被提供,则调用映射函数时用 thisArg 作为 this 的值。

此外,你需要实现两个辅助函数:

  1. toInteger(value):转换 value 为数值类型,如果不能转换则返回 0;对正负数取绝对值并向下取整,再根据原始符号返回结果。
  2. toLength(value):保证 value 转换成整数后位于合法范围 [0, Number.MAX_SAFE_INTEGER] 内(Number.MAX_SAFE_INTEGER 等于 2^53 - 1)。

题目要求

  1. 输入验证

    • arrayLikenullundefined 时,抛出 TypeError 异常,提示 "Array.from requires an array-like object - not null or undefined"
    • 当提供了 mapFn 参数,但其类型不是函数时,抛出 TypeError 异常,提示 "Array.from when provided mapFn must be a function"
  2. 辅助函数

    • 实现 toInteger(value),将传入值转换为数值,并按照要求返回整数。要求对 NaN 返回 0,对无穷大或 0 直接返回其值,否则返回带符号的向下取整结果。
    • 实现 toLength(value),利用 toInteger 保证返回值在 [0, Number.MAX_SAFE_INTEGER] 之间。
  3. 数组转换

    • 使用 Object(arrayLike) 获取输入的对象副本,并通过 toLength 得到合法的长度,创建一个新的数组(或数组状对象)。
    • 遍历 arrayLike 每个索引的位置,对于每个元素:
      • 如果存在 mapFn,则:
        • thisArg 被提供,以 thisArg 作为 this 调用 mapFn(iValue, i)
        • 否则直接调用 mapFn(iValue, i)
      • 如果不传入 mapFn,则直接将原始元素复制到新数组中。
    • 最终返回新生成的数组。
  4. 示例和测试要求

    • 输入:myFrom({0: 'a', 1: 'b', length: 2}) 应返回 ['a', 'b']
    • 输入:myFrom('hello') 应返回 ['h', 'e', 'l', 'l', 'o']
    • 输入:
      js
      function addIndex(el, i) {
        return el + i
      }
      myFrom({ 0: 'a', 1: 'b', length: 2 }, addIndex)
      应返回 ['a0', 'b1']
    • 输入:若 mapFn 存在但其类型不是函数,则抛出相应错误。

题目模版

js
/**
 * 自定义实现 Array.from 方法,将类数组对象或可迭代对象转换为数组。
 *
 * @param {ArrayLike|Iterable} arrayLike - 类数组对象或可迭代对象。
 * @param {Function} [mapFn] - 可选的映射函数,用于对每个元素进行处理。
 * @param {*} [thisArg] - 可选的上下文对象,用于绑定 `mapFn` 的 `this` 值。
 * @returns {Array} 返回一个新数组,包含从 `arrayLike` 转换而来的元素。
 * @throws {TypeError} 如果 `arrayLike` 为 null 或 undefined,抛出类型错误。
 * @throws {TypeError} 如果提供的 `mapFn` 不是函数,抛出类型错误。
 */
export default function myFrom(arrayLike, mapFn, thisArg) {

}
js
// 类型定义
interface ArrayLike<T> {
  readonly length: number
  readonly [n: number]: T
}

type MapFunction<T, U> = (value: T, index: number) => U

/**
 * 自定义实现 Array.from 方法,将类数组对象或可迭代对象转换为数组。
 *
 * @param arrayLike - 类数组对象或可迭代对象。
 * @param mapFn - 可选的映射函数,用于对每个元素进行处理。
 * @param thisArg - 可选的上下文对象,用于绑定 `mapFn` 的 `this` 值。
 * @returns 返回一个新数组,包含从 `arrayLike` 转换而来的元素。
 * @throws 如果 `arrayLike` 为 null 或 undefined,抛出类型错误。
 * @throws 如果提供的 `mapFn` 不是函数,抛出类型错误。
 */
export default function myFrom<T, U = T>(
  arrayLike: ArrayLike<T> | Iterable<T> | null | undefined,
  mapFn?: MapFunction<T, U>,
  thisArg?: any,
): U[] {

}

测试代码

js
import { describe, expect, it } from 'vitest'
import myFrom from './myFrom'

describe('myFrom function', () => {
  it('应正确处理基本的数组类对象', () => {
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', 'b', 'c'])
  })
  it('应正确处理 length 为负数的情况', () => {
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: -1 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确处理字符串', () => {
    const result = myFrom('abc')
    expect(result).toEqual(['a', 'b', 'c'])
  })

  it('应正确处理空的数组类对象', () => {
    const arrayLike = { length: 0 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确应用mapFn', () => {
    const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }
    const result = myFrom(arrayLike, value => value * 2)
    expect(result).toEqual([2, 4, 6])
  })

  it('应正确处理thisArg', () => {
    const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }
    const context = { multiplier: 3 }
    const result = myFrom(
      arrayLike,
      function (value) {
        return value * this.multiplier
      },
      context,
    )
    expect(result).toEqual([3, 6, 9])
  })

  it('应抛出错误,当arrayLike为null或undefined时', () => {
    expect(() => myFrom(null)).toThrow(TypeError)
    expect(() => myFrom(undefined)).toThrow(TypeError)
  })

  it('应抛出错误,当mapFn不是函数时', () => {
    const arrayLike = { 0: 1, 1: 2, length: 2 }
    expect(() => myFrom(arrayLike, 123)).toThrow(TypeError)
  })

  it('应正确处理稀疏数组类对象', () => {
    const arrayLike = { 0: 'a', 2: 'c', length: 4 }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', undefined, 'c', undefined])
    expect(result.length).toBe(4)
  })

  it('应正确处理长度超出Number.MAX_SAFE_INTEGER的情况', () => {
    const arrayLike = { length: Number.MAX_SAFE_INTEGER + 1 }
    expect(() => myFrom(arrayLike)).toThrow(RangeError)
  })

  it('应正确处理负数长度', () => {
    const arrayLike = { length: -5 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
    expect(result.length).toBe(0)
  })

  it('应正确处理可迭代对象(如Set)', () => {
    const set = new Set([1, 2, 3])
    const result = myFrom(set)
    expect(result).toEqual([1, 2, 3])
  })

  it('应正确处理可迭代对象(如Map)', () => {
    const map = new Map([
      ['a', 1],
      ['b', 2],
    ])
    const result = myFrom(map)
    expect(result).toEqual([
      ['a', 1],
      ['b', 2],
    ])
  })

  it('应正确处理带有非数字length属性的对象', () => {
    const arrayLike = { 0: 'a', 1: 'b', length: '2' }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', 'b'])
  })

  it('应正确处理length为NaN的情况', () => {
    const arrayLike = { 0: 'a', length: Number.NaN }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确处理mapFn的第二个参数index', () => {
    const arrayLike = { 0: 10, 1: 20, length: 2 }
    const result = myFrom(arrayLike, (v, i) => i)
    expect(result).toEqual([0, 1])
  })

  it('应正确处理mapFn返回undefined的情况', () => {
    const arrayLike = { 0: 1, 1: 2, length: 2 }
    const result = myFrom(arrayLike, () => undefined)
    expect(result).toEqual([undefined, undefined])
  })

  it('应正确处理thisArg为null的情况', () => {
    const arrayLike = { 0: 2, 1: 4, length: 2 }
    function fn(v) {
      return this ? this.x * v : v
    }
    const result = myFrom(arrayLike, fn, null)
    expect(result).toEqual([2, 4])
  })

  it('应正确处理mapFn为箭头函数时的this', () => {
    const arrayLike = { 0: 2, 1: 4, length: 2 }
    const context = { x: 10 }
    const result = myFrom(
      arrayLike,
      v => (this?.x ? this.x * v : v),
      context,
    )
    expect(result).toEqual([2, 4])
  })

  it('应正确处理对象没有length属性的情况', () => {
    const obj = { 0: 'a', 1: 'b' }
    const result = myFrom(obj)
    expect(result).toEqual([])
  })
})
ts
import { describe, expect, it } from 'vitest'
import myFrom from './myFrom'

describe('myFrom function', () => {
  it('应正确处理基本的数组类对象', () => {
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', 'b', 'c'])
  })
  it('应正确处理 length 为负数的情况', () => {
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: -1 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确处理字符串', () => {
    const result = myFrom('abc')
    expect(result).toEqual(['a', 'b', 'c'])
  })

  it('应正确处理空的数组类对象', () => {
    const arrayLike = { length: 0 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确应用mapFn', () => {
    const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }
    const result = myFrom(arrayLike, (value: number) => value * 2)
    expect(result).toEqual([2, 4, 6])
  })

  it('应正确处理thisArg', () => {
    const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }
    const context = { multiplier: 3 }
    const result = myFrom(
      arrayLike,
      function (this: { multiplier: number }, value: number) {
        return value * this.multiplier
      },
      context,
    )
    expect(result).toEqual([3, 6, 9])
  })

  it('应抛出错误,当arrayLike为null或undefined时', () => {
    expect(() => myFrom(null)).toThrow(TypeError)
    expect(() => myFrom(undefined)).toThrow(TypeError)
  })

  it('应抛出错误,当mapFn不是函数时', () => {
    const arrayLike = { 0: 1, 1: 2, length: 2 }
    expect(() => myFrom(arrayLike, 123 as any)).toThrow(TypeError)
  })

  it('应正确处理稀疏数组类对象', () => {
    const arrayLike = { 0: 'a', 2: 'c', length: 4 }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', undefined, 'c', undefined])
    expect(result.length).toBe(4)
  })

  it('应正确处理长度超出Number.MAX_SAFE_INTEGER的情况', () => {
    const arrayLike = { length: Number.MAX_SAFE_INTEGER + 1 }
    expect(() => myFrom(arrayLike)).toThrow(RangeError)
  })

  it('应正确处理负数长度', () => {
    const arrayLike = { length: -5 }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
    expect(result.length).toBe(0)
  })

  it('应正确处理可迭代对象(如Set)', () => {
    const set = new Set([1, 2, 3])
    const result = myFrom(set)
    expect(result).toEqual([1, 2, 3])
  })

  it('应正确处理可迭代对象(如Map)', () => {
    const map = new Map([
      ['a', 1],
      ['b', 2],
    ])
    const result = myFrom(map)
    expect(result).toEqual([
      ['a', 1],
      ['b', 2],
    ])
  })

  it('应正确处理带有非数字length属性的对象', () => {
    const arrayLike = { 0: 'a', 1: 'b', length: '2' as any }
    const result = myFrom(arrayLike)
    expect(result).toEqual(['a', 'b'])
  })

  it('应正确处理length为NaN的情况', () => {
    const arrayLike = { 0: 'a', length: Number.NaN }
    const result = myFrom(arrayLike)
    expect(result).toEqual([])
  })

  it('应正确处理mapFn的第二个参数index', () => {
    const arrayLike = { 0: 10, 1: 20, length: 2 }
    const result = myFrom(arrayLike, (v, i) => i)
    expect(result).toEqual([0, 1])
  })

  it('应正确处理mapFn返回undefined的情况', () => {
    const arrayLike = { 0: 1, 1: 2, length: 2 }
    const result = myFrom(arrayLike, () => undefined)
    expect(result).toEqual([undefined, undefined])
  })

  it('应正确处理thisArg为null的情况', () => {
    const arrayLike = { 0: 2, 1: 4, length: 2 }
    function fn(this: { x?: number } | null, v: number): number {
      return this ? (this.x || 1) * v : v
    }
    const result = myFrom(arrayLike, fn, null)
    expect(result).toEqual([2, 4])
  })

  it('应正确处理mapFn为箭头函数时的this', () => {
    const arrayLike = { 0: 2, 1: 4, length: 2 }
    const context = { x: 10 }
    // 箭头函数不绑定this,即使传入thisArg也无效
    const result = myFrom(arrayLike, (v: number) => v, context)
    expect(result).toEqual([2, 4])
  })

  it('应正确处理对象没有length属性的情况', () => {
    const obj = { 0: 'a', 1: 'b' } as any
    const result = myFrom(obj)
    expect(result).toEqual([])
  })
})

答案

类型路径
JS 版本problems/Day 06/answer.js
TS 版本problems/Day 06/ts/answer.ts
Review06.md

内容基于 MIT 许可 | 保持节奏 · 持续积累