《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 76. 什么是JavaScript中的“生成器函数”(Generator Function)?如何使用?
- 77. JavaScript中`Object.keys()`、`Object.values()`和`Object.entries()`的区别是什么?
- 78. 如何在JavaScript中实现一个简单的事件总线(Event Bus)?
- 79. JavaScript中`Number.isNaN()`和全局`isNaN()`的区别是什么?
- 80. 如何在JavaScript中实现一个简单的缓存(Cache)?
- 81. JavaScript中`String.prototype.replace()`的第二个参数可以是哪些类型?
- 82. 什么是JavaScript中的“装饰器”(Decorator)?如何使用?
- 83. JavaScript中`Object.getOwnPropertyDescriptor()`的作用是什么?
- 84. 如何在JavaScript中实现数组的随机打乱(Fisher-Yates洗牌算法)?
- 85. JavaScript中`String.prototype.match()`和`RegExp.prototype.exec()`的区别是什么?
- 86. 如何在JavaScript中实现深度克隆(Deep Clone)?
- 87. JavaScript中`async/await`和`Promise.then()`的区别是什么?
- 88. 什么是JavaScript中的“事件冒泡”(Event Bubbling)和“事件捕获”(Event Capturing)?
- 89. JavaScript中`Array.prototype.reduce()`的常见用法有哪些?
- 90. 如何在JavaScript中实现一个简单的观察者模式(Observer Pattern)?
- 二、150道面试题目录列表
一、本文面试题目录
76. 什么是JavaScript中的“生成器函数”(Generator Function)?如何使用?
生成器函数:
ES6引入的特殊函数,使用function*
定义,通过yield
关键字暂停和恢复执行,返回一个迭代器对象。
基本语法:
function* generator() {
yield 1;
yield 2;
return 3;
}
const iterator = generator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: true }
核心特性:
-
暂停执行:每次调用
next()
执行到下一个yield
,并返回其值。 -
传值给
yield
:通过iterator.next(value)
向生成器内部传值。function* generator() { const result = yield "请输入值"; console.log("接收到:", result); } const it = generator(); console.log(it.next().value); // "请输入值" it.next(100); // "接收到: 100"
-
生成无限序列:
function* infiniteSequence() { let i = 0; while (true) { yield i++; } } const seq = infiniteSequence(); console.log(seq.next().value); // 0 console.log(seq.next().value); // 1
77. JavaScript中Object.keys()
、Object.values()
和Object.entries()
的区别是什么?
核心区别:
方法 | 返回值类型 | 返回内容 |
---|---|---|
Object.keys(obj) | 字符串数组 | 对象自身的可枚举属性名(不含Symbol) |
Object.values(obj) | 值数组 | 对象自身的可枚举属性值(不含Symbol) |
Object.entries(obj) | [键, 值] 元组数组 | 对象自身的可枚举属性键值对(不含Symbol) |
代码示例:
const obj = { a: 1, b: 2, [Symbol('c')]: 3 };
console.log(Object.keys(obj)); // ["a", "b"]
console.log(Object.values(obj)); // [1, 2]
console.log(Object.entries(obj)); // [["a", 1], ["b", 2]]
应用场景:
-
遍历对象:
for (const [key, value] of Object.entries(obj)) { console.log(`${key}: ${value}`); }
-
转换为Map:
const map = new Map(Object.entries(obj));
78. 如何在JavaScript中实现一个简单的事件总线(Event Bus)?
事件总线:
实现组件间通信的机制,通过发布-订阅模式解耦事件发送者和接收者。
代码示例:
class EventBus {
constructor() {
this.events = {}; // 存储事件及其回调函数
}
// 注册事件监听
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 触发事件
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
// 移除事件监听
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb!== callback
);
}
}
}
// 使用示例
const eventBus = new EventBus();
// 订阅事件
const callback = (data) => console.log('接收到:', data);
eventBus.on('message', callback);
// 发布事件
eventBus.emit('message', 'Hello, Event Bus!'); // 输出:接收到: Hello, Event Bus!
// 取消订阅
eventBus.off('message', callback);
eventBus.emit('message', '再次发送'); // 无输出
79. JavaScript中Number.isNaN()
和全局isNaN()
的区别是什么?
核心区别:
方法 | 判断逻辑 | 示例 |
---|---|---|
Number.isNaN(value) | 仅当值为NaN 时返回true | Number.isNaN(NaN) → true |
isNaN(value) | 先将值转为数字,再判断是否为NaN | isNaN("abc") → true ("abc"转数字为NaN) |
代码示例:
// 实际值为NaN的情况
console.log(Number.isNaN(NaN)); // true
console.log(isNaN(NaN)); // true
// 非NaN但转换后为NaN的情况
console.log(Number.isNaN("abc")); // false("abc"本身不是NaN)
console.log(isNaN("abc")); // true("abc"转数字为NaN)
// 其他情况
console.log(Number.isNaN(undefined)); // false
console.log(isNaN(undefined)); // true(undefined转数字为NaN)
推荐使用:
优先使用Number.isNaN()
,避免隐式类型转换带来的误判。
80. 如何在JavaScript中实现一个简单的缓存(Cache)?
缓存实现:
基于Map或对象,存储已计算的结果,避免重复计算。
方法1:基于对象的简单缓存
function cachedFunction(fn) {
const cache = {};
return function(arg) {
const key = JSON.stringify(arg);
if (cache[key] === undefined) {
cache[key] = fn(arg);
}
return cache[key];
};
}
// 使用示例
const expensiveCalculation = (n) => {
console.log(`计算 ${n} 的结果...`);
return n * n;
};
const cachedCalc = cachedFunction(expensiveCalculation);
console.log(cachedCalc(5)); // 计算 5 的结果... → 25
console.log(cachedCalc(5)); // 直接返回缓存的 25
方法2:带过期时间的缓存
class Cache {
constructor(expiryTime = 60000) {
this.cache = new Map();
this.expiryTime = expiryTime;
}
get(key) {
const entry = this.cache.get(key);
if (entry && Date.now() - entry.timestamp < this.expiryTime) {
return entry.value;
}
this.cache.delete(key);
return undefined;
}
set(key, value) {
this.cache.set(key, { value, timestamp: Date.now() });
}
}
// 使用示例
const cache = new Cache(1000); // 1秒过期
cache.set('data', [1, 2, 3]);
console.log(cache.get('data')); // [1, 2, 3]
setTimeout(() => console.log(cache.get('data')), 1500); // undefined
81. JavaScript中String.prototype.replace()
的第二个参数可以是哪些类型?
replace()
的第二个参数类型:
-
字符串:直接替换匹配部分,支持特殊占位符:
$&
:匹配的子字符串。- `$``:匹配部分之前的文本。
$'
:匹配部分之后的文本。$n
:第n个捕获组(如$1
、$2
)。
"Hello World".replace("World", "JavaScript"); // "Hello JavaScript" "abc".replace(/(a)(b)/, "$2$1"); // "bac"(交换捕获组)
-
函数:对每个匹配项动态生成替换值,函数参数为:
- 匹配的子字符串。
- 捕获组(若有)。
- 匹配位置。
- 原字符串。
"a1b2c3".replace(/\d/g, (match) => parseInt(match) * 2); // "a2b4c6"
82. 什么是JavaScript中的“装饰器”(Decorator)?如何使用?
装饰器:
ES7引入的元编程特性,用于修改类、方法或属性的行为,语法为@decorator
。
基本语法:
// 类装饰器
function logClass(target) {
console.log(`Class ${target.name} was decorated`);
return target; // 必须返回原类或修改后的类
}
@logClass
class MyClass {}
// 方法装饰器
function logMethod(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${name} returned:`, result);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2); // 输出:Calling add with args: [1, 2] → Method add returned: 3
注意:
装饰器目前是ES提案(Stage 2),需通过Babel等工具编译后使用。
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
6 | Shader 编程 【图文示例 100+】 |
83. JavaScript中Object.getOwnPropertyDescriptor()
的作用是什么?
作用:
返回对象自身属性的描述符(包括值、可写性、可枚举性、可配置性等)。
属性描述符结构:
const obj = { a: 1 };
const descriptor = Object.getOwnPropertyDescriptor(obj, 'a');
console.log(descriptor);
/* 输出:
{
value: 1,
writable: true,
enumerable: true,
configurable: true
}
*/
应用场景:
-
修改属性配置:
Object.defineProperty(obj, 'a', { writable: false, enumerable: false, configurable: false });
-
获取不可枚举属性:
const arr = [1, 2, 3]; Object.defineProperty(arr, 'length', { enumerable: false }); console.log(Object.keys(arr)); // [0, 1, 2](length不可枚举) console.log(Object.getOwnPropertyDescriptor(arr, 'length')); // 可获取描述符
84. 如何在JavaScript中实现数组的随机打乱(Fisher-Yates洗牌算法)?
Fisher-Yates算法:
从数组末尾开始,依次随机选择前面的元素交换位置,时间复杂度O(n)。
代码示例:
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 随机选择0到i之间的索引
[array[i], array[j]] = [array[j], array[i]]; // 交换元素
}
return array;
}
const arr = [1, 2, 3, 4, 5];
console.log(shuffleArray(arr)); // 例如:[3, 5, 1, 4, 2]
注意:
直接修改原数组。若需返回新数组,先复制原数组:
const shuffled = shuffleArray([...arr]);
85. JavaScript中String.prototype.match()
和RegExp.prototype.exec()
的区别是什么?
核心区别:
方法 | 返回值 | 全局匹配(g 标志) | 正则状态(lastIndex ) |
---|---|---|---|
str.match(regex) | 非全局:匹配数组(含捕获组) 全局:仅匹配结果数组(不含捕获组) | 影响返回格式 | 不更新 |
regex.exec(str) | 每次调用返回一个匹配(含捕获组) | 多次调用遍历所有匹配 | 更新lastIndex |
代码示例:
const str = "a1b2c3";
const regex = /\d/g;
// match()
console.log(str.match(regex)); // ["1", "2", "3"](全局模式返回所有匹配)
console.log(str.match(/\d/)); // ["1", index: 1,
### 85. JavaScript中`String.prototype.match()`和`RegExp.prototype.exec()`的区别是什么?(续)
**代码示例**:
```javascript
const str = "a1b2c3";
const regex = /\d/g;
// match()
console.log(str.match(regex)); // ["1", "2", "3"](全局模式返回所有匹配)
console.log(str.match(/\d/)); // ["1", index: 1, input: "a1b2c3", groups: undefined](非全局返回首个匹配及捕获组)
// exec()
console.log(regex.exec(str)); // ["1", index: 1, input: "a1b2c3", groups: undefined]
console.log(regex.lastIndex); // 2(lastIndex更新为下一次匹配的起始位置)
console.log(regex.exec(str)); // ["2"]
console.log(regex.lastIndex); // 4
console.log(regex.exec(str)); // ["3"]
console.log(regex.exec(str)); // null(匹配结束)
应用场景:
- 获取所有匹配:用
match()
配合g
标志。 - 获取捕获组并遍历:用
exec()
循环调用(需注意重置lastIndex
)。
86. 如何在JavaScript中实现深度克隆(Deep Clone)?
深拷贝与浅拷贝的区别:
- 浅拷贝:只复制对象的一层属性,嵌套对象共享引用。
- 深拷贝:递归复制所有层级的属性,完全独立。
实现方法:
-
简易版(使用JSON):
function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } const obj = { a: { b: 2 } }; const clone = deepClone(obj); obj.a.b = 3; console.log(clone.a.b); // 2(不受原对象影响)
局限性:无法处理函数、正则、循环引用等。
-
完整实现(递归+Map处理循环引用):
function deepClone(obj, map = new Map()) { if (obj === null || typeof obj!== 'object') return obj; if (map.has(obj)) return map.get(obj); // 处理循环引用 const clone = Array.isArray(obj)? [] : {}; map.set(obj, clone); for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], map); } } return clone; } // 测试循环引用 const obj = { a: 1 }; obj.b = obj; // 循环引用 const clone = deepClone(obj); console.log(clone.b === clone); // true
87. JavaScript中async/await
和Promise.then()
的区别是什么?
核心区别:
特性 | async/await | Promise.then() |
---|---|---|
语法风格 | 同步风格(代码线性执行) | 链式调用(嵌套可能导致回调地狱) |
错误处理 | 使用try...catch | 使用.catch() |
调试 | 断点调试更直观 | 调试多层嵌套Promise较复杂 |
并行执行 | 默认串行,需配合Promise.all | 可自然并行(多个then链) |
返回值 | 返回Promise | 返回Promise |
代码示例:
// Promise.then()
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => console.error(error));
}
// async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return processData(data);
} catch (error) {
console.error(error);
}
}
并行执行对比:
// 串行(async/await)
async function serial() {
const result1 = await task1();
const result2 = await task2(); // 等待task1完成
return [result1, result2];
}
// 并行(async/await + Promise.all)
async function parallel() {
const [result1, result2] = await Promise.all([task1(), task2()]);
return [result1, result2];
}
88. 什么是JavaScript中的“事件冒泡”(Event Bubbling)和“事件捕获”(Event Capturing)?
事件流阶段:
- 捕获阶段:事件从window开始,逐级向下传播到目标元素。
- 目标阶段:事件到达目标元素。
- 冒泡阶段:事件从目标元素开始,逐级向上传播到window。
事件监听参数:
addEventListener(type, listener, options)
中的options
可设为:
capture: true
:使用捕获阶段(默认false
,使用冒泡阶段)。once: true
:只执行一次。passive: true
:提高滚动性能(不阻止默认行为)。
代码示例:
<div id="parent">
<button id="child">点击我</button>
</div>
<script>
// 冒泡阶段(默认)
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent - 冒泡');
});
// 捕获阶段
document.getElementById('child').addEventListener('click', () => {
console.log('Child - 捕获');
}, true);
// 点击按钮输出顺序:
// Child - 捕获(捕获阶段先触发)
// Parent - 冒泡(冒泡阶段后触发)
</script>
阻止传播:
event.stopPropagation()
:阻止事件继续冒泡或捕获。event.stopImmediatePropagation()
:阻止当前元素上的其他同类型事件处理函数。
89. JavaScript中Array.prototype.reduce()
的常见用法有哪些?
reduce()
语法:
array.reduce((accumulator, currentValue, currentIndex, array) => {
// 返回累加值
}, initialValue);
常见用法:
-
求和/乘积:
const sum = [1, 2, 3].reduce((acc, num) => acc + num, 0); // 6 const product = [1, 2, 3].reduce((acc, num) => acc * num, 1); // 6
-
数组扁平化:
const nested = [[1, 2], [3, 4]]; const flat = nested.reduce((acc, arr) => acc.concat(arr), []); // [1, 2, 3, 4]
-
统计元素出现次数:
const count = ['a', 'b', 'a', 'c', 'b'].reduce((acc, char) => { acc[char] = (acc[char] || 0) + 1; return acc; }, {}); // { a: 2, b: 2, c: 1 }
-
分组对象:
const people = [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 25 }, { name: 'Charlie', age: 30 } ]; const grouped = people.reduce((acc, person) => { const key = person.age; if (!acc[key]) acc[key] = []; acc[key].push(person); return acc; }, {}); /* 结果: { 25: [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 25 }], 30: [{ name: 'Charlie', age: 30 }] } */
90. 如何在JavaScript中实现一个简单的观察者模式(Observer Pattern)?
观察者模式:
定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。
代码示例:
class EventEmitter {
constructor() {
this.events = {}; // 存储事件及其观察者回调
}
// 注册观察者
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布事件
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
// 移除观察者
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb!== callback
);
}
}
}
// 使用示例
const emitter = new EventEmitter();
// 注册观察者
const callback = (data) => console.log('接收到:', data);
emitter.on('message', callback);
// 发布事件
emitter.emit('message', 'Hello, Observer!'); // 输出:接收到: Hello, Observer!
// 移除观察者
emitter.off('message', callback);
emitter.emit('message', '再次发送'); // 无输出
应用场景:
- 事件处理系统(如DOM事件)。
- 状态管理库(如Redux的subscribe机制)。
- 组件间通信。