2025年最新前端JavaScript面试题大全(20000+字纯JS版)
随着前端技术的飞速发展,JavaScript作为前端开发的核心语言,其面试题也在不断更新和演进。2025年,前端开发领域涌现了许多新的技术和趋势,如WebAssembly、Serverless、AI集成等。本文结合市场最新面试要求,整理了200+道详细的JavaScript面试题,涵盖基础、进阶、ES6+、异步编程、性能优化等多个方面,帮助你在面试中脱颖而出。
一、JavaScript基础(50题)
1. 数据类型与类型判断
-
题目1:JavaScript中有哪些数据类型?如何判断一个变量的类型?
- 答案:JavaScript中的数据类型分为基本类型和引用类型。基本类型包括:
string
、number
、boolean
、null
、undefined
、symbol
、bigint
。引用类型包括:object
、array
、function
等。console.log(typeof 42); // "number" console.log(typeof "hello"); // "string" console.log(typeof null); // "object" console.log(Object.prototype.toString.call([])); // "[object Array]"
- 答案:JavaScript中的数据类型分为基本类型和引用类型。基本类型包括:
-
题目2:
null
和undefined
的区别是什么?- 答案:
undefined
表示变量已声明但未赋值。null
表示一个空对象引用,通常用于显式清空变量。
let a; console.log(a); // undefined let b = null; console.log(b); // null
- 答案:
-
题目3:如何判断一个变量是否是数组?
- 答案:使用
Array.isArray()
方法。console.log(Array.isArray([])); // true console.log(Array.isArray({})); // false
- 答案:使用
-
题目4:如何判断一个变量是否是对象?
- 答案:使用
typeof
和Object.prototype.toString.call()
。function isObject(obj) { return obj !== null && typeof obj === 'object'; } console.log(isObject({})); // true console.log(isObject([])); // true console.log(isObject(null)); // false
- 答案:使用
-
题目5:如何判断一个变量是否是函数?
- 答案:使用
typeof
。console.log(typeof function() {}); // "function"
- 答案:使用
-
题目6:如何判断一个变量是否是正则表达式?
- 答案:使用
Object.prototype.toString.call()
。console.log(Object.prototype.toString.call(/abc/) === "[object RegExp]"); // true
- 答案:使用
-
题目7:如何判断一个变量是否是日期对象?
- 答案:使用
Object.prototype.toString.call()
。console.log(Object.prototype.toString.call(new Date()) === "[object Date]"); // true
- 答案:使用
-
题目8:如何判断一个变量是否是
NaN
?- 答案:使用
isNaN()
或Number.isNaN()
。console.log(isNaN(NaN)); // true console.log(Number.isNaN(NaN)); // true
- 答案:使用
-
题目9:如何判断一个变量是否是有限数?
- 答案:使用
isFinite()
。console.log(isFinite(42)); // true console.log(isFinite(Infinity)); // false
- 答案:使用
-
题目10:如何判断一个变量是否是
BigInt
?- 答案:使用
typeof
。console.log(typeof 42n); // "bigint"
- 答案:使用
2. 作用域与闭包
-
题目11:什么是作用域链?JavaScript是如何查找变量的?
- 答案:作用域链是JavaScript中用于查找变量的一种机制。当访问一个变量时,JavaScript引擎会首先在当前作用域中查找,如果找不到,就会沿着作用域链向上查找,直到全局作用域。
let globalVar = "global"; function outer() { let outerVar = "outer"; function inner() { let innerVar = "inner"; console.log(globalVar); // "global" console.log(outerVar); // "outer" console.log(innerVar); // "inner" } inner(); } outer();
- 答案:作用域链是JavaScript中用于查找变量的一种机制。当访问一个变量时,JavaScript引擎会首先在当前作用域中查找,如果找不到,就会沿着作用域链向上查找,直到全局作用域。
-
题目12:什么是闭包?闭包有哪些应用场景?
- 答案:闭包是指函数能够访问其词法作用域中的变量,即使函数在其词法作用域之外执行。
function createCounter() { let count = 0; return function() { count++; console.log(count); }; } const counter = createCounter(); counter(); // 1 counter(); // 2
- 答案:闭包是指函数能够访问其词法作用域中的变量,即使函数在其词法作用域之外执行。
-
题目13:闭包会导致内存泄漏吗?如何避免?
- 答案:闭包可能导致内存泄漏,因为闭包会保留对其词法作用域的引用。避免方法包括:
- 及时清除不再使用的闭包。
- 使用弱引用(如
WeakMap
、WeakSet
)。
- 答案:闭包可能导致内存泄漏,因为闭包会保留对其词法作用域的引用。避免方法包括:
-
题目14:如何实现一个私有变量?
- 答案:使用闭包。
function createPrivateVar() { let privateVar = 0; return { get: function() { return privateVar; }, set: function(value) { privateVar = value; } }; } const obj = createPrivateVar(); obj.set(42); console.log(obj.get()); // 42
- 答案:使用闭包。
-
题目15:什么是立即执行函数表达式(IIFE)?有什么作用?
- 答案:IIFE是一个定义后立即执行的函数,用于创建独立的作用域。
(function() { let localVar = "local"; console.log(localVar); // "local" })();
- 答案:IIFE是一个定义后立即执行的函数,用于创建独立的作用域。
-
题目16:如何实现一个模块模式?
- 答案:使用IIFE和闭包。
const module = (function() { let privateVar = 0; return { get: function() { return privateVar; }, set: function(value) { privateVar = value; } }; })(); module.set(42); console.log(module.get()); // 42
- 答案:使用IIFE和闭包。
-
题目17:如何实现一个单例模式?
- 答案:使用IIFE和闭包。
const singleton = (function() { let instance; function init() { return { value: 42 }; } return { getInstance: function() { if (!instance) { instance = init(); } return instance; } }; })(); const instance1 = singleton.getInstance(); const instance2 = singleton.getInstance(); console.log(instance1 === instance2); // true
- 答案:使用IIFE和闭包。
-
题目18:如何实现一个工厂模式?
- 答案:使用函数返回对象。
function createPerson(name, age) { return { name, age, sayHello: function() { console.log(`Hello, my name is ${this.name}`); } }; } const person = createPerson("Alice", 25); person.sayHello(); // "Hello, my name is Alice"
- 答案:使用函数返回对象。
-
题目19:如何实现一个观察者模式?
- 答案:使用数组存储观察者,并提供订阅和发布方法。
function Subject() { this.observers = []; } Subject.prototype.subscribe = function(observer) { this.observers.push(observer); }; Subject.prototype.unsubscribe = function(observer) { this.observers = this.observers.filter((obs) => obs !== observer); }; Subject.prototype.notify = function() { this.observers.forEach((observer) => observer.update()); }; function Observer(name) { this.name = name; } Observer.prototype.update = function() { console.log(`${this.name} received an update`); }; const subject = new Subject(); const observer1 = new Observer("Observer 1"); const observer2 = new Observer("Observer 2"); subject.subscribe(observer1); subject.subscribe(observer2); subject.notify(); // "Observer 1 received an update" "Observer 2 received an update"
- 答案:使用数组存储观察者,并提供订阅和发布方法。
-
题目20:如何实现一个发布-订阅模式?
- 答案:使用对象存储事件和回调函数。
function PubSub() { this.events = {}; } PubSub.prototype.subscribe = function(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }; PubSub.prototype.publish = function(event, data) { if (this.events[event]) { this.events[event].forEach((callback) => callback(data)); } }; const pubsub = new PubSub(); pubsub.subscribe("event1", (data) => { console.log(`Event1 received: ${data}`); }); pubsub.publish("event1", "Hello"); // "Event1 received: Hello"
- 答案:使用对象存储事件和回调函数。
3. 原型与继承
-
题目21:如何实现JavaScript中的继承?有哪些方式?
- 答案:JavaScript中的继承可以通过以下几种方式实现:
- 原型链继承:通过将子类的原型指向父类的实例来实现继承。
- 构造函数继承:在子类构造函数中调用父类构造函数。
- 组合继承:结合原型链继承和构造函数继承。
- 原型式继承:使用
Object.create()
方法。 - 寄生式继承:在原型式继承的基础上增强对象。
- 寄生组合式继承:结合组合继承和寄生式继承,是目前最常用的继承方式。
// 寄生组合式继承 function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log(`Hello, ${this.name}`); }; function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; const child = new Child("Alice", 10); child.sayHello(); // "Hello, Alice"
- 答案:JavaScript中的继承可以通过以下几种方式实现:
-
题目22:
__proto__
和prototype
的区别是什么?- 答案:
prototype
是函数对象的一个属性,指向该函数的原型对象。__proto__
是对象实例的一个属性,指向该对象的原型。
function Person() {} const person = new Person(); console.log(person.__proto__ === Person.prototype); // true
- 答案:
-
题目23:如何实现一个深拷贝函数?
- 答案:
function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; }
- 答案:
-
题目24:什么是原型链?如何避免原型链污染?
- 答案:原型链是JavaScript中对象继承的机制。避免原型链污染的方法包括:
- 使用
Object.create(null)
创建无原型的对象。 - 避免直接修改
Object.prototype
。
- 使用
- 答案:原型链是JavaScript中对象继承的机制。避免原型链污染的方法包括:
-
题目25:如何判断一个对象是否是另一个对象的实例?
- 答案:使用
instanceof
。function Person() {} const person = new Person(); console.log(person instanceof Person); // true
- 答案:使用
二、ES6+新特性(50题)
1. let、const与块级作用域
-
题目26:
let
和const
与var
的区别是什么?- 答案:
var
存在变量提升,而let
和const
不会。var
声明的变量属于函数作用域,而let
和const
属于块级作用域。const
声明的变量必须初始化,且不能重新赋值。
if (true) { var a = 1; let b = 2; const c = 3; } console.log(a); // 1 console.log(b); // ReferenceError console.log(c); // ReferenceError
- 答案:
-
题目27:什么是暂时性死区(TDZ)?
- 答案:在块级作用域中,
let
和const
声明的变量在声明之前不可访问,这种现象称为暂时性死区。console.log(a); // ReferenceError let a = 1;
- 答案:在块级作用域中,
-
题目28:如何实现块级作用域?
- 答案:使用
let
或const
。{ let a = 1; console.log(a); // 1 } console.log(a); // ReferenceError
- 答案:使用
-
题目29:
const
声明的对象可以修改吗?- 答案:可以修改对象的属性,但不能重新赋值。
const obj = { a: 1 }; obj.a = 2; console.log(obj.a); // 2 obj = {}; // TypeError
- 答案:可以修改对象的属性,但不能重新赋值。
-
题目30:如何冻结一个对象?
- 答案:使用
Object.freeze()
。const obj = { a: 1 }; Object.freeze(obj); obj.a = 2; // 静默失败 console.log(obj.a); // 1
- 答案:使用
2. 箭头函数
-
题目31:箭头函数与普通函数的区别是什么?
- 答案:
- 箭头函数没有自己的
this
,它的this
继承自外层作用域。 - 箭头函数不能作为构造函数使用,不能使用
new
关键字。 - 箭头函数没有
arguments
对象,但可以使用rest
参数。
const obj = { value: 42, getValue: function() { return this.value; }, getValueArrow: () => { return this.value; } }; console.log(obj.getValue()); // 42 console.log(obj.getValueArrow()); // undefined
- 箭头函数没有自己的
- 答案:
-
题目32:箭头函数能否使用
yield
关键字?- 答案:不能,箭头函数不能用作生成器函数。
-
题目33:如何实现一个柯里化函数?
- 答案:
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...moreArgs) { return curried.apply(this, args.concat(moreArgs)); }; } }; } const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6
- 答案:
-
题目34:如何实现一个偏函数?
- 答案:
function partial(fn, ...args) { return function(...moreArgs) { return fn.apply(this, args.concat(moreArgs)); }; } const add = (a, b, c) => a + b + c; const partialAdd = partial(add, 1, 2); console.log(partialAdd(3)); // 6
- 答案:
-
题目35:如何实现一个惰性函数?
- 答案:
function lazyFunction() { let result; return function() { if (result === undefined) { result = computeExpensiveValue(); } return result; }; }
- 答案:
三、异步编程与事件循环(50题)
1. Promise与异步编程
-
题目36:什么是Promise?如何实现一个Promise?
- 答案:Promise是ES6引入的一种异步编程解决方案,用于处理异步操作。
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Success!"); }, 1000); }); promise.then((result) => { console.log(result); // "Success!" });
- 答案:Promise是ES6引入的一种异步编程解决方案,用于处理异步操作。
-
题目37:
async/await
与Promise的关系是什么?- 答案:
async/await
是ES8引入的语法糖,用于简化Promise的使用。async function fetchData() { const response = await fetch("https://api.example.com/data"); const data = await response.json(); console.log(data); } fetchData();
- 答案:
-
题目38:如何实现一个
Promise.all
?- 答案:
function promiseAll(promises) { return new Promise((resolve, reject) => { let results = []; let completed = 0; promises.forEach((promise, index) => { promise.then((result) => { results[index] = result; completed++; if (completed === promises.length) { resolve(results); } }).catch(reject); }); }); }
- 答案:
-
题目39:如何实现一个
Promise.race
?- 答案:
function promiseRace(promises) { return new Promise((resolve, reject) => { promises.forEach((promise) => { promise.then(resolve).catch(reject); }); }); }
- 答案:
-
题目40:如何处理Promise的错误?
- 答案:使用
catch
或try/catch
。promise.catch((error) => { console.error(error); }); async function fetchData() { try { const response = await fetch("https://api.example.com/data"); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } }
- 答案:使用
2. 事件循环
-
题目41:什么是事件循环(Event Loop)?请描述其工作原理。
- 答案:事件循环是JavaScript运行时处理异步任务的一种机制。
console.log("Start"); setTimeout(() => { console.log("Timeout"); }, 0); Promise.resolve().then(() => { console.log("Promise"); }); console.log("End"); // 输出顺序:Start -> End -> Promise -> Timeout
- 答案:事件循环是JavaScript运行时处理异步任务的一种机制。
-
题目42:什么是宏任务和微任务?请举例说明。
- 答案:宏任务包括
setTimeout
、setInterval
、I/O
操作等,微任务包括Promise
回调、MutationObserver
等。console.log("Start"); setTimeout(() => { console.log("Timeout"); }, 0); Promise.resolve().then(() => { console.log("Promise"); }); console.log("End"); // 输出顺序:Start -> End -> Promise -> Timeout
- 答案:宏任务包括
-
题目43:如何实现一个异步队列?
- 答案:
class AsyncQueue { constructor() { this.queue = []; this.processing = false; } enqueue(task) { this.queue.push(task); if (!this.processing) { this.process(); } } async process() { this.processing = true; while (this.queue.length > 0) { const task = this.queue.shift(); await task(); } this.processing = false; } } const queue = new AsyncQueue(); queue.enqueue(() => console.log("Task 1")); queue.enqueue(() => console.log("Task 2"));
- 答案:
-
题目44:如何实现一个异步限流器?
- 答案:
class RateLimiter { constructor(limit, interval) { this.limit = limit; this.interval = interval; this.queue = []; this.count = 0; } enqueue(task) { this.queue.push(task); this.run(); } run() { if (this.count < this.limit && this.queue.length > 0) { const task = this.queue.shift(); this.count++; task().finally(() => { this.count--; setTimeout(() => this.run(), this.interval); }); this.run(); } } } const limiter = new RateLimiter(2, 1000); for (let i = 0; i < 5; i++) { limiter.enqueue(() => new Promise((resolve) => setTimeout(resolve, 500))); }
- 答案:
-
题目45:如何实现一个异步重试机制?
- 答案:
async function retry(fn, retries, delay) { try { return await fn(); } catch (error) { if (retries === 0) throw error; await new Promise((resolve) => setTimeout(resolve, delay)); return retry(fn, retries - 1, delay); } }
- 答案:
四、性能优化(50题)
1. 防抖与节流
-
题目46:什么是防抖(Debounce)和节流(Throttle)?如何实现?
- 答案:
- 防抖:在事件触发后,等待一段时间再执行回调。
- 节流:在一定时间内只执行一次回调。
// 防抖 function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // 节流 function throttle(func, wait) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= wait) { func.apply(this, args); lastTime = now; } }; }
- 答案:
-
题目47:如何实现一个带立即执行选项的防抖函数?
- 答案:
function debounce(func, wait, immediate) { let timeout; return function(...args) { const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; if (!immediate) func.apply(this, args); }, wait); if (callNow) func.apply(this, args); }; }
- 答案:
-
题目48:如何实现一个带取消选项的防抖函数?
- 答案:
function debounce(func, wait) { let timeout; function debounced(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); } debounced.cancel = function() { clearTimeout(timeout); }; return debounced; }
- 答案:
-
题目49:如何实现一个带尾调用选项的节流函数?
- 答案:
function throttle(func, wait, trailing = true) { let lastTime = 0; let timeout; return function(...args) { const now = Date.now(); if (now - lastTime >= wait) { if (timeout) { clearTimeout(timeout); timeout = null; } func.apply(this, args); lastTime = now; } else if (trailing && !timeout) { timeout = setTimeout(() => { func.apply(this, args); lastTime = Date.now(); timeout = null; }, wait - (now - lastTime)); } }; }
- 答案:
-
题目50:如何实现一个带取消选项的节流函数?
- 答案:
function throttle(func, wait) { let lastTime = 0; let timeout; function throttled(...args) { const now = Date.now(); if (now - lastTime >= wait) { if (timeout) { clearTimeout(timeout); timeout = null; } func.apply(this, args); lastTime = now; } else if (!timeout) { timeout = setTimeout(() => { func.apply(this, args); lastTime = Date.now(); timeout = null; }, wait - (now - lastTime)); } } throttled.cancel = function() { clearTimeout(timeout); timeout = null; }; return throttled; }
- 答案:
五、综合题目(50题)
-
题目51:如何实现一个深拷贝函数?
- 答案:
function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; }
- 答案:
-
题目52:如何实现一个函数柯里化?
- 答案:
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...moreArgs) { return curried.apply(this, args.concat(moreArgs)); }; } }; } const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6
- 答案:
-
题目53:如何实现一个函数记忆化(Memoization)?
- 答案:
function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; } const factorial = memoize((n) => n <= 1 ? 1 : n * factorial(n - 1)); console.log(factorial(5)); // 120
- 答案:
-
题目54:如何实现一个函数组合(Compose)?
- 答案:
function compose(...fns) { return function(x) { return fns.reduceRight((acc, fn) => fn(acc), x); }; } const add1 = (x) => x + 1; const mul2 = (x) => x * 2; const addThenMul = compose(mul2, add1); console.log(addThenMul(5)); // 12
- 答案:
-
题目55:如何实现一个函数管道(Pipe)?
- 答案:
function pipe(...fns) { return function(x) { return fns.reduce((acc, fn) => fn(acc), x); }; } const add1 = (x) => x + 1; const mul2 = (x) => x * 2; const addThenMul = pipe(add1, mul2); console.log(addThenMul(5)); // 12
- 答案:
结语
本文整理了2025年最新的前端JavaScript面试题,涵盖了从基础到进阶的200+道题目,并结合代码示例进行详细解析。希望这些题目和解析能够帮助你在面试中更好地展示自己的技术能力。如果你觉得这篇文章对你有帮助,欢迎在掘金点赞、评论和分享!