跳到主要内容

语言和 API✅

['1', '2', '3'].map(parseInt) 输出结果?

答案

输出结果是 [1, NaN, NaN]

答案解析

  1. parseInt 函数的第二个参数是基数(radix),它指定了解析数字时使用的进制。支持的基数范围是 2 到 36。
    • 如果基数为 0 或未指定,parseInt 会根据字符串的格式自动判断基数。
    • 基数为 1 是无效的,parseInt 会返回 NaN
    • 基数为 2 时,字符串必须是二进制数字。
    • 基数为 8 时,字符串可以是八进制数字。
    • 基数为 10 时,字符串可以是十进制数字。
    • 基数为 16 时,字符串可以是十六进制数字。
    • 基数为 36 时,字符串可以包含数字和字母, 不区分大小写。
  2. Array.prototype.map 方法会将数组中的每个元素传递给回调函数,并且会传递三个参数:当前元素、当前索引和原数组。

所以 ['1', '2', '3'].map(parseInt) 的执行等效为

['1', '2', '3'].map((element, index) => parseInt(element, index))

因此:

  • 对于第一个元素 '1'parseInt('1', 0) 返回 1,因为基数为 10。
  • 对于第二个元素 '2'parseInt('2', 1) 返回 NaN,因为基数 1 是无效的。
  • 对于第三个元素 '3'parseInt('3', 2) 返回 NaN,因为基数 2 不能解析数字 3。 所以最终结果是 [1, NaN, NaN]

铺平嵌套数组

答案

可以直接采用数组的 flat 方法来实现数组的铺平。

const arr = [1, [2, [3, 4], 5], [6, 7]]
const flattened = arr.flat(Infinity)
console.log(flattened) // Output: [1, 2, 3, 4, 5, 6, 7]

如果需要手写实现,可以使用递归的方式来实现数组的铺平。

function flattenArray (arr) {
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
return acc.concat(flattenArray(item))
} else {
return acc.concat(item)
}
}, [])
}
const arr = [1, [2, [3, 4], 5], [6, 7]]
const flattened = flattenArray(arr)
console.log(flattened) // Output: [1, 2, 3, 4, 5, 6, 7]

手写实现 instanceof

答案

instanceof 用于判断一个对象是否是某个构造函数的实例。它会沿着对象的原型链向上查找,直到找到对应的构造函数的 prototype,如果找到了就返回 true,否则返回 false

instanceof 的底层原理是判断构造函数的 prototype 属性是否出现在对象的原型链上。 可以通过循环遍历对象的原型链,判断是否有一项等于构造函数的 prototype

function isInstanceOf (obj, constructor) {
// 基本类型直接返回 false
if (typeof obj !== 'object' || obj === null) return false
let proto = Object.getPrototypeOf(obj)
while (proto) {
if (proto === constructor.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}

// 示例
console.log(isInstanceOf([], Array)) // true
console.log(isInstanceOf({}, Array)) // false
  • instanceof 只能判断引用类型(如对象、数组、函数),对基本类型(如字符串、数字、布尔值)无效。
  • 如果右侧参数不是函数,会抛出 TypeError
  • instanceof 判断的是原型链关系,不是构造函数本身。例如,Object.create(Array.prototype) 也会被 instanceof Array 判断为 true

手写实现 Object.create

答案

Object.create 的作用是创建一个新对象,并使用指定的原型对象和可选的属性来初始化它。常用于实现继承或创建没有原型的纯对象。Object.create(proto) 会返回一个新对象,这个对象的原型([[Prototype]])指向传入的 proto。如果传入 null,则创建一个没有原型的对象。

function myObjectCreate (proto) {
if (typeof proto !== 'object' && typeof proto !== 'function' || proto === null) {
throw new TypeError('Object prototype may only be an Object or null')
}
function F () {}
F.prototype = proto
return new F()
}

// 示例
const obj = { a: 1 }
const newObj = myObjectCreate(obj)
console.log(newObj.a) // 1
console.log(Object.getPrototypeOf(newObj) === obj) // true
  • 传入的 proto 必须是对象或 null,否则会抛出 TypeError
  • 通过这种方式创建的对象,其构造函数为 F,不是原型对象的构造函数。
  • 如果需要为新对象添加属性,可以在第二个参数中传递属性描述符(原生 Object.create 支持,手写版可扩展)。
  • 创建没有原型的对象时,Object.create(null) 常用于字典对象,避免原型链上的属性干扰。

手写 JSON.stringify 和 手写 JSON.parse 实现

实现 Promise

实现 Promise.allSettled

答案

Promise.allSettled 方法会接收一个 Promise 数组,并返回一个新的 Promise。当所有输入的 Promise 都已完成(无论是 fulfilled 还是 rejected)时,返回的 Promise 会以每个输入 Promise 的最终状态和值/原因组成的对象数组作为结果。

手写实现如下:

function allSettled (promises) {
return new Promise((resolve) => {
const results = []
let count = 0
const total = promises.length

if (total === 0) {
resolve([])
return
}

promises.forEach((p, i) => {
Promise.resolve(p)
.then(
value => {
results[i] = { status: 'fulfilled', value }
},
reason => {
results[i] = { status: 'rejected', reason }
}
)
.finally(() => {
count++
if (count === total) {
resolve(results)
}
})
})
})
}

// 示例
const ps = [
Promise.resolve(1),
Promise.reject(new Error('err')),
42
]
allSettled(ps).then(console.log)
// 输出:[{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}, {status: 'fulfilled', value: 42}]

手写代码实现 promise.all

手写代码实现 promise.race

promise.finally 怎么实现的?

实现 async 函数

参考资料

实现 call 或 apply 方法?

答案

实现 callapply 的核心是:将目标函数作为对象的属性临时挂载,通过对象调用改变 this,再执行并删除该属性。两者区别在于参数传递方式不同。

简易实现:

// 简单模拟 apply
Function.prototype.myApply = function (context, args) {
context = context || window
const fn = Symbol('fn')
context[fn] = this
let result
if (!args) {
result = context[fn]()
} else {
result = context[fn](...args)
}
delete context[fn]
return result
}

// 简单模拟 call
Function.prototype.myCall = function (context, ...args) {
return this.myApply(context, args)
}

注意事项与细节:

  1. this 绑定contextnullundefined 时,this 默认指向全局对象(浏览器下为 window)。
  2. 参数处理apply 接收参数数组,call 依次传递参数。
  3. 属性冲突:临时属性名应唯一,推荐用 Symbol 防止覆盖原有属性。
  4. 返回值:需返回原函数的执行结果。
  5. 严格模式:严格模式下 thisnull 时不会自动指向全局对象。

示例:

function greet (age) {
return `Hello, I am ${this.name}, ${age} years old`
}
const obj = { name: 'Alice' }
console.log(greet.myCall(obj, 20)) // Hello, I am Alice, 20 years old
console.log(greet.myApply(obj, [21])) // Hello, I am Alice, 21 years old

总结:

  • call/apply 都用于改变函数执行时的 this 指向。
  • call 参数逐个传递,apply 参数为数组。
  • 实现时注意属性唯一性、返回值、参数展开和 this 绑定细节。
  • bind 的实现更复杂,需考虑构造函数场景和参数柯里化。

实现 bind

答案

corejs

function customBind (context, ...bindParams) {
const self = this; const bound = function (...params) {
return self.apply(self instanceof bound ? self : context, bindParams.concat(params))
}
const noop = function () {}
if (this.prototype) {
// eslint-disable-next-line
noop.prototype = this.prototype; bound.prototype = new noop()

}
return bound
}

实现 new 操作符

48%