Skip to content

高频笔试题

本文档整理了 JavaScript 面试中常见的高频笔试题,包括输出题、代码分析题等。

输出题

题 1:let 和 var 在循环中的区别

js
// 使用 var
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i)
    }, 0)
}
// 输出结果:5 5 5 5 5

// 使用 let
for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i)
    }, 0)
}
// 输出结果:0 1 2 3 4

解析

  • var 的情况var 是函数作用域,循环中的 i 是同一个变量。当 setTimeout 回调执行时,循环已经结束,i 的值已经是 5,所以输出 5 个 5。
  • let 的情况let 是块级作用域,每次循环都会创建一个新的 i,每个 setTimeout 回调都捕获了对应迭代的 i 值,所以输出 0 1 2 3 4。

解决方案(使用 var 时)

js
// 方法1:使用立即执行函数(IIFE)
for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j)
        }, 0)
    })(i)
}

// 方法2:使用 bind
for (var i = 0; i < 5; i++) {
    setTimeout(function (j) {
        console.log(j)
    }.bind(null, i), 0)
}

// 方法3:使用箭头函数 + 参数
for (var i = 0; i < 5; i++) {
    setTimeout((j) => {
        console.log(j)
    }, 0, i)
}

题 2:this 指向问题

js
const obj = {
    name: 'Tom',
    sayName: function () {
        console.log(this.name)
    },
    sayNameArrow: () => {
        console.log(this.name)
    }
}

obj.sayName()        // 输出:Tom
obj.sayNameArrow()   // 输出:undefined(严格模式下)或 window.name(非严格模式)

const fn = obj.sayName
fn()                 // 输出:undefined(严格模式下)或 window.name(非严格模式)

解析

  • obj.sayName():普通函数,this 指向调用对象 obj,输出 Tom
  • obj.sayNameArrow():箭头函数,this 继承外层作用域(全局),this.nameundefined
  • fn():函数作为普通函数调用,this 指向全局对象

题 3:闭包陷阱

js
var arr = []
for (var i = 0; i < 3; i++) {
    arr[i] = function () {
        return i
    }
}
console.log(arr[0]())  // 输出:3
console.log(arr[1]())  // 输出:3
console.log(arr[2]())  // 输出:3

解析:所有函数都引用了同一个变量 i,当函数执行时,i 已经是 3。

解决方案

js
// 方法1:使用 let
var arr = []
for (let i = 0; i < 3; i++) {
    arr[i] = function () {
        return i
    }
}

// 方法2:使用 IIFE
var arr = []
for (var i = 0; i < 3; i++) {
    arr[i] = (function (j) {
        return function () {
            return j
        }
    })(i)
}

题 4:变量提升

js
console.log(a)      // 输出:undefined
console.log(b)      // 报错:Cannot access 'b' before initialization
console.log(c)      // 报错:c is not defined

var a = 1
let b = 2
const c = 3

解析

  • var a:变量提升,值为 undefined
  • let b:存在暂时性死区(TDZ),在声明前访问会报错
  • c:未声明,直接报错

题 5:类型转换

js
console.log([] + [])           // 输出:""(空字符串)
console.log([] + {})           // 输出:"[object Object]"
console.log({} + [])           // 输出:"[object Object]"
console.log({} + {})           // 输出:"[object Object][object Object]"
console.log(true + true)       // 输出:2
console.log(true + false)     // 输出:1
console.log('2' + 1)          // 输出:"21"
console.log('2' - 1)          // 输出:1

解析

  • + 运算符:如果有一个操作数是字符串,则进行字符串拼接
  • - 运算符:会将操作数转换为数字
  • 对象和数组在转换为字符串时会调用 toString() 方法

题 6:事件循环

js
console.log('1')

setTimeout(() => {
    console.log('2')
}, 0)

Promise.resolve().then(() => {
    console.log('3')
})

console.log('4')

// 输出顺序:1 4 3 2

解析

  1. 同步代码:console.log('1') → 输出 1
  2. setTimeout 放入宏任务队列
  3. Promise.then 放入微任务队列
  4. 同步代码:console.log('4') → 输出 4
  5. 执行微任务:console.log('3') → 输出 3
  6. 执行宏任务:console.log('2') → 输出 2

题 7:作用域链

js
var a = 1
function outer() {
    var a = 2
    function inner() {
        console.log(a)
        var a = 3
    }
    inner()
}
outer()  // 输出:undefined

解析inner 函数内部声明了 var a = 3,由于变量提升,a 在函数顶部被声明但未赋值,所以输出 undefined

题 8:原型链

js
function Person() {}
Person.prototype.name = 'Person'

const p = new Person()
p.name = 'p'

