Array.prototype.map是怎么工作的?

439 阅读1分钟

作为一名合格的前端,js中的map是几乎每天都会写的函数,必须把它的来龙去脉 搞清楚,把它嚼碎后慢慢咀嚼然后咽下去,再配一杯82年的白开水🛫️

打开 mdn 搜索一下 Array.prototype.map 可以看到 map 有两个形参, 第一个参数是一个回调函数,第二个参数是 this 指向,在第一个参数的函数中又包括三个行参,分别是元素项、索引、原数组;
map 的返回值是一个 的数组。

知道了怎么用,那我们大概来手写一个 map 来试试:

Array.prototype.my_map = function(fn, self){
    let newArray = []
    // 判断 fn 是否是函数 否则报错(为了跟 map 一样,报错尽量跟它一摸一样)
    if(typeof fn !== 'function') throw `TypeError: ${fn} is not a function`
    for(let i = 0; i < this.length; i++){
        newArray.push(fn.call(self, this[i], i, this))
    }
    return newArray
}

// 测试1 通过😄
[1].map(v=>v+1)    // [2]
[1].my_map(v=>v+1) // [2]

// 测试2 通过😊 提示语是类似了,这边原生的map会多提示具体报错的行数
[1].map('ss')     // Uncaught TypeError: ss is not a function
                  // at Array.map (<anonymous>)
                  // at <anonymous>:1:5
[1].my_map('ss')  // Uncaught TypeError: ss is not a function

简单的测试了一下,基本没什么问题 🎉,还是来系统测试一遍看看,js的类型都有以下 内容,我们来依次测试看看:

  • null
  • undefined
  • Boolean
  • Number
  • BigInt
  • String
  • Object
  • Array
  • Function
  • Symbol
  • Date
// null ✅
[1].map(null)    // Uncaught TypeError: null is not a function
                 // at Array.map (<anonymous>)
                 // at <anonymous>:1:5
[1].my_map(null) // Uncaught TypeError: null is not a function

// undefined ✅
[1].map(undefined)    // Uncaught TypeError: undefined is not a function
                      // at Array.map (<anonymous>)
                      // at <anonymous>:1:5
[1].my_map(undefined) // Uncaught TypeError: undefined is not a function

// Boolean ✅
[1].map(Boolean)    // [true]
[1].my_map(Boolean) // [true]

[1].map(true)       // Uncaught TypeError: true is not a function
                    // at Array.map (<anonymous>)
                    // at <anonymous>:1:5
[1].my_map(true)    // Uncaught TypeError: true is not a function

[1].map(false)      // Uncaught TypeError: false is not a function
                    // at Array.map (<anonymous>)
                    // at <anonymous>:1:5
[1].my_map(false)   // Uncaught TypeError: false is not a function

// Number ✅
[1].map(Number)    // [1]
[1].my_map(Number) // [1]

[1].map(1)         // Uncaught TypeError: 1 is not a function
                   // at Array.map (<anonymous>)
                   // at <anonymous>:1:5
[1].my_map(1)      // Uncaught TypeError: 1 is not a function

// BigInt ✅
[1].map(BitInt)    // Uncaught ReferenceError: BitInt is not defined
                   // at <anonymous>:1:9
[1].my_map(BitInt) // Uncaught ReferenceError: BitInt is not defined
                   // at <anonymous>:1:12
                   
[1].map(1n)        // Uncaught TypeError: 1 is not a function
                   // at Array.map (<anonymous>)
                   // at <anonymous>:1:5
[1].my_map(1n)     // Uncaught TypeError: 1 is not a function

// String ✅
[1].map(String)    // ['1']
[1].my_map(String) // ['1']

[1].map('a')       // Uncaught TypeError: a is not a function
                   // at Array.map (<anonymous>)
                   // at <anonymous>:1:5
[1].my_map('a')    // Uncaught TypeError: a is not a function

// Object ❌
[1].map(Object)    // [Number]
[1].my_map(Object) // [Number]

[1].map({})        // Uncaught TypeError: #<Object> is not a function
                   // at Array.map (<anonymous>)
                   // at <anonymous>:1:5
[1].my_map({})     // Uncaught TypeError: [object Object] is not a function

// Array ❌
[1].map(Array)    // [Array(3)]
[1].my_map(Array) // [Array(3)]

