首页首页
前端
非前端
辅助
Github
前端
非前端
辅助
Github
  • 前端基础

    • HTML基础
    • CSS基础
    • JS基础
    • ES6基础
    • HTTP基础
    • 前端缓存
    • 页面性能
    • 数据结构基础
    • 我的文章
  • 前端进阶

    • Vue2基础
    • Vue2进阶
    • Vue3基础
    • Vue3进阶
    • React基础
    • React进阶
    • React18新特性
    • Vue和React对比
    • RN基础
    • RN环境搭建和打包发布
    • 打包工具
    • TS基础
    • Nuxt基础
    • 小程序基础
    • 微前端基础
    • uni-app基础
    • 业务相关
    • 低代码相关
  • 前端代码练习

    • CSS代码练习
    • JS代码练习
    • 算法代码练习
  • 前端代码技巧

    • 工具库
    • 工具函数
    • CSS动画库
    • CSS代码技巧
    • JS代码技巧
    • 项目技巧

1.let和const(⭐️)

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升会提升提升但存在TDZ提升但存在TDZ
初始化(声明就赋值)不用 var a;不用 let a;需要const a = 1;
重复声明允许不允许不允许
值可变性可变可变不可变(基本类型)
全局作用域成为window属性不成为window属性不成为window属性

变量提升是 JS 引擎在代码执行前的一个预处理行为,它会将变量和函数的声明移动到当前作用域的顶部。

一、let 关键字

1. 块级作用域

let 声明的变量只在当前代码块(由 {} 界定)内有效,包括 if、for、while 等语句的代码块。

{
  let x = 10;
  console.log(x); // 10
}
console.log(x); // ReferenceError: x is not defined
  • 特点​​:
    • 不同于 var 的函数作用域,let 是真正的块级作用域
    • 在循环中每次迭代都会创建新的绑定
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出 0, 1, 2
}

有关算法题

2. 暂时性死区(TDZ)

定义​​:在代码块内,使用 let 声明变量之前,该变量都不可访问的区域称为暂时性死区。

console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 2;

// 对比 var​​:
console.log(b); // undefined
var b = 2;
  • ​原理​​:
    • 变量在作用域内已经存在(编译阶段)
    • 但在声明语句执行前不可访问(运行阶段)
    • 这种设计是为了强制良好的编程习惯

3. 不允许重复声明

  • 规则​​:
    • 在同一作用域内,let 不允许重复声明同名变量
    • 包括与 var、const 或函数参数的重复声明
let c = 1;
let c = 2; // SyntaxError: Identifier 'c' has already been declared

function test(arg) {
  let arg = 10; // SyntaxError: Identifier 'arg' has already been declared
}

二、const 关键字详解

1. 必须初始化

  • 规则​​:
    • const 声明时必须立即赋值
    • 不能先声明后赋值
const PI = 3.14; // 正确
const MAX; // SyntaxError: Missing initializer in const declaration

2. 基本类型不可变

  • 定义​​:
    • 对于基本类型(Number、String、Boolean 等),const 声明的变量值不可改变
    • 对于引用类型(Object、Array 等),变量绑定的内存地址不可变,但内容可修改
const NAME = "Alice";
NAME = "Bob"; // TypeError: Assignment to constant variable

const ARR = [1, 2];
ARR.push(3); // 允许
ARR = [4, 5]; // TypeError

3. 冻结对象技巧

方法​​:使用 Object.freeze() 使对象完全不可变

const OBJ = Object.freeze({ prop: "value" });
OBJ.prop = "new"; // 静默失败(严格模式下报错)

提示

Object.freeze()

  • 浅冻结:只冻结对象本身,不冻结嵌套对象
  • 深度冻结:需要递归冻结所有嵌套对象
  • Object.freeze()实际应用场景
    • 1、配置对象(常量配置)
// 1. 应用配置
const APP_CONFIG = Object.freeze({
  API_BASE_URL: 'https://api.example.com',
});

// 尝试修改会失败
APP_CONFIG.API_BASE_URL = 'https://hacked.com';  // ❌ 静默失败

三、底层原理

    1. ​创建阶段​​(编译时):
    • 在作用域中创建变量
    • let/const 会进入 TDZ 状态
    1. ​初始化阶段​​(运行时):
    • 执行到声明语句时初始化
    • let/const 结束 TDZ
    1. ​赋值阶段​​:
    • 为变量赋值

命名规范

