Pressidian
花园入口
笔记
项目
关于
实验室
GitHub
花园入口
笔记
项目
关于
实验室
GitHub

KNOWLEDGE PATHS

笔记库
当前位置
笔记库/前端/面试/代码

面试-常考经典2

4 分钟阅读 · Note

目录树 578 篇

          • 面试-常考经典1
          • 面试-常考经典2
          • 面试-数组专项
        • 可投递企业
      • 前端技术栈
    • 笔记目录
    • CLAUDE.md
    • Vue 组件与 Render 函数

关联笔记 6

↗面试-常考经典1同一路径↗面试-数组专项同一路径↗5 最长回文子串 ⌚️共同主题↗解析模板字符串共同主题↗牛客 101共同主题↗数组&字符串技巧共同主题
  • 面试-常考经典2

面试-常考经典2

> Last Format Time:6/12/2026 20:34:58

啥样的题目都有哈,没分类。这里是我自己总结的一些题目 🔥为常考,⌚️为我面试的时候考过

异步加法⌚️

实现 sum 函数

// 异步加法
function asyncAdd(a, b, cb) {
    setTimeout(() => {
        cb(null, a + b)
    }, Math.random() * 1000)
}

async function total() {
    const res1 = await sum(1, 3, 5, 7, 9, 1, 3, 5, 7, 9);
    const res2 = await sum(1, 3, 5, 7, 9, 1, 3, 5, 7, 9);
    console.log([res1, res2])
    return [res1, res2]
}
total()

// 实现 sum 函数
async function sum(...args) {
    // 边界条件:没有参数返回0,只有一个参数直接返回该参数
    if (args.length === 0) return 0;
    if (args.length === 1) return args[0];
    
    // 边界条件:如果只有两个参数,直接调用 asyncAdd 相加
    if (args.length === 2) {
        return new Promise((resolve) => {
            asyncAdd(args[0], args[1], (_, res) => resolve(res));
        });
    }

    // 二分法:将数组从中间劈开,分成左右两部分
    const mid = Math.floor(args.length / 2);
    const leftArgs = args.slice(0, mid);
    const rightArgs = args.slice(mid);

    // 并行计算左右两部分的和(递归调用)
    // Promise.all 会让左右两边的计算同时开始,而不是等左边算完再算右边
    const [leftSum, rightSum] = await Promise.all([
        sum(...leftArgs),
        sum(...rightArgs)
    ]);

    // 最后将左右两边的结果相加
    return new Promise((resolve) => {
        asyncAdd(leftSum, rightSum, (_, res) => resolve(res));
    });
}

防抖与节流 ⌚️

  • 考察点:性能优化、闭包、定时器。
  • 场景:搜索框输入(防抖)、滚动加载/按钮防重复点击(节流)。
  • 关键点:setTimeout 的使用、this 指向和参数传递。
// 防抖
function debounce(fn, time) {
	// 闭包
    let timer = null

	// 返回一个经过包装的函数
    return function () {
	    // 获取函数的context与参数
        let _this = this
        let arg = arguments
	    
	    // 移除定时器,再设置一个新的
        if (timer) {
            clearTimeout(timer)
            timer = null
        }
        timer = setTimeout(() => {
            fn.apply(_this, arg)
            // fn.apply(this, arguments) 这样写也是一样的,没有区别
        }, time)
    }
}

// 节流
function throttle(fn, time) {
    let timer = null

    return function () {
        let _this = this
        let arg = arguments
		
		// 如果存在定时器,就什么也不做,定时器会自动的清除自己
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(_this, arg)
	            
	            // 时间到了后,将timer清除
	            // 写 clearTimeout(timer)是一样的,清除后的timer也是null
                timer = null
            }, time)
        }
    }
}


深拷贝

  • 考察点:递归、引用类型处理、循环引用。
  • 场景:复杂对象的数据隔离。
  • 关键点:处理数组/对象/日期/正则,以及如何处理循环引用(使用 WeakMap)。