[1].map([])       // Uncaught TypeError: [object Array] is not a function
                  // at Array.map (<anonymous>)
                  // at <anonymous>:1:5
[1].my_map([])    // Uncaught TypeError:  is not a function

// Function ✅
[1].map(Function)    // Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
                     // at Function (<anonymous>)
                     // at Array.map (<anonymous>)
                     // at <anonymous>:1:5
[1].my_map(Function) // Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
                     // at Function (<anonymous>)
                     // at Array.my_map (<anonymous>:6:26)
                     // at <anonymous>:1:5

[1].map(v=>v*2)      // [2]
[1].my_map(v=>v*2)   // [2]

// Symbol ❌
[1].map(Symbol)            // [Symbol(1)]
[1].my_map(Symbol)         // [Symbol(1)]

[1].map(Symbol('name'))    // Uncaught TypeError: Symbol(name) is not a function
                           // at Array.map (<anonymous>)
                           // at <anonymous>:1:5
[1].my_map(Symbol('name')) // Uncaught TypeError: Cannot convert a Symbol value to a string
                           // at Array.my_map (<anonymous>:4:54)
                           // at <anonymous>:1:5
                           
// Date ❌
[1].map(Date)          // ['Wed Jan 04 2023 23:00:52 GMT+0800 (中国标准时间)']
[1].my_map(Date)       // ['Wed Jan 04 2023 23:00:52 GMT+0800 (中国标准时间)']

[1].map(new Date())    // VM1423:1 Uncaught TypeError: [object Date] is not a function
                       // at Array.map (<anonymous>)
                       // at <anonymous>:1:5
[1].my_map(new Date()) // Uncaught TypeError: Wed Jan 04 2023 23:02:13 GMT+0800 (中国标准时间) is not a function

通过类型的测试后,基本没什么太大问题,就是提示语有些出入,对我们自己实现的 my_map 稍微调整下

Array.prototype.my_map = function(fn, self){
    const type = Object.prototype.toString.call(fn)
    let newArray = []
    // 判断 fn 是否是函数 否则报错(为了跟 map 一样,报错尽量跟它一摸一样)
    let warn = ''
    
    // Object 这边提示语有点歧义🤔️
    if(type.slice(8, -1) === 'Object') 
        warn = 'TypeError: #<Object> is not a function'
    // Array
    else if(type.slice(8, -1) === 'Array')
        warn = `TypeError: ${type} is not a function`
    // Symbol
    else if(type.slice(8, -1) === 'Symbol')
        warn = `TypeError: ${fn.toString()} is not a function`
    else warn = `TypeError: ${fn} is not a function`
    
    if(typeof fn !== 'function') throw warn
    for(let i = 0; i < this.length; i++){
        newArray.push(fn.call(self, this[i], i, this))
    }
    return newArray
}

// 再一次 Object ✅
[1].map({})        // Uncaught TypeError: #<Object> is not a function
                   // at Array.map (<anonymous>)
                   // at <anonymous>:1:5
[1].my_map({})     // Uncaught TypeError: #<Object> is not a function

// 再一次 Array ✅
[1].map([])       // Uncaught TypeError: [object Array] is not a function
                  // at Array.map (<anonymous>)
                  // at <anonymous>:1:5
[1].my_map([])    // Uncaught TypeError: [object Array] is not a function

// 再一次 Symbol ✅
[1].map(Symbol('name'))     // Uncaught TypeError: Symbol(name) is not a function
                            // at Array.map (<anonymous>)
                            // at <anonymous>:1:5
[1].my_map(Symbol('name'))  // Uncaught TypeError: Symbol(name) is not a function

调整完后基本可以实现 map 的效果,最后我们再来解释下 map 第二个参数具体是做什么的?🤔️
举个🌰:

const obj = {a: 2}
const fn = function(item) {
  return item * this.a
}

[1].map(fn, obj)    // [2]
[1].my_map(fn, obj) // [2]

想必看到这个🌰后,应该知道当前回调函数的 this 指向了 obj, 即可以调用 obj 内的属性了 ✌️
这边你可能还有疑问,如果第二个参数不传呢? 答案:this 指向 window

以上是个人的一些见解,如果有误,烦请帮忙指出 👉 十分感谢!