ES6 面试题及详细答案 80题 (33-40)-- Symbol与集合数据结构

发布于:2025-09-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

一、本文面试题目录

33. 什么是Symbol?它有什么特性?

Symbol是ES6新增的一种原始数据类型,用于表示独一无二的值,不属于任何其他类型(如字符串、数字等)。它的核心特性如下:

  1. 唯一性:即使两个Symbol的描述相同,它们也是不同的。

    const s1 = Symbol("test");
    const s2 = Symbol("test");
    console.log(s1 === s2); // false
    
  2. 不可枚举性:Symbol作为对象属性时,不会被for...inObject.keys()等方法枚举,也不会被JSON.stringify()序列化。

    const obj = {
      [Symbol("id")]: 123,
      name: "test"
    };
    console.log(Object.keys(obj)); // ['name'](Symbol属性未被枚举)
    
  3. 不能与其他类型直接运算:Symbol值不能与字符串、数字等进行拼接或算术运算,否则会报错。

    const s = Symbol("a");
    // console.log(s + "b"); // TypeError: Cannot convert a Symbol value to a string
    
  4. 可作为对象属性的键:解决对象属性名冲突问题(如多模块协作时避免属性覆盖)。

  5. 无法被实例化:Symbol是原始类型,不能用new操作符创建(new Symbol()会报错)。

34. 如何创建一个Symbol类型的值?如何为其添加描述?

创建Symbol值的方式及添加描述的方法如下:

一、创建Symbol值

通过Symbol()函数创建,注意不能使用new关键字(因Symbol是原始类型,非对象)。

const s = Symbol(); // 创建一个无描述的Symbol
console.log(typeof s); // "symbol"(特殊的原始类型)
二、为Symbol添加描述

Symbol()函数可接收一个字符串作为描述(description),用于调试时区分不同的Symbol(不影响其唯一性)。

  1. 直接传入描述字符串

    const s1 = Symbol("userName"); // 描述为"userName"
    const s2 = Symbol("age");      // 描述为"age"
    
  2. 通过description属性获取描述

    console.log(s1.description); // "userName"(ES2019新增属性)
    
  3. 注意:描述相同的Symbol仍是不同的值:

    const s3 = Symbol("id");
    const s4 = Symbol("id");
    console.log(s3 === s4); // false(描述相同但值不同)
    
  4. 通过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作为对象属性的优势
  1. 避免属性名冲突
    Symbol的值唯一,即使属性名的描述相同,也不会覆盖已有属性(解决多模块协作或动态属性场景的冲突问题)。

    const moduleA = { [Symbol("id")]: 1 };
    const moduleB = { [Symbol("id")]: 2 };
    // 合并两个对象时,Symbol属性不会冲突
    const merged = { ...moduleA, ...moduleB };
    console.log(merged[Object.getOwnPropertySymbols(moduleA)[0]]); // 1(属性保留)
    
  2. 隐藏属性
    Symbol属性不会被for...inObject.keys()Object.values()枚举,适合定义“私有”属性(非真正私有,但不易被意外访问)。

    const obj = {
      public: "可见属性",
      [Symbol("private")]: "隐藏属性"
    };
    for (const key in obj) {
      console.log(key); // 仅输出"public"(Symbol属性被忽略)
    }
    
二、获取对象的Symbol属性

需使用专门的API获取:

  1. 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属性值)
    
  2. 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的核心特性是“存储唯一的值”(重复值会被自动忽略),利用这一特性可简洁实现数组去重。

实现步骤:
  1. 将数组转为Set(自动去重);
  2. 将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 === NaNfalse)。

const arrWithNaN = [NaN, NaN, 1, 1];
console.log([...new Set(arrWithNaN)]); // [NaN, 1](NaN被去重)

38. Map与对象(Object)相比有哪些优势?

