语言和 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 实现
实现 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
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)
}
注意事项与细节:
- 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
}