《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 16. Symbol 有什么作用?
- 17. 什么是内存泄漏?
- 18. 什么是垃圾回收机制?
- 19. 事件循环(event loop)是什么?
- 20. 什么是微任务和宏任务?
- 21. 解释节流函数 throttle 的实现原理
- 22. 如何实现函数防抖(debounce)?
- 23. 什么是浅拷贝和深拷贝?
- 24. 什么是立即执行函数(IIFE)?有什么作用?
- 25. 如何将字符串转换为数字?
- 26. 如何判断一个值是否为 NaN?
- 27. 数组扁平化的方法有哪些?
- 28. 什么是函数柯里化(Currying)?举例说明
- 29. 什么是扩展运算符(...)?有哪些用途?
- 30. 如何交换两个变量的值(不使用临时变量)?
- 二、150道面试题目录列表
一、本文面试题目录
16. Symbol 有什么作用?
Symbol
用于创建独一无二的值,常用于对象属性名,可避免属性名冲突。例如:
const key1 = Symbol('key1');
const key2 = Symbol('key1');
let obj = {};
obj[key1] = 'value1';
obj[key2] = 'value2';
console.log(obj[key1]); // value1
console.log(obj[key2]); // value2
console.log(key1 === key2); // false
17. 什么是内存泄漏?
内存泄漏是指程序中不再使用的对象仍然被引用,导致内存无法被垃圾回收机制释放,从而造成内存浪费。常见场景有全局变量未及时清理、闭包使用不当、定时器未清除、DOM引用未释放等。
18. 什么是垃圾回收机制?
浏览器通常采用标记清除算法或引用计数算法来实现垃圾回收。标记清除算法会先标记所有活动对象(被引用的对象),然后清除未被标记的对象(不再被引用的对象)占用的内存。引用计数算法是通过记录对象的引用次数,当引用次数为0时,就回收该对象的内存,但这种算法存在循环引用的问题。
19. 事件循环(event loop)是什么?
事件循环是JavaScript实现非阻塞编程的机制。主线程先执行同步任务,当同步任务执行完后,会将异步任务产生的回调放入任务队列中。事件循环会不断检查任务队列,将符合条件的任务(宏任务或微任务)放入主线程执行。微任务会在当前同步任务执行完后立即执行,宏任务则是在微任务队列清空后执行。例如:
console.log('start');
setTimeout(() => {console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {console.log('promise then');
});
console.log('end');
// 输出:start -> end -> promise then -> setTimeout
20. 什么是微任务和宏任务?
- 微任务:通常在当前同步任务执行完后立即执行,常见的微任务有
Promise.then
、MutationObserver
等。 - 宏任务:会在微任务队列清空后执行,常见的宏任务有
setTimeout
、setInterval
、requestAnimationFrame
、DOM渲染
等。
21. 解释节流函数 throttle 的实现原理
节流函数用于限制函数在一定时间内只能执行一次,常用于处理频繁触发的事件,如滚动事件、 resize事件等。实现原理是通过记录上一次执行的时间,当本次触发时间与上一次时间间隔大于设定的延迟时间时,才执行函数。示例代码如下:
function throttle(fn, delay) {let last = 0;return function() {const now = Date.now();if (now - last >= delay) {fn.apply(this, arguments);last = now;}};
}
function handleScroll() {console.log('scrolling');
}
window.addEventListener('scroll', throttle(handleScroll, 500));
22. 如何实现函数防抖(debounce)?
防抖函数是指在事件触发后,延迟一段时间再执行函数,如果在这段时间内事件又被触发,则重新计时,直到最后一次触发后的延迟时间结束才执行函数。常用于搜索框输入、按钮多次点击等场景。示例代码如下:
function debounce(fn, delay) {let timer;return function() {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, arguments);}, delay);};
}
function handleInput() {console.log('input event handled');
}
let input = document.getElementById('input');
input.addEventListener('input', debounce(handleInput, 300));
23. 什么是浅拷贝和深拷贝?
-
浅拷贝:只复制对象的第一层属性,如果属性值是引用类型(如对象、数组),拷贝的是引用地址,原对象和拷贝对象会共享该引用类型的值,修改其中一个会影响另一个。
常见的浅拷贝方法有Object.assign()
、扩展运算符...
、数组的slice()
和concat()
等。// 浅拷贝示例 const obj1 = { a: 1, b: { c: 2 } }; const obj2 = Object.assign({}, obj1); // 浅拷贝 obj2.b.c = 3; console.log(obj1.b.c); // 输出3,因为obj1和obj2共享b对象的引用
-
深拷贝:完全复制对象的所有层级属性,包括嵌套的引用类型,原对象和拷贝对象互不影响。
实现深拷贝的方法有递归遍历拷贝、JSON.parse(JSON.stringify())
(有局限性)等。// 深拷贝示例(递归实现) 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; } const obj3 = { a: 1, b: { c: 2 } }; const obj4 = deepClone(obj3); obj4.b.c = 3; console.log(obj3.b.c); // 输出2,原对象不受影响
24. 什么是立即执行函数(IIFE)?有什么作用?
立即执行函数(Immediately Invoked Function Expression,IIFE)是定义后立即执行的函数,语法上通过将函数包裹在括号中,再添加执行括号实现。
作用:
- 创建独立的作用域,避免变量污染全局作用域。
- 可以封装私有变量和逻辑,只暴露需要的接口。
示例代码:
// 基本语法
(function() {const msg = "IIFE执行了";console.log(msg); // 输出"IIFE执行了"
})();// 带参数的IIFE
(function(a, b) {console.log(a + b); // 输出3
})(1, 2);// 避免全局污染示例
const globalVar = "全局变量";
(function() {const globalVar = "局部变量"; // 不影响外部全局变量console.log(globalVar); // 输出"局部变量"
})();
console.log(globalVar); // 输出"全局变量"
25. 如何将字符串转换为数字?
JavaScript 中常见的字符串转数字方法有:
Number()
:通用转换函数,可转换整数、小数、空字符串(转为0)等。parseInt()
:解析整数,可指定基数(进制),忽略非数字字符。parseFloat()
:解析浮点数,只能处理十进制,忽略非数字字符。- 一元运算符
+
:简洁的转换方式,效果类似Number()
。
示例代码:
const str1 = "123";
const str2 = "123.45";
const str3 = "123abc";
const str4 = "";console.log(Number(str1)); // 123(整数)
console.log(Number(str2)); // 123.45(浮点数)
console.log(Number(str3)); // NaN(包含非数字字符)
console.log(Number(str4)); // 0(空字符串)console.log(parseInt(str1)); // 123
console.log(parseInt(str2)); // 123(忽略小数部分)
console.log(parseInt(str3)); // 123(忽略"abc")
console.log(parseInt("11", 2)); // 3(解析二进制的"11"为十进制3)console.log(parseFloat(str2)); // 123.45
console.log(parseFloat(str3)); // 123console.log(+str1); // 123(同Number())
console.log(+str4); // 0
26. 如何判断一个值是否为 NaN?
NaN
(Not a Number)是一个特殊的数值,表示“不是数字”,但 typeof NaN
会返回 'number'
,且 NaN
不等于任何值(包括自身)。
判断方法:
- 使用全局函数
isNaN()
:但会先将参数转换为数字,非数字转换后为NaN
则返回true
(有缺陷)。 - 使用
Number.isNaN()
:ES6 新增,仅当参数是NaN
时返回true
(更准确)。 - 利用
NaN!== NaN
的特性:通过值是否不等于自身判断。
示例代码:
const a = NaN;
const b = "not a number";
const c = 123;console.log(isNaN(a)); // true
console.log(isNaN(b)); // true("not a number"转换为数字是NaN)
console.log(isNaN(c)); // falseconsole.log(Number.isNaN(a)); // true
console.log(Number.isNaN(b)); // false(b是字符串,不是NaN)
console.log(Number.isNaN(c)); // falseconsole.log(a!== a); // true(NaN的独特特性)
console.log(b!== b); // false
27. 数组扁平化的方法有哪些?
数组扁平化是将多维数组转换为一维数组,常见方法:
Array.prototype.flat(depth)
:ES6 方法,depth
为扁平化深度(默认1,Infinity
表示完全扁平化)。- 递归遍历:通过递归判断元素是否为数组,非数组则添加到结果。
toString()
+split()
:将数组转为字符串后分割(仅适用于纯数字数组)。
示例代码:
const arr = [1, [2, [3, [4]], 5]];// 方法1:flat()
console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5]// 方法2:递归
function flatten(arr) {let result = [];for (let item of arr) {if (Array.isArray(item)) {result = result.concat(flatten(item)); // 递归处理子数组} else {result.push(item);}}return result;
}
console.log(flatten(arr)); // [1, 2, 3, 4, 5]// 方法3:toString() + split()(仅数字数组)
console.log(arr.toString().split(',').map(Number)); // [1, 2, 3, 4, 5]
28. 什么是函数柯里化(Currying)?举例说明
函数柯里化是将接收多个参数的函数转换为一系列接收单个参数的函数的过程,每次调用返回一个新函数,直到接收完所有参数后执行原函数逻辑。
作用:参数复用、延迟执行、提高函数灵活性。
示例代码:
// 普通函数:计算a + b + c
function add(a, b, c) {return a + b + c;
}// 柯里化后的函数
function curryAdd(a) {return function(b) {return function(c) {return a + b + c;};};
}console.log(add(1, 2, 3)); // 6
console.log(curryAdd(1)(2)(3)); // 6// 参数复用示例:固定第一个参数为10
const add10 = curryAdd(10);
console.log(add10(2)(3)); // 15(10 + 2 + 3)
console.log(add10(5)(5)); // 20(10 + 5 + 5)
29. 什么是扩展运算符(…)?有哪些用途?
扩展运算符(...
)用于将可迭代对象(数组、字符串、Set等)展开为独立元素,常见用途:
- 复制数组或对象(浅拷贝)。
- 合并数组或对象。
- 将类数组转换为数组。
- 函数传参时展开数组作为参数。
示例代码:
// 1. 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]// 2. 合并数组
const arr3 = [4, 5];
const mergedArr = [...arr1, ...arr3]; // [1, 2, 3, 4, 5]// 3. 复制对象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // {a:1, b:2, c:3}// 4. 合并对象(后面的属性覆盖前面的)
const obj3 = { b: 3, d: 4 };
const mergedObj = { ...obj1, ...obj3 }; // {a:1, b:3, d:4}// 5. 转换类数组(如arguments)
function sum() {return [...arguments].reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3)); // 6// 6. 展开字符串
const str = "hello";
console.log([...str]); // ['h', 'e', 'l', 'l', 'o']
30. 如何交换两个变量的值(不使用临时变量)?
不使用临时变量交换两个变量的值,可通过以下方法:
- 数组解构赋值(ES6,最简洁)。
- 算术运算(仅适用于数字)。
- 异或运算(适用于整数,利用二进制特性)。
示例代码:
// 方法1:数组解构赋值(通用)
let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y); // 20 10// 方法2:算术运算(仅数字)
let a = 5, b = 8;
a = a + b; // a = 13
b = a - b; // b = 13 - 8 = 5
a = a - b; // a = 13 - 5 = 8
console.log(a, b); // 8 5// 方法3:异或运算(适用于整数)
let m = 3, n = 5;
m = m ^ n; // m = 3 ^ 5 = 6(二进制11 ^ 101 = 110)
n = m ^ n; // n = 6 ^ 5 = 3(110 ^ 101 = 011)
m = m ^ n; // m = 6 ^ 3 = 5(110 ^ 011 = 101)
console.log(m, n); // 5 3
二、150道面试题目录列表
文章序号 | Javascript面试题150道 |
---|---|
1 | Javascript面试题及答案150道(001-015) |
2 | Javascript面试题及答案150道(016-030) |
3 | Javascript面试题及答案150道(031-045) |
4 | Javascript面试题及答案150道(046-060) |
5 | Javascript面试题及答案150道(061-075) |
6 | Javascript面试题及答案150道(076-090) |
7 | Javascript面试题及答案150道(091-105) |
8 | Javascript面试题及答案150道(106-120) |
9 | Javascript面试题及答案150道(121-135) |
10 | Javascript面试题及答案150道(136-150) |