高频笔试题
本文档整理了 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,输出Tomobj.sayNameArrow():箭头函数,this继承外层作用域(全局),this.name为undefinedfn():函数作为普通函数调用,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:变量提升,值为undefinedlet 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解析:
- 同步代码:
console.log('1')→ 输出 1 setTimeout放入宏任务队列Promise.then放入微任务队列- 同步代码:
console.log('4')→ 输出 4 - 执行微任务:
console.log('3')→ 输出 3 - 执行宏任务:
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)]
}