类型命名风格示例
常量全大写+下划线const MAX_SIZE = 10
普通变量驼峰式let itemCount = 0
私有变量下划线前缀let _internal = 1

2.函数的扩展

一、箭头函数

优点:
(1)、函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)、写法更加简洁

缺点:
(1)、不可以当作构造函数,箭头函数没有 this 值,同时 箭头函数也没有 prototype,也就是说,不可以使用new命令,否则会抛出一个错误。
(2)、不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(3)、不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

更多对比参考:JS基础的this指向

二、函数默认值

直接为函数的参数指定默认值

function Point(x = 0, y = 0) {}

三、rest参数

用于获取函数的多余参数,这样就不需要使用arguments对象了。
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10
  • 与 arguments 的区别​​:
    • 剩余参数是真正的数组
    • 只包含未对应形参的实参
    • 必须放在参数列表最后

3.数组的扩展

一、扩展运算符(...)的应用

1. 复制数组

const arr1 = [1, 2];
const arr2 = [...arr1]; // 浅拷贝

2. 合并数组

const merged = [...[1, 2], ...[3, 4]]; // [1, 2, 3, 4]

3. 替代apply方法

Math.max(...[1, 2, 3]); // 等同于 Math.max(1, 2, 3)

4. 数组空位的处理

// 将数组空位转为 undefined 或者指定值:
console.log([1, , 3].map(x => x || 0)); // [1, 0, 3]

二、创建数组的新方法

1. Array.from()

将类数组对象或可迭代对象转为数组

let arrayLike = {
    '0': 'a', '1': 'b', '2': 'c', length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

2. Array.of()

创建包含任意数量元素的数组

Array.of(3);       // [3] 
Array.of(1, 2, 3); // [1, 2, 3]
Array.of();        // []

三、数组实例的新方法

1. find()

查找第一个符合条件的元素

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];
console.log(users.find(u => u.id === 2)); // { id: 2, name: 'Bob' }

2. findIndex()

查找第一个符合条件的元素索引

console.log([1, 5, 10].findIndex(x => x > 3)); // 1

3. fill()

填充数组元素

// 填充整个数组
console.log([1, 2, 3].fill(0)); // [0, 0, 0]
// 指定范围填充
console.log([1, 2, 3, 4].fill(0, 1, 3)); // [1, 0, 0, 4]

4. includes()

判断是否包含某元素

console.log([1, 2, NaN].includes(2));    // true
console.log([1, 2, NaN].includes(NaN)); // true

5. 遍历方法

entries() / keys() / values() - 返回迭代器

for (const [index, value] of ['a', 'b'].entries()) {
  console.log(index, value); // 0 'a' → 1 'b'
}

6. flat() ES2019 (ES10)

flat() 用于将嵌套数组"拉平"(数组扁平化)

const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]

4.对象的扩展

一. 属性简写

1. 属性值简写

const name = 'Alice';
const age = 25;
// ES5
const person = { name: name, age: age };
// ES6
const person = { name, age }; // 自动使用变量名作为属性名

2. 方法简写

// ES5
const obj = {
  sayHello: function() { console.log('Hello')}
};
// ES6
const obj = {
  sayHello() {console.log('Hello')}
};

二、计算属性名

const propKey = 'name';
const dynamicKey = 'First' + 'Name';

const obj = {
  [propKey]: 'Alice',       // 使用变量作为属性名
  ['age']: 25,             // 使用表达式作为属性名
  [dynamicKey]: 'Bob',     // 计算结果作为属性名
  ['say' + 'Hello']() {    // 计算方法名
    console.log('Hello');
  }
};

三、对象扩展运算符(...)

1. 对象浅拷贝

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 }; // { a: 1, b: 2 }

2. 对象合并

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }

3. 默认值与覆盖

const defaults = { theme: 'light', fontSize: 16 };
const userSettings = { fontSize: 18 };
const finalSettings = { ...defaults, ...userSettings };
// { theme: 'light', fontSize: 18 }

四、新增对象方法

1. Object.is()

相当于全等符(===),不同之处只有两个:一是+0不等于-0,二是NaN等于自身

Object.is(NaN, NaN);   // true
Object.is(+0, -0);     // false

2. Object.assign()

对象合并(浅拷贝)

const target = { a: 1 };
const source = { b: 2 };
Object.assign(target, source); // { a: 1, b: 2 }

3. Object.keys() / Object.values() / Object.entries()