function deepClone(obj, map = new WeakMap()) {
	// typeof 是object的只有object与null
    if (typeof obj !== 'object' || obj === null) {
        return obj
    }

    if (obj instanceof Date) {
        return new Date(obj.getTime())
    }

    if (obj instanceof RegExp) {
        return new RegExp(obj.source, obj.flags)
    }
	
	// 如果在缓存中存在拷贝出的对象,说明这里出现了循环引用
    if (map.has(obj)) {
        return map.get(obj)
    }

    if (Array.isArray(obj)) {
        let newArr = []
		
		// 建立起被拷贝对象与拷贝出的对象之间的映射
        map.set(obj, newArr)
        obj.forEach((item, index) => {
            newArr[index] = deepClone(item, map)
        })
        return newArr
    }
	
    let newObj = {}
    map.set(obj, newObj)
    Object.keys(obj).forEach((key) => {
        newObj[key] = deepClone(obj[key], map)
    })
    return newObj
}
test = {
    a: 1,
    b: 2,
    c: {
        d: 4,
        e: 5,
    },
}

obj = {
    a: 1,
    b: test,
    c: {
        d: 4,
        e: 5,
    },
}

let newObj = deepClone(obj)

obj.b.a = 100
console.log(newObj, obj)


手写 Promise(promise、all、allSettled、race、any与map)⌚️🔥

  • 考察点:异步编程、状态机模式、发布订阅。
  • 场景:理解异步控制流。
  • 关键点:三种状态(Pending/Fulfilled/Rejected)的转换、then 的链式调用、resolve 和 reject 的处理。

promise

class MyPromise {
    constructor(executor) {
        this.state = 'pending'
        this.value = null
        this.reason = null
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
		
        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfiled'
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
		
        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach((fn) => fn())
            }
        }
		
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
	
	// then 这里的两个函数实际上可以直接填两个函数的名字,其参数会自动在内部被填上
    then(onFulfilled, onRejected) {
        if (this.state === 'fulfiled') {
            onFulfilled(this.value)
        } else if (this.state === 'rejected') {
            onRejected(this.reason)
        } else if (this.state === 'pending') {
            this.onFulfilledCallbacks.push(() => onFulfilled(this.value))
            this.onRejectedCallbacks.push(() => onRejected(this.reason))
        }
    }
	
    catch(onRejected) {
        return this.then(null, onRejected)
    }
	
    finally(onFinally) { 
        return this.then(
            (value) => {
                onFinally()
                return value
            },
            (reason) => {
                onFinally()
                throw reason
            },
        )
    }
}

上面的是不能链式调用的Promise,将其中的成员方法then改造为下面的就可以实现链式调用了:

// 核心改造:then 方法
    then(onFulfilled, onRejected) {
        // 处理未传入回调的情况 - 需要将函数设为默认或透传
        onFulfilled =
            typeof onFulfilled === 'function' ? onFulfilled : (value) => value
        onRejected =
            typeof onRejected === 'function'
                ? onRejected
                : (reason) => {
                      throw reason
                  }

        // 1. 关键!每次调用 .then() 都必须返回一个新的 Promise
        return new ChainablePromise((resolve, reject) => {
            // 2. 定义一个函数来处理 onFulfilled 回调的执行和结果
            const handleFulfillment = () => {
                try {
                    // 执行用户传入的成功回调
                    const result = onFulfilled(this.value)
                    // 检查回调的返回值
                    if (result instanceof ChainablePromise) {
                        // 如果返回的是一个 Promise,就等待它完成
                        result.then(resolve, reject)
                    } else {
                        // 如果返回的是普通值,就用这个值 resolve 新的 Promise
                        resolve(result)
                    }
                } catch (error) {
                    // 如果回调函数内部抛出异常,则 reject 新的 Promise
                    reject(error)
                }
            }
			
            // 3. 定义一个函数来处理 onRejected 回调,和上面的逻辑很像
            const handleRejection = () => {
                try {
                    const result = onRejected(this.reason)
                    if (result instanceof ChainablePromise) {
                        result.then(resolve, reject)
                    } else {
                        resolve(result)
                    }
                } catch (error) {
                    reject(error)
                }
            }
			
            // 4. 根据当前 Promise 的状态,决定何时执行回调
            if (this.state === 'fulfilled') {
                handleFulfillment()
            }
            if (this.state === 'rejected') {
                handleRejection()
            }
            if (this.state === 'pending') {
                // 如果还是 pending 状态,就将回调函数存入队列,等待未来执行
                this.onFulfilledCallbacks.push(handleFulfillment)
                this.onRejectedCallbacks.push(handleRejection)
            }
        })
    }

all

// 这是我面试的时候写的,没有返回一个Promise,没有提供resolve与reject

