《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。
文章目录
一、本文面试题目录
33. 什么是Symbol?它有什么特性?
Symbol是ES6新增的一种原始数据类型,用于表示独一无二的值,不属于任何其他类型(如字符串、数字等)。它的核心特性如下:
唯一性:即使两个Symbol的描述相同,它们也是不同的。
const s1 = Symbol("test"); const s2 = Symbol("test"); console.log(s1 === s2); // false
不可枚举性:Symbol作为对象属性时,不会被
for...in
、Object.keys()
等方法枚举,也不会被JSON.stringify()
序列化。const obj = { [Symbol("id")]: 123, name: "test" }; console.log(Object.keys(obj)); // ['name'](Symbol属性未被枚举)
不能与其他类型直接运算:Symbol值不能与字符串、数字等进行拼接或算术运算,否则会报错。
const s = Symbol("a"); // console.log(s + "b"); // TypeError: Cannot convert a Symbol value to a string
可作为对象属性的键:解决对象属性名冲突问题(如多模块协作时避免属性覆盖)。
无法被实例化:Symbol是原始类型,不能用
new
操作符创建(new Symbol()
会报错)。
34. 如何创建一个Symbol类型的值?如何为其添加描述?
创建Symbol值的方式及添加描述的方法如下:
一、创建Symbol值
通过Symbol()
函数创建,注意不能使用new
关键字(因Symbol是原始类型,非对象)。
const s = Symbol(); // 创建一个无描述的Symbol
console.log(typeof s); // "symbol"(特殊的原始类型)
二、为Symbol添加描述
Symbol()
函数可接收一个字符串作为描述(description),用于调试时区分不同的Symbol(不影响其唯一性)。
直接传入描述字符串:
const s1 = Symbol("userName"); // 描述为"userName" const s2 = Symbol("age"); // 描述为"age"
通过
description
属性获取描述:console.log(s1.description); // "userName"(ES2019新增属性)
注意:描述相同的Symbol仍是不同的值:
const s3 = Symbol("id"); const s4 = Symbol("id"); console.log(s3 === s4); // false(描述相同但值不同)
通过
Symbol.for()
创建全局共享的Symbol(特殊场景):
若需要让描述相同的Symbol在全局范围内保持唯一,可使用Symbol.for()
,它会在全局Symbol注册表中查找描述,存在则返回,否则创建新的并注册。const s5 = Symbol.for("globalKey"); const s6 = Symbol.for("globalKey"); console.log(s5 === s6); // true(全局共享,描述相同则值相同) console.log(Symbol.keyFor(s5)); // "globalKey"(获取全局Symbol的描述)
35. Symbol作为对象属性有什么优势?如何获取对象的Symbol属性?
一、Symbol作为对象属性的优势
避免属性名冲突:
Symbol的值唯一,即使属性名的描述相同,也不会覆盖已有属性(解决多模块协作或动态属性场景的冲突问题)。const moduleA = { [Symbol("id")]: 1 }; const moduleB = { [Symbol("id")]: 2 }; // 合并两个对象时,Symbol属性不会冲突 const merged = { ...moduleA, ...moduleB }; console.log(merged[Object.getOwnPropertySymbols(moduleA)[0]]); // 1(属性保留)
隐藏属性:
Symbol属性不会被for...in
、Object.keys()
、Object.values()
枚举,适合定义“私有”属性(非真正私有,但不易被意外访问)。const obj = { public: "可见属性", [Symbol("private")]: "隐藏属性" }; for (const key in obj) { console.log(key); // 仅输出"public"(Symbol属性被忽略) }
二、获取对象的Symbol属性
需使用专门的API获取:
Object.getOwnPropertySymbols(obj)
:
返回对象自身所有Symbol属性的数组。const s = Symbol("test"); const obj = { [s]: "value" }; const symbols = Object.getOwnPropertySymbols(obj); console.log(symbols); // [Symbol(test)] console.log(obj[symbols[0]]); // "value"(访问Symbol属性值)
Reflect.ownKeys(obj)
:
返回对象自身所有属性(包括字符串键和Symbol键)的数组。const obj = { a: 1, [Symbol("b")]: 2 }; console.log(Reflect.ownKeys(obj)); // ['a', Symbol(b)](包含所有键)
36. Set和Map的区别是什么?分别适用于哪些场景?
Set和Map是ES6新增的集合数据结构,核心区别及适用场景如下:
特性 | Set | Map |
---|---|---|
存储内容 | 唯一的值(value) | 键值对(key-value),键唯一 |
本质 | 无序的“值的集合” | 无序的“键值对集合” |
常用API | add() 、has() 、delete() 、size |
set() 、get() 、has() 、delete() 、size |
键的特性 | 无键,值本身具有唯一性 | 键可以是任意类型(包括对象),且键唯一 |
遍历方式 | 遍历值(values() 、keys() 等价,因无键) |
遍历键、值或键值对(keys() 、values() 、entries() ) |
适用场景:
Set:
- 存储不重复的值(如数组去重);
- 实现数学中的“集合”操作(如交集、并集、差集);
- 跟踪对象是否存在(如DOM节点的引用集合,避免重复操作)。
// 数组去重 const arr = [1, 2, 2, 3]; const uniqueArr = [...new Set(arr)]; // [1, 2, 3]
Map:
- 需要键值对映射关系,且键不是字符串(如用对象作为键);
- 存储复杂数据的关联关系(如缓存数据:键为请求URL,值为响应结果);
- 频繁添加/删除键值对的场景(性能优于Object)。
// 用对象作为键 const objKey = { id: 1 }; const map = new Map(); map.set(objKey, "关联数据"); console.log(map.get(objKey)); // "关联数据"
37. Set如何实现数组去重?
Set的核心特性是“存储唯一的值”(重复值会被自动忽略),利用这一特性可简洁实现数组去重。
实现步骤:
- 将数组转为Set(自动去重);
- 将Set转回数组(通过扩展运算符
...
或Array.from()
)。
示例代码:
// 基础用法:去重基本类型值
const arr = [1, 2, 2, 3, 3, 3, "a", "a"];
const uniqueArr1 = [...new Set(arr)]; // 扩展运算符转换
const uniqueArr2 = Array.from(new Set(arr)); // Array.from转换
console.log(uniqueArr1); // [1, 2, 3, "a"]
// 注意:无法去重引用类型(因对象的引用不同)
const obj1 = { id: 1 };
const obj2 = { id: 1 };
const arrWithObjects = [obj1, obj2, obj1];
const uniqueObjects = [...new Set(arrWithObjects)];
// 结果:[ {id:1}, {id:1}, {id:1} ](obj1和obj2引用不同,未去重)
原理:
Set判断值是否重复的逻辑与===
类似,但有一个例外:NaN
在Set中被视为相等(而NaN === NaN
为false
)。
const arrWithNaN = [NaN, NaN, 1, 1];
console.log([...new Set(arrWithNaN)]); // [NaN, 1](NaN被去重)
38. Map与对象(Object)相比有哪些优势?
Map作为键值对集合,相比传统对象(Object)有以下优势:
键的类型更灵活
Object的键只能是字符串或Symbol,而Map的键可以是任意类型(包括数字、对象、函数等)。const map = new Map(); const objKey = { id: 1 }; map.set(objKey, "value"); // 用对象作为键 map.set(123, "number key"); // 用数字作为键
键的顺序更明确
Map会保留键的插入顺序,而Object在ES6前不保证属性顺序(数字键会被优先排序)。const map = new Map(); map.set("b", 2); map.set("a", 1); console.log([...map.keys()]); // ['b', 'a'](保留插入顺序) const obj = { b: 2, a: 1 }; console.log(Object.keys(obj)); // ['b', 'a'](现代JS也保留顺序,但历史行为不一致)
更容易获取键值对数量
Map通过size
属性直接获取键值对数量,而Object需要手动计算(如Object.keys(obj).length
)。const map = new Map([["a", 1], ["b", 2]]); console.log(map.size); // 2(直接获取)
更适合频繁添加/删除操作
Map的set()
、delete()
性能优于Object的属性添加/删除,尤其在键值对数量动态变化时。避免原型链污染
Object会继承原型链上的属性(如toString
、hasOwnProperty
),可能导致键名冲突;Map无原型链,仅包含自身键值对。const obj = {}; console.log(obj["toString"]); // 函数(继承自原型) const map = new Map(); console.log(map.has("toString")); // false(无继承属性)
遍历方式更统一
Map原生支持迭代器,可直接用for...of
遍历,而Object需要先获取键数组再遍历。
39. WeakSet和WeakMap的特性是什么?与Set、Map有何区别?
WeakSet和WeakMap是ES6新增的“弱引用”集合,特性及与Set、Map的区别如下:
一、核心特性(WeakSet和WeakMap共有的)
弱引用:
对存储的对象键(WeakMap)或值(WeakSet)保持弱引用,即不影响垃圾回收(GC)。若对象的其他引用被删除,Weak集合中的对象会被自动回收,释放内存。不可枚举:
没有size
属性,也不支持keys()
、values()
等遍历方法,无法枚举其中的元素(因元素可能随时被GC回收)。只能存储对象:
WeakSet的元素和WeakMap的键必须是对象(不能是原始类型,如数字、字符串)。
二、与Set、Map的区别
特性 | Set | WeakSet | Map | WeakMap |
---|---|---|---|---|
存储内容 | 任意类型的值 | 仅对象(值) | 任意类型的键值对 | 键必须是对象 |
引用类型 | 强引用(阻止GC) | 弱引用(不阻止GC) | 强引用(键) | 弱引用(键) |
可枚举性 | 可枚举(有size) | 不可枚举(无size) | 可枚举(有size) | 不可枚举(无size) |
常用API | add/has/delete | add/has/delete | set/get/has/delete | set/get/has/delete |
适用场景 | 去重、集合操作 | 临时存储对象(如DOM节点) | 键值对映射 | 关联对象元数据(不影响GC) |
示例:
// WeakMap示例(键为对象,弱引用)
let obj = { id: 1 };
const weakMap = new WeakMap();
weakMap.set(obj, "metadata");
obj = null; // 解除obj的引用,原对象会被GC回收,weakMap中对应的键值对也会消失
// WeakSet示例(值为对象,弱引用)
let domNode = document.createElement("div");
const weakSet = new WeakSet();
weakSet.add(domNode);
domNode = null; // DOM节点引用被解除,会被GC回收,weakSet中不再包含该节点
适用场景:
- WeakSet:存储临时对象(如DOM节点),避免内存泄漏。
- WeakMap:为对象添加元数据(如缓存对象的计算结果),不影响对象的生命周期。
40. 如何遍历Set和Map中的元素?
Set和Map均为可迭代对象(实现了[Symbol.iterator]
),可通过多种方式遍历,具体方法如下:
一、遍历Set
Set存储的是“值的集合”,keys()
和values()
返回的迭代器相同(因无键),常用values()
或直接遍历。
for...of
循环(推荐):const set = new Set(["a", "b", "c"]); for (const value of set) { console.log(value); // "a" → "b" → "c" }
forEach()
方法:set.forEach((value, key, self) => { // 注意:Set的forEach回调中,value和key参数相同(因无键) console.log(value); // "a" → "b" → "c" });
通过
values()
/keys()
迭代器:const values = set.values(); console.log(values.next().value); // "a" console.log(values.next().value); // "b"
通过
entries()
迭代器(返回[value, value]
形式的数组):for (const [value, sameValue] of set.entries()) { console.log(value, sameValue); // "a" "a" → "b" "b" → "c" "c" }
二、遍历Map
Map存储的是“键值对”,可分别遍历键、值或键值对。
遍历键值对(
entries()
,默认迭代器):const map = new Map([["name", "Alice"], ["age", 30]]); // for...of直接遍历(默认使用entries()) for (const [key, value] of map) { console.log(`${key}: ${value}`); // "name: Alice" → "age: 30" }
遍历键(
keys()
):for (const key of map.keys()) { console.log(key); // "name" → "age" }
遍历值(
values()
):for (const value of map.values()) { console.log(value); // "Alice" → 30 }
forEach()
方法:map.forEach((value, key, self) => { console.log(`${key}: ${value}`); // "name: Alice" → "age: 30" });
总结:
- Set的遍历以“值”为核心,方法简单;
- Map的遍历可灵活选择键、值或键值对,适合不同场景(如仅需值时用
values()
,需键值关系时用entries()
)。