const obj = { a: 1, b: 2 };

Object.keys(obj);    // ['a', 'b']
Object.values(obj);  // [1, 2]
Object.entries(obj); // [['a', 1], ['b', 2]]

4. super 关键字

指向当前对象的原型对象

const proto = {
  greet() { return 'Hello'}
};

const obj = {
  greet() { return super.greet() + ' World' }
};

Object.setPrototypeOf(obj, proto);
obj.greet(); // "Hello World"

5. Object.getPrototypeOf(obj)

返回指定对象的原型(即内部 [[Prototype]] 属性的值)

const parent = { name: 'Parent' };
const child = Object.create(parent);
console.log(Object.getPrototypeOf(child) === parent); // true

与传统方式的对比

// 传统非标准方式
console.log(child.__proto__ === parent); // true
// 标准方式(推荐)
console.log(Object.getPrototypeOf(child) === parent); // true

6. Object.setPrototypeOf(obj, prototype)

设置一个指定对象的原型到另一个对象或 null

const parent = { name: 'Parent' };
const child = { name: 'Child' };

Object.setPrototypeOf(child, parent);

console.log(child.name); // "Child"
console.log(child.toString()); // 继承自 Object.prototype
console.log(Object.getPrototypeOf(child) === parent); // true

与传统方式的对比

// 传统非标准方式(不推荐)
child.__proto__ = parent;
// 标准方式(推荐)
Object.setPrototypeOf(child, parent);

7. Object.fromEntries() ES2019 (ES10)

用于将键值对列表(如 Map、数组等)转换为一个对象。它是 Object.entries() 的逆操作。

数组转换为对象

const entries = [
  ['name', 'Alice'],
  ['age', 25],
  ['isAdmin', true]
];

const obj = Object.fromEntries(entries);
console.log(obj);
// { name: "Alice", age: 25, isAdmin: true }

Map 转换为对象

const map = new Map([
  ['name', 'Bob'],
  ['age', 30]
]);

const obj = Object.fromEntries(map);
console.log(obj);
// { name: "Bob", age: 30 }

与 Object.entries() 的关系

Object.fromEntries() 是 Object.entries() 的逆操作:

const originalObj = { a: 1, b: 2 };
const entries = Object.entries(originalObj);
// [ ['a', 1], ['b', 2] ]
const newObj = Object.fromEntries(entries);
// { a: 1, b: 2 }

5.Promise介绍(⭐️)

Promise 对象是异步编程的一种解决方案,j解决传统的回调地狱函数的问题。
Promises/A+ 规范是 JavaScript Promise 的标准,规定了一个 Promise 所必须具有的特性。
Promise对象有以下两个特点。

一、对象的状态不受外界影响。

Promise对象代表一个异步操作,

有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

二、一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise对象的状态改变,只有两种可能:
从pending变为fulfilled和从pending变为rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

Promise 实例方法

1. then()

promise.then(
  value => { /* 成功处理 */ },
  error => { /* 失败处理 */ }
);
  • ​​特点​​:
    • 可以链式调用
    • 返回新的 Promise 对象
    • 第二个参数可选(通常用 catch 代替)

2. catch()

promise.catch(
  error => { /* 错误处理 */ }
);

3. finally()

promise.finally(
  () => { /* 无论成功失败都会执行 */ }
);
  • ​​典型用途​​:
    • 清理资源
    • 隐藏加载状态

Promise 静态方法

1. Promise.resolve()

// 创建一个立即 resolve 的 Promise
Promise.resolve('success').then(value => {
  console.log(value); // 'success'
});

等价于​​:

new Promise(resolve => resolve('success'))

2. Promise.reject()

// 创建一个立即 reject 的 Promise
Promise.reject(new Error('fail')).catch(error => {
  console.error(error); // Error: fail
});

3. Promise.all()

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [value1, value2, value3]
  })
  .catch(error => {
    // 任意一个失败就会触发
  });
  • 特点​​:
    • 所有 Promise 都成功才算成功
    • 一个失败立即 reject
    • 结果数组顺序与输入一致

自己实现Promise.all的方案

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 1. 参数必须为数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    const results = [];
    let completed = 0;
    const total = promises.length;
    
    // 2. 空数组立即 resolve
    if (total === 0) {
      return resolve(results);
    }
    
    // 3. 处理每个 Promise
    promises.forEach((promise, index) => {
      // 4. 确保处理的是 Promise
      Promise.resolve(promise)
        .then(value => {
          // 5. 按顺序存储结果
          results[index] = value;
          completed++;
          
          // 6. 检查是否全部完成
          if (completed === total) {
            resolve(results);
          }
        })
        .catch(reason => {
          // 7. 任何一个失败就 reject
          reject(reason);
        });
    });
  });
}

