跳到主要内容

语言和 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 实现​

答案
function stringify (obj) {
  const type = typeof obj
  if (obj === null) {
    // 处理 null
    return 'null'
  }
  if (type === 'string') {
    // 处理字符串类型
    return `"${obj}"`
  }
  if (type === 'number' || type === 'boolean') {
    // 处理数字和布尔类型
    return String(obj)
  }
  if (Array.isArray(obj)) {
    // 处理数组
    const elements = obj.map(item => stringify(item))
    return `[${elements.join(',')}]`
  }
  // 处理对象
  const props = Object.keys(obj).map(key => {
    const value = stringify(obj[key])
    return `"${key}":${value}`
  })
  return `{${props.join(',')}}`
}

function parse (json) {
  return eval(`(${json})`)
}

export default {
  stringify,
  parse
}

Open browser consoleTests

实现 Promise​

答案

核心概念 Promise 是异步编程的一种解决方案,遵循 Promises/A+ 规范。它本质是一个状态机,具有以下核心特性:

  1. 三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),状态一旦改变不可逆转。
  2. 决议机制:通过 resolve 和 reject 函数改变状态并传递结果或拒绝原因。
  3. 链式调用:then 方法返回一个新的 Promise,支持链式异步编排,并通过 Promise 解决程序(Promise Resolution Procedure)递归展开嵌套的 Promise / Thenable。
  4. 异步微任务执行:回调函数必须在当前执行栈清空后的微任务(如 queueMicrotask 或 MutationObserver/setTimeout)中异步执行。

核心实现:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
constructor (executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []

const resolve = (value) => {
if (value instanceof MyPromise) {
return value.then(resolve, reject)
}
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}

const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}

try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}

then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }

const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}

const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}

if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})

return promise2
}

catch (onRejected) {
return this.then(null, onRejected)
}
}

function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
}

let called = false
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
const then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}

面试官视角

  • 核心考查候选人对异步事件循环、Promises/A+ 规范(尤其是链式调用与循环引用防御)的理解深度。
  • 加分项:能主动说明为什么 then 返回新 Promise 而非 this,以及 queueMicrotask 相对于 setTimeout 的微任务优势。

延伸阅读

实现 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.all 接收一个可迭代对象(如数组),返回一个新 Promise:

  1. 全胜则胜:当所有输入的 Promise 都兑现(fulfilled)时,返回的 Promise 兑现,其值为所有输入结果按原顺序组成的数组。
  2. 一败则败(快速失败):只要有一个 Promise 拒绝(rejected),返回的 Promise 立即以该拒绝原因拒绝。
  3. 空集合处理:传入空可迭代对象时,同步返回已兑现为空数组的 Promise。

手写实现:

function promiseAll (iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable)
const len = promises.length
if (len === 0) {
return resolve([])
}

const results = new Array(len)
let completedCount = 0

promises.forEach((item, index) => {
// 通过 Promise.resolve 兼容非 Promise 的常量值
Promise.resolve(item).then(
(value) => {
results[index] = value
completedCount++
if (completedCount === len) {
resolve(results)
}
},
(reason) => {
// 快速失败:第一个异常直接 reject
reject(reason)
}
)
})
})
}

// 验证
const p1 = Promise.resolve(10)
const p2 = 20
const p3 = new Promise((resolve) => setTimeout(() => resolve(30), 50))
promiseAll([p1, p2, p3]).then(console.log) // [10, 20, 30]

面试官视角

  • 常见考察陷阱:使用 results.length === len 判断完成情况(错误,因为数组按索引直接赋值会导致中间空位也占据 length),必须使用独立的计数器 completedCount。
  • 加分项:支持非数组的可迭代参数(Array.from(iterable)),且对每个元素包裹 Promise.resolve。

延伸阅读

手写代码实现 promise.race​

答案

核心概念 Promise.race 接收可迭代对象,返回一个率先完成的 Promise:

  • 只要迭代器中的某个 Promise 率先兑现或拒绝,返回的 Promise 就以该结果率先做出相同状态的决议。
  • 若传入空数组,返回的 Promise 将永远处于 pending 状态(符合规范)。

手写实现:

function promiseRace (iterable) {
return new Promise((resolve, reject) => {
for (const item of iterable) {
Promise.resolve(item).then(resolve, reject)
}
})
}

// 验证
const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 50))
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 200))
promiseRace([fast, slow]).then(console.log) // 输出: 'fast'