console.log(p.name)              // 输出:'p'
delete p.name
console.log(p.name)              // 输出:'Person'
console.log(p.__proto__.name)   // 输出:'Person'

解析

  • 第一次输出:访问实例属性 p.name,值为 'p'
  • delete p.name 删除实例属性
  • 第二次输出:实例属性不存在,通过原型链访问 Person.prototype.name,值为 'Person'

题 9:数组方法

js
const arr = [1, 2, 3, 4, 5]

console.log(arr.map(item => item * 2))        // [2, 4, 6, 8, 10]
console.log(arr.filter(item => item > 2))     // [3, 4, 5]
console.log(arr.reduce((sum, item) => sum + item, 0))  // 15
console.log(arr.find(item => item > 3))       // 4
console.log(arr.some(item => item > 4))       // true
console.log(arr.every(item => item > 0))      // true

题 10:Promise 执行顺序

js
console.log('start')

Promise.resolve()
    .then(() => {
        console.log('promise1')
        return Promise.resolve()
    })
    .then(() => {
        console.log('promise2')
    })

setTimeout(() => {
    console.log('setTimeout')
}, 0)

console.log('end')

// 输出顺序:start end promise1 promise2 setTimeout

题 11:async/await

js
async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}

async function async2() {
    console.log('async2')
}

console.log('script start')
async1()
console.log('script end')

// 输出顺序:script start async1 start async2 script end async1 end

解析

  • await 后面的代码会被放入微任务队列
  • async2() 执行完后,async1 end 进入微任务队列
  • 同步代码执行完后,再执行微任务

题 12:对象属性访问

js
const obj = {
    a: 1,
    b: function () {
        console.log(this.a)
    },
    c: () => {
        console.log(this.a)
    }
}

obj.b()    // 输出:1
obj.c()    // 输出:undefined(严格模式下)

题 13:运算符优先级

js
console.log(2 + 3 * 4)           // 14
console.log((2 + 3) * 4)         // 20
console.log(2 + '3' * 4)         // 14('3' 转换为 3)
console.log(2 + '3' + 4)         // "234"
console.log(2 + 3 + '4')         // "54"

题 14:变量作用域

js
var x = 1
function test() {
    console.log(x)
    var x = 2
    console.log(x)
}
test()  // 输出:undefined 2

解析:函数内部 var x = 2 会提升到函数顶部,但赋值不会提升,所以第一次输出 undefined

题 15:数组去重

js
const arr = [1, 2, 2, 3, 3, 3, 4, 4, 5]

// 方法1:使用 Set
const unique1 = [...new Set(arr)]
console.log(unique1)  // [1, 2, 3, 4, 5]

// 方法2:使用 filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index)
console.log(unique2)  // [1, 2, 3, 4, 5]

// 方法3:使用 reduce
const unique3 = arr.reduce((acc, cur) => {
    if (!acc.includes(cur)) {
        acc.push(cur)
    }
    return acc
}, [])
console.log(unique3)  // [1, 2, 3, 4, 5]

题 16:深拷贝实现

js
// 方法1:JSON 方法(有局限性)
const obj = { a: 1, b: { c: 2 } }
const deep1 = JSON.parse(JSON.stringify(obj))

// 方法2:递归实现
function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
    
    if (obj instanceof Date) {
        return new Date(obj.getTime())
    }
    
    if (obj instanceof Array) {
        return obj.map(item => deepClone(item))
    }
    
    if (typeof obj === 'object') {
        const cloned = {}
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                cloned[key] = deepClone(obj[key])
            }
        }
        return cloned
    }
}

题 17:防抖和节流