4. Promise.race()

该 Promise 会"跟随"第一个完成的 Promise(无论成功或失败)。

  • 核心特点
    • 竞速机制​​:只关心第一个完成的 Promise
    • 短路特性​​:一旦有一个 Promise 完成(无论成功或失败),立即返回
    • 结果继承​​:返回的 Promise 状态和值与第一个完成的 Promise 一致
const promise1 = new Promise((resolve) => 
  setTimeout(() => resolve('Promise 1'), 1000)
);
const promise2 = new Promise((_, reject) => 
  setTimeout(() => reject('Promise 2'), 500)
);

Promise.race([promise1, promise2])
  .then(value => console.log('Success:', value))
  .catch(error => console.error('Error:', error)); 
// 输出:Error: Promise 2 (因为 promise2 先完成)
  • 典型应用​​:
  1. 请求超时处理
function fetchWithTimeout(url, timeout = 3000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Request timeout')), timeout)
  );

  return Promise.race([fetchPromise, timeoutPromise]);
}

fetchWithTimeout('https://api.example.com/data')
  .then(response => response.json())
  .catch(error => console.error(error));
  1. 竞速场景
const server1 = fetch('https://server1.example.com/data');
const server2 = fetch('https://server2.example.com/data');

Promise.race([server1, server2])
  .then(response => {
    console.log('更快响应的服务器:', response.url);
  });

5. Promise.allSettled() (ES2020)

Promise.allSettled([promise1, promise2])
.then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log(result.value);
    } else {
      console.error(result.reason);
    }
  });
});
  • 与 Promise.all() 区别​​:
    • 不会因为某个 Promise 失败而中断
    • 总是等到所有 Promise 完成

6. Promise.any() (ES2021)

  • 核心行为
    • 接收一个 Promise 可迭代对象(通常是数组)
    • 返回一个新的 Promise
    • ​当输入的任何一个 Promise 成功(fulfilled)时​​,立即 resolve 该 Promise 的结果
    • ​当所有 Promise 都失败(rejected)时​​,reject 一个 AggregateError(包含所有错误)
Promise.any([promise1, promise2])
  .then(value => {
    // 第一个成功的 Promise 的结果
  })
  .catch(errors => {
    // 所有 Promise 都失败
  });
  • 实际应用场景1:多服务器请求,取最快成功响应
const servers = [
  fetch('https://server1.example.com/data'),
  fetch('https://server2.example.com/data'),
  fetch('https://server3.example.com/data')
];

Promise.any(servers)
  .then(response => response.json())
  .then(data => {
    console.log('从最快响应的服务器获取数据:', data);
  })
  .catch(() => {
    console.error('所有服务器请求都失败了');
  });

可取消(终止)的 Promise

1. 使用 AbortController (推荐),适用于 fetch 和 axios 等 API:

const controller = new AbortController();
const signal = controller.signal;

// 创建一个可取消的 Promise
const fetchPromise = new Promise((resolve, reject) => {
  fetch('https://api.example.com/data', { signal })
    .then(response => response.json())
    .then(resolve)
    .catch(reject);

  // 监听 abort 事件
  signal.addEventListener('abort', () => {
    reject(new DOMException('Aborted', 'AbortError'));
  });
});

// 取消 Promise
controller.abort();

2. 使用 Promise.race()

通过与其他 Promise 竞争来实现取消:

function makeCancellable(promise) {
  let rejectFn;
  const wrappedPromise = new Promise((resolve, reject) => {
    rejectFn = reject;
    promise.then(resolve).catch(reject);
  });
  wrappedPromise.cancel = () => {
    rejectFn(new Error('Promise was cancelled'));
  };
  return wrappedPromise;
}

// 使用示例
const myPromise = new Promise(resolve => {
  setTimeout(() => resolve('Done'), 3000);
});
const cancellable = makeCancellable(myPromise);
// 取消
setTimeout(() => cancellable.cancel(), 1000);

Promise串行实现

1. for循环+async awiat