面试官视角

  • 考查点非常纯粹:Promise 状态一旦改变不可逆(调用多次 resolve/reject 仅首次生效),利用此特性可直接将外部 resolve/reject 传给每个子项。

延伸阅读

promise.finally 怎么实现的?​

答案

核心概念 Promise.prototype.finally(callback) 用于注册在 Promise 敲定(无论兑现还是拒绝)后都会执行的回调:

  1. callback 不接受任何参数。
  2. 保持原状态透传:如果原 Promise 成功,finally 会把成功值继续往下传;若原 Promise 失败,继续向下抛出原 reason。
  3. 若 callback() 返回一个 Promise,finally 会等待该 Promise 决议完毕后再向下透传原值;若 callback 内部抛出异常或返回 rejected Promise,则最终以该新错误拒绝。

手写实现:

Promise.prototype.myFinally = function (callback) {
const P = this.constructor || Promise
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
)
}

// 验证
Promise.resolve('ok')
.myFinally(() => console.log('执行清理'))
.then(val => console.log('最终值:', val)) // 最终值: ok

面试官视角

  • 容易忽略的细节:P.resolve(callback()).then(...) 保证了即使 callback 是异步 Promise 也能正确等待其完成,同时保持原本的 value/reason 干净透传。

延伸阅读

实现 async 函数​

答案

核心概念 async/await 是 Generator 函数与 Promise 的语法糖,核心思想是协程(Coroutine)的自动执行器(如 co 模块):

  • Generator 函数暂停在 yield 处,将控制权交还给自动执行器。
  • 自动执行器监听 yield 返回的 Promise 状态,在其 resolve 时调用 iterator.next(value) 将结果注入回生成器,在其 reject 时调用 iterator.throw(reason) 抛出异常。

手写实现(Co 模式自动执行器):

function asyncToGenerator (generatorFn) {
return function (...args) {
const gen = generatorFn.apply(this, args)

return new Promise((resolve, reject) => {
function step (key, arg) {
let generatorResult
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}

const { value, done } = generatorResult
if (done) {
return resolve(value)
} else {
return Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
)
}
}

step('next')
})
}
}

// 验证
const delay = (ms, val) => new Promise(resolve => setTimeout(() => resolve(val), ms))

const testAsync = asyncToGenerator(function * () {
const a = yield delay(50, 1)
const b = yield delay(50, 2)
return a + b
})

testAsync().then(res => console.log('结果:', res)) // 结果: 3

面试官视角

  • 考查从生成器、协程到现代 async/await 的技术演进理解;要求熟练手写 step 递归推进逻辑及异常捕获。

延伸阅读

实现 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)
}

注意事项与细节:

  1. this 绑定:context 为 null 或 undefined 时,this 默认指向全局对象(浏览器下为 window)。
  2. 参数处理:apply 接收参数数组,call 依次传递参数。
  3. 属性冲突:临时属性名应唯一,推荐用 Symbol 防止覆盖原有属性。
  4. 返回值:需返回原函数的执行结果。
  5. 严格模式:严格模式下 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​

答案

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 操作符​

答案

核心概念 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。其执行内部逻辑(规范规范:[[Construct]] 内部方法):

  1. 创建空对象:创建一个全新的普通 JavaScript 对象,并将其原型([[Prototype]])链接到构造函数的 prototype 属性。
  2. 绑定 this 并执行:将新创建的对象作为 this 上下文,执行构造函数代码,初始化属性与方法。
  3. 返回值裁决:检查构造函数的返回值。如果构造函数显式返回了一个对象类型(非原始值),则最终返回该对象;否则返回第一步创建的新对象。

手写实现:

function myNew (constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('Constructor must be a function')
}

// 1. 创建继承自 constructor.prototype 的新对象
const obj = Object.create(constructor.prototype)

// 2. 执行构造函数,绑定 this 为 obj
const result = constructor.apply(obj, args)

// 3. 判决返回值:若返回引用类型则采用该结果,否则返回 obj
const isObject = typeof result === 'object' && result !== null
const isFunction = typeof result === 'function'
return (isObject || isFunction) ? result : obj
}

// 验证
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
return `Hi, I am ${this.name}`
}

const p = myNew(Person, 'Alice', 18)
console.log(p.sayHi()) // Hi, I am Alice
console.log(p instanceof Person) // true

面试官视角

  • 考查重点在于原型链继承的串联(Object.create 或 Object.setPrototypeOf)以及对返回值的严格判断(必须区分 null 与真实引用类型)。

延伸阅读