// 另外这里也不是写在原型上的,而是挂载在构造函数上
Promise.prototype.myAll=function(promises){
    let n = promises.length
    let settledCount =0

	// 这里也是后加上去的
    let res = []

    promises.forEach((promise,index)=>{
        Promise.resolve(promise).then((value)=>{
            settledCount++
            if(settledCount===n){
                res[index] = value
                resolve(res)
            }
        }).catch(reject(
            new Error('error!!!')
        ))
    })
}
  • 挂载在构造函数上 :不需要创建实例,直接用 Promise.myAll() 调用,类似 Promise.all() 和 Promise.race() 这种静态方法
  • 挂载在原型上 :需要先 new Promise() 创建实例,然后在实例上调用 当前代码挂载在构造函数上是正确的做法,因为 Promise.myAll 的语义和原生的 Promise.all() 一致,都是直接传入一组 promise,而不是在某个 promise 实例上调用。
Promise.myAll = (promises) => {
    return new Promise((resolve, reject) => {
        let total = promises.length
        let results = []
        let finishedCount = 0

        promises.forEach((promise, index) => {
            Promise.resolve(promise)
                .then((value) => {
                    results[index] = value
                    finishedCount++
                    if (finishedCount === total) {
                        resolve(results)
                    }
                })
                .catch(reject)
        })
    })
}

const promises = [
    Promise.resolve('成功1'),
    // Promise.reject('错误'),
    Promise.resolve('成功2'),
    new Promise((resolve) => setTimeout(() => resolve('延时成功'), 2000)),
]

Promise.myAll(promises)
    .then((values) => {
        console.log(values)
    })
    .catch((error) => {
        console.log(error)
    })
// [ '成功1', '成功2', '延时成功' ]

allSettled

和all很像,不过是将处理返回结果的代码移动到了finally中

Promise.myAllSettled = function (promises) {
    return new Promise((resolve, reject) => {
        const results = new Array(promises.length)
        let settledCount = 0

        promises.forEach((promise, index) => {
            Promise.resolve(promise)
                .then((value) => {
                    results[index] = { status: 'fulfilled', value }
                })
                .catch((reason) => {
                    results[index] = { status: 'rejected', reason }
                })
                .finally(() => {
                    settledCount++
                    if (settledCount === promises.length) {
                        resolve(results)
                    }
                })
        })
    })
}


const promises = [
    Promise.resolve('成功1'),
    Promise.reject('错误'),
    Promise.resolve('成功2'),
    new Promise((resolve) => setTimeout(() => resolve('延时成功'), 2000)),
]

Promise.MyAllSettled(promises).then((results) => {
    console.log(results)
})

// [
//     ({ status: 'fulfilled', value: '成功1' },
//     { status: 'rejected', reason: '错误' },
//     { status: 'fulfilled', value: '成功2' },
//     { status: 'fulfilled', value: '延时成功' })
// ]

race

Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    // 1. 遍历所有 Promise
    promises.forEach(promise => {
      // 2. 使用 Promise.resolve 包装,确保处理非 Promise 值
      Promise.resolve(promise)
        .then(resolve) // 3. 任何一个 Promise 成功,立即 resolve
        .catch(reject); // 4. 任何一个 Promise 失败,立即 reject
    });
  });
};

const promises = [
    // Promise.resolve('成功1'),
    Promise.reject('错误'),
    // Promise.resolve('成功2'),
    new Promise((resolve) => setTimeout(() => resolve('延时成功'), 2000)),
]

Promise.myRace(promises)
    .then((result) => {
        console.log(result)
    })
    .catch((error) => {
        console.log(error)
    })
// 错误

any

和all很像

Promise.myAny = function(promises) {
  return new Promise((resolve, reject) => {
    const errors = []; // 1. 收集所有失败的原因
    let rejectedCount = 0; // 2. 计数器,记录已失败的 Promise 数量

    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(resolve) // 3. 任何一个 Promise 成功,立即 resolve
        .catch(error => {
          // 4. 记录失败原因
          errors.push(error);
          rejectedCount++;
          // 5. 只有当所有 Promise 都失败时,才 reject
          if (rejectedCount === promises.length) {
            // AggregateError 是 ES2021 内置的错误类型
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        });
    });
  });
};

map

实际上是带有并发限制的请求:

/**
 * 实现一个带并发限制的 Promise.map
 * @param {Array} list - 需要处理的数组
 * @param {Function} iteratee - 映射函数
 * @param {Object} options - 选项,例如 { concurrency: 2 } 表示最多同时执行2个
 * @returns {Promise}
 */