async function serialRequests(requestList) {
  const results = [];
  let previousResult = null;
  
  for (let i = 0; i < requestList.length; i++) {
    try {
      const requestConfig = requestList[i];
      
      // 如果请求函数需要上一个结果
      let params = requestConfig.params;
      if (typeof params === 'function') {
        params = params(previousResult);
      }
      
      // 执行请求
      const result = await requestConfig.api(params);
      results.push(result);
      
      // 保存结果给下一个请求使用
      previousResult = result;
      
    } catch (error) {
      console.error(`第 ${i + 1} 个请求失败:`, error);
      throw error; // 或者 break
    }
  }
  
  return results;
}

// 使用示例
async function example() {
  const apis = [
    {
      api: getUserInfo,        // 获取用户信息
      params: { userId: 123 }  // 初始参数
    },
    {
      api: getOrderList,       // 获取订单列表
      params: (user) => ({     // 使用上一个请求的结果
        userId: user.id,
        page: 1
      })
    },
  ];
  
  try {
    const results = await serialRequests(apis);
    console.log('所有请求完成:', results);
  } catch (error) {
    console.error('请求链失败:', error);
  }
}

2、Promise.then 链式调用

function chainRequests(initialParams) {
  return getUserInfo(initialParams)
    .then(user => {
      console.log('第一步完成:', user);
      return getOrderList({ userId: user.id });
    })
    .then(orders => {
      console.log('第二步完成:', orders);
      return getOrderDetail({ orderId: orders[0]?.id });
    })
    .then(orderDetail => {
      console.log('第三步完成:', orderDetail);
      return { user: arguments[0], orderDetail };
    })
    .catch(error => {
      console.error('请求链失败:', error);
      throw error;
    });
}

// 使用
chainRequests({ userId: 123 })
  .then(finalResult => {
    console.log('最终结果:', finalResult);
  });

6.async/await

async/await 是 ES2017 (ES8) 引入的异步编程语法糖,基于 Promise 实现,可以让异步代码看起来像同步代码一样直观。

async 可以说是在generator的基础上进行改进

generator函数的执行要通过next()方法,除此之外还可以依靠co模块,而async函数像普通函数一样执行
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了
async的语法比promise和generator都简单

7.import 和 export

ES6 模块系统是 JavaScript 官方标准化的模块解决方案,提供了 import 和 export 语法来实现模块的导入和导出。

一、基本导出(export)

1. 命名导出(Named Exports)

// 方式1:声明时直接导出
export const name = 'Alice';
export function sayHello() {
  console.log('Hello');
}

// 方式2:先声明后统一导出
const age = 25;
const job = 'Developer';
export { age, job as occupation }; // 使用as重命名

2. 默认导出(Default Export)

// 每个模块只能有一个默认导出
export default class Person {
  constructor(name) {
    this.name = name;
  }
}

3. 混合导出

export const version = '1.0';
export default function() {
  console.log('Default function');
}

二、基本导入(import)

1. 导入命名导出

import { name, sayHello } from './module.js';
import { age, occupation as job } from './module.js'; // 使用as重命名

// 导入所有命名导出为命名空间对象
import * as module from './module.js';
console.log(module.name);

2. 导入默认导出

import Person from './module.js'; // 名称可自定义
import customName from './module.js';

3. 混合导入

import defaultExport, { namedExport } from './module.js';
import defaultExport, * as namespace from './module.js';

三、高级用法

1. 动态导入(Dynamic Import)

// 返回Promise
import('./module.js')
  .then(module => {
    console.log(module.default);
  })
  .catch(err => {
    console.error('加载失败', err);
  });

// 在async函数中
async function loadModule() {
  const module = await import('./module.js');
}

2. 重新导出(Re-export)

// 从其他模块导入并立即导出
export { default } from './module.js'; // 默认导出
export { name } from './module.js';   // 命名导出
export * from './module.js';         // 所有命名导出

3. 导入时执行(无绑定导入)

import './init.js'; // 仅执行模块,不导入任何内容

8.Class 以及 extends

一、Class 基本语法

1. 类定义

class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
  
  // 静态方法
  static create(name) {
    return new Person(name, 0);
  }
}

2. 使用类

const alice = new Person('Alice', 25);
alice.sayHello(); // "Hello, my name is Alice"

const baby = Person.create('Baby');

二、Class 的核心特性

1. 本质仍是函数

typeof Person; // "function"
Person === Person.prototype.constructor; // true