Map作为键值对集合,相比传统对象(Object)有以下优势:

  1. 键的类型更灵活
    Object的键只能是字符串或Symbol,而Map的键可以是任意类型(包括数字、对象、函数等)。

    const map = new Map();
    const objKey = { id: 1 };
    map.set(objKey, "value"); // 用对象作为键
    map.set(123, "number key"); // 用数字作为键
    
  2. 键的顺序更明确
    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也保留顺序,但历史行为不一致)
    
  3. 更容易获取键值对数量
    Map通过size属性直接获取键值对数量,而Object需要手动计算(如Object.keys(obj).length)。

    const map = new Map([["a", 1], ["b", 2]]);
    console.log(map.size); // 2(直接获取)
    
  4. 更适合频繁添加/删除操作
    Map的set()delete()性能优于Object的属性添加/删除,尤其在键值对数量动态变化时。

  5. 避免原型链污染
    Object会继承原型链上的属性(如toStringhasOwnProperty),可能导致键名冲突;Map无原型链,仅包含自身键值对。

    const obj = {};
    console.log(obj["toString"]); // 函数(继承自原型)
    
    const map = new Map();
    console.log(map.has("toString")); // false(无继承属性)
    
  6. 遍历方式更统一
    Map原生支持迭代器,可直接用for...of遍历,而Object需要先获取键数组再遍历。

39. WeakSet和WeakMap的特性是什么?与Set、Map有何区别?

WeakSet和WeakMap是ES6新增的“弱引用”集合,特性及与Set、Map的区别如下:

一、核心特性(WeakSet和WeakMap共有的)
  1. 弱引用
    对存储的对象键(WeakMap)或值(WeakSet)保持弱引用,即不影响垃圾回收(GC)。若对象的其他引用被删除,Weak集合中的对象会被自动回收,释放内存。

  2. 不可枚举
    没有size属性,也不支持keys()values()等遍历方法,无法枚举其中的元素(因元素可能随时被GC回收)。

  3. 只能存储对象
    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()或直接遍历。

  1. for...of循环(推荐):

    const set = new Set(["a", "b", "c"]);
    for (const value of set) {
      console.log(value); // "a" → "b" → "c"
    }
    
  2. forEach()方法

    set.forEach((value, key, self) => {
      // 注意:Set的forEach回调中,value和key参数相同(因无键)
      console.log(value); // "a" → "b" → "c"
    });
    
  3. 通过values()/keys()迭代器

    const values = set.values();
    console.log(values.next().value); // "a"
    console.log(values.next().value); // "b"
    
  4. 通过entries()迭代器(返回[value, value]形式的数组):

    for (const [value, sameValue] of set.entries()) {
      console.log(value, sameValue); // "a" "a" → "b" "b" → "c" "c"
    }
    
二、遍历Map

Map存储的是“键值对”,可分别遍历键、值或键值对。

  1. 遍历键值对(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"
    }
    
  2. 遍历键(keys()

    for (const key of map.keys()) {
      console.log(key); // "name" → "age"
    }
    
  3. 遍历值(values()

    for (const value of map.values()) {
      console.log(value); // "Alice" → 30
    }
    
  4. forEach()方法

    map.forEach((value, key, self) => {
      console.log(`${key}: ${value}`); // "name: Alice" → "age: 30"
    });
    
总结:
  • Set的遍历以“值”为核心,方法简单;
  • Map的遍历可灵活选择键、值或键值对,适合不同场景(如仅需值时用values(),需键值关系时用entries())。

二、80道ES6 面试题目录列表

文章序号 ES6 80道
1 ES6面试题及详细答案80道(01-05)
2 ES6面试题及详细答案80道(06-12)
3 ES6面试题及详细答案80道(13-21)
4 ES6面试题及详细答案80道(22-32)
5 ES6面试题及详细答案80道(33-40)
6 ES6面试题及详细答案80道(41-54)
7 ES6面试题及详细答案80道(55-61)
8 ES6面试题及详细答案80道(62-80)

网站公告

今日签到

点亮在社区的每一天
去签到