Promise.myMapWithConcurrency = function(list, iteratee, options = {}) {
  const concurrency = options.concurrency || Infinity; // 默认无限制
  const results = new Array(list.length);
  let nextIndexToProcess = 0; // 下一个要处理的任务索引
  let completedCount = 0;

  return new Promise((resolve, reject) => {
    // 启动第一批任务,数量不超过并发限制
    function startNext() {
      // 这里会跑满
      while (nextIndexToProcess < list.length && activeCount < concurrency) {
        const index = nextIndexToProcess++;
        activeCount++;

        // 执行任务
        Promise.resolve(iteratee(list[index], index))
          .then(value => {
            results[index] = value;
            completedCount++;
            activeCount--;
            if (completedCount === list.length) {
		        resolve(results);
		    }
            startNext(); // 当前任务完成,尝试启动下一个,第一批任务完成一个,就空了一个,就再调一个
          })
          .catch(reject); // 任何一个任务失败,整个 Promise.map 失败
      }
    }
    let activeCount = 0;
    // 开始执行
    startNext();
  });
};

数组扁平化

  • 考察点:数组操作、递归、迭代。
  • 场景:处理树形结构数据(如菜单、目录)。
  • 关键点:flat() 的模拟实现、reduce + concat、正则方法。
Array.prototype.myFlat = function (depth = 1) {
    // console.log(this) ← 普通函数的 this 指向调用者(即数组本身)我最开始是使用的箭头函数,由于箭头函数没有this,会有问题
    if (depth === 0) return this

    return this.reduce((acc, cur) => {
        if (Array.isArray(cur)) {
            return acc.concat(cur.myFlat(depth - 1))
        } else {
	        // 注意concat只能“去皮”一层数组
	        // 在直接concat原始值的时候,就会直接加入数组
            return acc.concat(cur)
        }
    }, [])
}

console.log(arr.myFlat(1))
// [ 1, 3, 4, 6, 1, 3, [ 1, 2 ] ]

🔥函数柯里化

  • 考察点:闭包、函数式编程思想。
  • 场景:参数复用、延迟执行。
  • 关键点:判断参数个数是否满足,返回新函数继续接收参数。

核心思想:将一个多参数函数转换为一系列单参数函数的链式调用 这这这,能考吗?

function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args)
        }
        return function (nextArgs) {
            return curried.apply(this, args.concat(nextArgs))
        }
    }
}

const fn = (a, b) => {
    console.log(a, b)
}

// 这里的1是第一次返回的curried的...args收集进去的,2是第二次返回的匿名函数的nextArgs收集进去的
let curriedFn = curry(fn)(1)(2)
// 1 2

工作流程:

  1. curried(1) - 收集到 1 个参数,不足 3 个,返回新函数
  2. 新函数被调用时 curried(1)(2),继续收集得到 [1, 2]
  3. 最终 curried(1)(2)(3) - 参数达到 3 个,调用原函数

手写 call / apply / bind

  • 考察点:this 指向改变、函数执行上下文。
  • 场景:借用其他对象的方法。
  • 关键点:将函数绑定到指定对象上执行,处理参数列表,bind 需要返回一个函数。
// ...args 将参数收集到了args中
Function.prototype.myCall = function (thisArg, ...args) {
    thisArg = thisArg || window
    // 将被call的函数(this)绑定到thisArg上,这样在调用fn时,this指向thisArg
    thisArg.fn = this

    let res = thisArg.fn(args)
    delete thisArg.fn
    return res
}

// apply的实现和这个几乎一样,就是参数不同,apply接收一个数组,call接收多个参数
Function.prototype.myApply = function (thisArg, args) {
    thisArg = thisArg || window
    // 借助临时的属性fn,将被apply的函数(this)绑定到thisArg上,这样在调用fn时,this指向thisArg
    thisArg.fn = this

    let res = thisArg.fn(args)
    delete thisArg.fn
    return res
}

// 返回一个被call过的函数就可以了
Function.prototype.myBind = function (thisArg, ...args) {
    let fn = this
    // 这里会返回一个新的函数,如果在bind的时候args没有传,可以在调用新函数时传
    return function (...rest) {
        // 如果在args的位置传了值,在调用新函数时可以不用传了
        fn.call(thisArg, ...args, ...rest)
    }
}