js
// 防抖:延迟执行,在事件触发 n 秒后才执行,如果 n 秒内再次触发则重新计时
function debounce(fn, delay) {
    let timer = null
    return function (...args) {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}

// 节流:固定时间执行一次,无论触发多少次
function throttle(fn, delay) {
    let lastTime = 0
    return function (...args) {
        const now = Date.now()
        if (now - lastTime >= delay) {
            fn.apply(this, args)
            lastTime = now
        }
    }
}

题 18:函数柯里化

实现一个函数 curry,将一个接受多个参数的函数转换为一系列接受单个参数的函数。 提示:

js

function curry(fn) {
    // 返回一个函数,该函数接受任意数量的参数
    return function curried(...args) {
        // 如果参数数量足够,执行原始函数并返回结果
        if (args.length >= fn.length) {
            return fn.apply(this, args)
        } else {
            // 如果参数数量不够,返回一个新函数,等待更多参数
            return function (...nextArgs) {
                return curried.apply(this, args.concat(nextArgs))
            }
        }
    }
}

// 使用示例
function add(a, b, c) {
    return a + b + c
}

const curriedAdd = curry(add)
console.log(curriedAdd(1)(2)(3))    // 6
console.log(curriedAdd(1, 2)(3))    // 6
console.log(curriedAdd(1)(2, 3))    // 6

题 19:数组扁平化

js
const arr = [1, [2, 3], [4, [5, 6]]]

// 方法1:使用 flat
console.log(arr.flat(Infinity))  // [1, 2, 3, 4, 5, 6]

// 方法2:递归实现
function flatten(arr) {
    return arr.reduce((acc, cur) => {
        return acc.concat(Array.isArray(cur) ? flatten(cur) : cur)
    }, [])
}

// 方法3:使用 toString(仅适用于数字)
function flatten2(arr) {
    return arr.toString().split(',').map(Number)
}

// 方法4:使用栈实现
function flatten3(arr){
    const res=[]
    const stack=[...arr]
    while(stack.length){
        const item=stack.pop()
        if(Array.isArray(item)){
            // 如果是数组,继续压栈(将项遍历并添加到栈尾)
            stack.push(...item)
        }else{
            res.push(item)
        }
    }
    // 将结果反转,因为栈是先进后出的,所以需要反转
    return res.reverse()
}

题 20:实现 Promise

js
class MyPromise {
    constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        
        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled'
                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(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
            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))
        }
    }
}

代码实现题

题 21:实现 bind 方法

js
Function.prototype.myBind = function (context, ...args) {
    const fn = this
    return function (...nextArgs) {
        return fn.apply(context, [...args, ...nextArgs])
    }
}

// 使用示例
const obj = { name: 'Tom' }
function sayName(age, city) {
    console.log(this.name, age, city)
}

const boundFn = sayName.myBind(obj, 20)
boundFn('Beijing')  // Tom 20 Beijing

题 22:实现 call 方法

js
Function.prototype.myCall = function (context, ...args) {
    context = context || window
    const fn = Symbol('fn')
    context[fn] = this
    const result = context[fn](...args)
    delete context[fn]
    return result
}

题 23:实现 apply 方法

js
Function.prototype.myApply = function (context, args) {
    context = context || window
    const fn = Symbol('fn')
    context[fn] = this
    const result = context[fn](...args)
    delete context[fn]
    return result
}

题 24:实现 new 操作符

js
function myNew(constructor, ...args) {
    const obj = Object.create(constructor.prototype)
    const result = constructor.apply(obj, args)
    return result instanceof Object ? result : obj
}

题 25:实现 instanceof

js
function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left)
    const prototype = right.prototype
    
    while (proto !== null) {
        if (proto === prototype) {
            return true
        }
        proto = Object.getPrototypeOf(proto)
    }
    return false
}

算法题

题 26:两数之和

js
function twoSum(nums, target) {
    const map = new Map()
    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i]
        if (map.has(complement)) {
            return [map.get(complement), i]
        }
        map.set(nums[i], i)
    }
    return []
}

题 27:数组去重并排序

基础版本(仅处理一维数组)

js
function uniqueAndSort(arr) {
    return [...new Set(arr)].sort((a, b) => a - b)
}

// 使用示例
console.log(uniqueAndSort([3, 1, 2, 2, 3, 4]))  // [1, 2, 3, 4]

支持嵌套数组版本

js
function uniqueAndSort(arr) {
    // 先扁平化数组
    function flatten(arr) {
        return arr.reduce((acc, cur) => {
            return acc.concat(Array.isArray(cur) ? flatten(cur) : cur)
        }, [])
    }
    
    // 扁平化、去重、排序
    return [...new Set(flatten(arr))].sort((a, b) => a - b)
}

// 使用示例
console.log(uniqueAndSort([0, 1, 2, [1, 5, 6, [2, 5, 7, 9, 0]]]))
// 输出:[0, 1, 2, 5, 6, 7, 9]

console.log(uniqueAndSort([3, [1, 2], [2, [3, 4]], 5]))
// 输出:[1, 2, 3, 4, 5]

使用 flat 方法(ES2019+)

js
function uniqueAndSort(arr) {
    return [...new Set(arr.flat(Infinity))].sort((a, b) => a - b)
}

// 使用示例
console.log(uniqueAndSort([0, 1, 2, [1, 5, 6, [2, 5, 7, 9, 0]]]))
// 输出:[0, 1, 2, 5, 6, 7, 9]

题 28:实现快速排序

js
function quickSort(arr) {
    if (arr.length <= 1) return arr
    
    const pivot = arr[Math.floor(arr.length / 2)]
    const left = arr.filter(x => x < pivot)
    const middle = arr.filter(x => x === pivot)
    const right = arr.filter(x => x > pivot)
    
    return [...quickSort(left), ...middle, ...quickSort(right)]
}

这是我的个人文档