语言和 API✅
['1', '2', '3'].map(parseInt) 输出结果?
答案
输出结果是 [1, NaN, NaN]。
答案解析
parseInt函数的第二个参数是基数(radix),它指定了解析数字时使用的进制。支持的基数范围是 2 到 36。- 如果基数为 0 或未指定,
parseInt会根据字符串的格式自动判断基数。 - 基数为 1 是无效的,
parseInt会返回NaN。 - 基数为 2 时,字符串必须是二进制数字。
- 基数为 8 时,字符串可以是八进制数字。
- 基数为 10 时,字符串可以是十进制数字。
- 基数为 16 时,字符串可以是十六进制数字。
- 基数为 36 时,字符串可以包含数字和字母, 不区分大小写。
- 如果基数为 0 或未指定,
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 实现
答案
Tests
实现 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 方法?
答案
实现 call 和 apply 的核心是:将目标函数作为对象的属性临时挂载,通过对象调用改变 this,再执行并删除该属性。两者区别在于参数传递方式不同。
简易实现:
// 简单模拟 apply
// eslint-disable-next-line
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
// eslint-disable-next-line
Function.prototype.myCall = function (context, ...args) {
return this.myApply(context, args)
}
注意事项与细节:
- this 绑定:
context为null或undefined时,this默认指向全局对象(浏览器下为window)。 - 参数处理:
apply接收参数数组,call依次传递参数。 - 属性冲突:临时属性名应唯一,推荐用
Symbol防止覆盖原有属性。 - 返回值:需返回原函数的执行结果。
- 严格模式:严格模式下
this为null时不会自动指向全局对象。
示例:
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
答案
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
}