事件发布订阅模式

  • 考察点:设计模式、解耦。
  • 场景:Vue/React 的底层原理、组件通信。
  • 关键点:维护一个事件中心(对象),包含 on, emit, off 方法。
class EventBus {
    constructor() {
        // 创建一个空对象,用于存储事件监听函数
        this.events = {}
    }

    on(event, callback) {
        // 如果事件不存在,创建一个空数组
        if (!this.events[event]) {
            this.events[event] = []
        }
        // 如果存在存储事件的数组,就将回调函数添加到数组中
        this.events[event].push(callback)
    }

    off(event, callback) {
        // 如果事件不存在,直接返回
        if (!this.events[event]) return

        // 如果没有传入回调函数,就删除事件监听函数
        if (!callback) {
            delete this.events[event]
            return
        }
        // 过滤,将传入的回调函数从数组中移除
        this.events[event] = 
	        this.events[event].filter((cb) => cb !== callback)
    }

    emit(event, ...args) {
        // 如果事件不存在,直接返回
        if (!this.events[event]) return
        
        // 遍历事件监听函数数组,调用每个回调函数
        this.events[event].forEach((callback) => callback(...args))
    }

    once(event, callback) {
        const wrapper = (...args) => {
            callback(...args)
            this.off(event, wrapper)
        }
        // 订阅了事件,将处理的函数中添加了off方法,用于移除事件监听函数
        this.on(event, wrapper)
    }
}

const bus = new EventBus()

function handleUserClick(data) {
    console.log('用户点击:', data)
}

function handleUserLogin(data) {
    console.log('用户登录:', data)
}

function handleUserLoginOnce(data) {
    console.log('仅触发一次:', data)
}

bus.on('click', handleUserClick)
bus.on('login', handleUserLogin)
bus.once('login', handleUserLoginOnce)
//
// EventBus {
//   events: {
//     click: [ [Function: handleUserClick] ],
//     login: [ [Function: handleUserLogin], [Function: wrapper] ]
//   }
// }

console.log('=== 触发 click 事件 ===')
bus.emit('click', { x: 100, y: 200 })
bus.emit('click', { x: 150, y: 250 })
// === 触发 click 事件 ===
// 用户点击: { x: 100, y: 200 }
// 用户点击: { x: 150, y: 250 }

console.log('\n=== 触发 login 事件 ===')
bus.emit('login', { username: '张三', time: '2024-01-01' })
bus.emit('login', { username: '李四', time: '2024-01-02' })
// === 触发 login 事件 ===
// 用户登录: { username: '张三', time: '2024-01-01' }
// 仅触发一次: { username: '张三', time: '2024-01-01' }
// 用户登录: { username: '李四', time: '2024-01-02' }

console.log('\n=== 移除 click 监听 ===')
bus.off('click', handleUserClick)
bus.emit('click', { x: 300, y: 300 })

console.log('\n=== 移除所有 login 监听 ===')
bus.off('login')
bus.emit('login', { username: '王五', time: '2024-01-03' })


数组去重

  • 考察点:ES6 新特性、数据结构。
  • 场景:数据处理。
  • 关键点:Set 的使用、filter、reduce、Map、includes
const arr = [1, 2, 2, 3, 3, 3, 4, 4, 5, '1', '2', 1, 2]

function unique1(arr) {
	// 将数组转为set,再展开,优雅实在是优雅
    return [...new Set(arr)]
    // return Array.from(new Set(arr)) 也是一样的
}

function unique2(arr) {
    const result = []
    for (const item of arr) {
        if (!result.includes(item)) {
            result.push(item)
        }
    }
    return result
}

// 就是换了一个存储的数据结构,本质与上面的没有什么区别
function unique3(arr) {
    const map = new Map()
    for (const item of arr) {
        if (!map.has(item)) {
            map.set(item, item)
        }
    }
    return [...map.values()]
}

function unique4(arr) {
    return arr.reduce((acc, item) => {
        if (!acc.includes(item)) {
            acc.push(item)
        }
        return acc
    }, [])
}

console.log('原始数组:', arr)
console.log('\n方法1 - Set:', unique1(arr))
console.log('方法2 - includes:', unique2(arr))
console.log('方法3 - Map:', unique3(arr))
console.log('方法4 - reduce:', unique4(arr))