2. 类的方法

  • 实例方法:定义在 prototype 上
  • 静态方法:定义在类本身(添加 static 关键字)

3. 类与普通构造函数的区别

  • 必须使用 new 调用
  • 不存在提升(暂时性死区)
  • 默认严格模式
  • 方法不可枚举

三、extends 实现继承

1. 基本继承

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age); // 必须首先调用super()
    this.grade = grade;
  }
  
  study() {
    console.log(`${this.name} is studying`);
  }
}

2. super 关键字

  • super():调用父类构造函数(只能在子类构造函数中使用)
  • super.method():调用父类方法

9.Set 和 Map(⭐️)

特性SetMap
​​存储内容​唯一值键值对
​​键/值类型​任意类型值任意类型键
​​​相等判断​​SameValueZeroSameValueZero
​​顺序​插入顺序插入顺序
主要方法​add/has/deleteset/get/has/delete
应用场景​值唯一性检查键值映射存储

一、Set(集合)

1. 基本特性

  • 类似数组,但​​成员值唯一​​(无重复值)
  • 使用 SameValueZero 算法判断值是否相等(类似 ===,但认为 NaN 等于自身)
  • 插入顺序即为遍历顺序

2. 基本用法

const set = new Set();

// 添加值
set.add(1).add(2).add(2).add(NaN).add(NaN);
console.log(set.size); // 3 (1, 2, NaN)
// 判断存在
console.log(set.has(1)); // true
// 删除值
set.delete(2);
console.log(set.size); // 2
// 清空集合
set.clear();

3. 初始化与转换

// 通过数组初始化
const set = new Set([1, 2, 3, 3]);
console.log([...set]); // [1, 2, 3]
// 转为数组
const arr = Array.from(set);

4. 初始化与转换

数组去重

const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

典型业务场景

    1. 数据去重(最常用场景)
// 用户选择的标签去重
const selectedTags = ['前端', 'JavaScript', '前端', 'React'];
const uniqueTags = [...new Set(selectedTags)];
// ['前端', 'JavaScript', 'React']

// 商品颜色规格去重
const productColors = ['红', '蓝', '红', '绿', '蓝'];
const uniqueColors = Array.from(new Set(productColors));
    1. 投票/点赞系统
// 用户点赞记录
const postLikes = new Set();
function likePost(userId, postId) {
  const key = `${userId}_${postId}`;
  if (postLikes.has(key)) {
    console.log('已经点过赞了');
    return false;
  }
  postLikes.add(key);
  // 执行点赞逻辑...
  return true;
}
  • 更适合使用 Set 的情况:
    • ​大数据量去重​​:当需要处理10万+数据去重时,Set比数组过滤性能高得多
    • ​​频繁存在性检查​​:has()操作时间复杂度为O(1),远优于数组的includes()
    • 集合运算​​:并集、交集、差集等运算

二、Map(映射)

1. 基本特性

  • 类似对象,但​​键可以是任意类型​​(对象只能用字符串/Symbol作为键)
  • 按插入顺序迭代
  • 相比普通对象有更好的性能(频繁增删键值对的场景)
const map = new Map();
// 添加键值对
map.set('name', 'Alice')
   .set({}, 'object key')
   .set(NaN, 'not a number');
// 获取值
console.log(map.get(NaN)); // "not a number"
// 判断键存在
console.log(map.has('name')); // true
// 删除键值对
map.delete('name');
// 清空映射
map.clear();

2. 初始化与转换

// 通过二维数组初始化
const map = new Map([
  ['name', 'Alice'],
  ['age', 25]
]);

// 转为数组
console.log([...map]); // [['name', 'Alice'], ['age', 25]]

典型业务场景

    1. 键值对数据存储(需要复杂键时)
// 用对象作为键存储DOM元素关联数据
const domData = new Map();
const button = document.getElementById('myButton');

domData.set(button, {
  clickCount: 0,
  lastClickTime: null
});

button.addEventListener('click', () => {
  const data = domData.get(button);
  data.clickCount++;
  data.lastClickTime = new Date();
});
  • 更适合使用 Map 的情况:
    • ​频繁按键查找​​:比普通对象查找更高效,特别是键为复杂类型时
    • ​需要有序遍历​​:Map保持插入顺序,而对象属性顺序不可靠
    • 大数据量键值存储​​:当键值对数量很大时(10万+),Map性能优于对象
最后更新: 2026/1/8 20:58
Prev
JS基础
Next
HTTP基础