手写 new 操作符

  • 考察点:原型链、构造函数机制。

  • 场景:理解对象创建过程。

  • 关键点:创建一个空对象、链接原型、绑定 this、判断返回值。

  • 创建空对象

  • 将对象的隐式原型链接到函数的原型上

  • 将空对象作为调用者,调用构造函数

  • 判断构造出来的结果,根据结果的类型返回值

function myNew(constructor, ...args) {
    const obj = {}
    
    // 使用 Object.setPrototypeOf() 方法设置原型链。
    // 将obj的__proto__设置为constructor
    Object.setPrototypeOf(obj, constructor.prototype)
    
    // 将构造函数的 this 指向设置为 obj,等于obj.constructor(args)
    // 也就是初始化一个空的对象
    const result = constructor.apply(obj, args)
    
    return result instanceof Object ? result : obj
}

function Person(name, age) {
    this.name = name
    this.age = age
}

Person.prototype.sayHello = function () {
    return `你好,我是${this.name},今年${this.age}岁`
}

function Animal(type, sound) {
    this.type = type
    this.sound = sound
    return { custom: 'object' }
}

const p1 = new Person('张三', 25)
const p2 = myNew(Person, '李四', 30)

console.log('=== 使用 new ===')
console.log('p1:', p1)
console.log('p1.sayHello():', p1.sayHello())
// === 使用 new ===
// p1: Person { name: '张三', age: 25 }
// p1.sayHello(): 你好,我是张三,今年25岁

console.log('\n=== 使用 myNew ===')
console.log('p2:', p2)
console.log('p2.sayHello():', p2.sayHello())
// === 使用 myNew ===
// p2: { custom: 'object' }
// p2.sayHello(): 你好,我是李四,今年30岁

console.log('\n=== 构造函数返回对象的情况 ===')
const a1 = new Animal('狗', '汪汪')
const a2 = myNew(Animal, '猫', '喵喵')
console.log('a1:', a1)
console.log('a2:', a2)
// === 构造函数返回对象的情况 ===
// a1: { custom: 'object' }
// a2: { custom: 'object' }

console.log('\n=== 原型链验证 ===')
console.log('p2 instanceof Person:', p2 instanceof Person)
console.log(
    'Object.getPrototypeOf(p2) === Person.prototype:',
    Object.getPrototypeOf(p2) === Person.prototype,
)
// === 原型链验证 ===
// p2 instanceof Person: true
// Object.getPrototypeOf(p2) === Person.prototype: true


配置项深度合并

在插件或组件库开发中,需将用户配置与默认配置进行深度合并,确保嵌套属性也被正确覆盖。

function deepMerge(target, source) {
  for (let key in source) {
    if (source[key] instanceof Object && key in target) {
      Object.assign(source[key], deepMerge(target[key], source[key]));
    }
  }
  return Object.assign(target || {}, source);
}

sleep/delay

基于promise实现的

const sleep = (time) => {
  return new Promise((resolve) => {
    setTimeout(resolve(`sleep: ${time}ms`), time);
  });
};

sleep(23).then((message) => {
  console.log(message); // sleep: 23ms
})

isArray

// [补充说明]:兼容的数组检测方法
function myIsArray(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}

手写reduce

可以看另一个的实现,更加的简单 关键考点解析:

  • 初始值处理:必须判断 initialValue 是否为 undefined,而非简单使用 == null,以区分 null 和 undefined。
  • 空数组防御:当数组为空且无初始值时,应抛出类型错误,模拟原生行为。
  • 稀疏数组兼容:使用 i in this 检查索引是否存在,确保跳过空位,符合规范要求。
Array.prototype.fakeReduce =function(callback, init) {
    const arr = this
    
    function next(acc, index) {
        if (index >= arr.length) return acc
        
        let result = callback(acc, arr[index], index, arr)
        return next(result, index + 1)
    }
	
    let res
    
    if (init !== undefined) {
        res = next(init, 0)
    } else {
        res = next(arr[0], 1)
    }
	
    return res
}

let arr = [1, 3, 4, 5]
console.log(arr.fakeReduce((sum, item) => sum + item))
// 13
Array.prototype.myReduce = function (callback, init) {
    let arr = this

    function next(res, index) {
        if (index >= arr.length) return res
        return next(callback(res, arr[index], index, arr), index + 1)
    }

    if (init === undefined) {
        return next(arr[0], 1)
    } else {
        return next(init, 0)
    }
}

let arr = [1, 3, 2]

console.log(
    arr.myReduce((sum, item) => 
        sum + item
    ,0),
)