Skip to content

JavaScript ES6+ 新特性面试题

1. 请简述 ES6 引入的主要新特性

Details

ES6(ECMAScript 2015)是 JavaScript 的一个重要版本,引入了许多新特性,以下是主要的新特性:

1. 箭头函数(Arrow Functions)

箭头函数提供了更简洁的函数语法,并且没有自己的 this 绑定。

javascript
// 传统函数
function add(a, b) {
  return a + b;
}

// 箭头函数
const add = (a, b) => a + b;

// 带函数体的箭头函数
const multiply = (a, b) => {
  const result = a * b;
  return result;
};

2. 模板字符串(Template Literals)

模板字符串允许在字符串中嵌入表达式,使用反引号(`)包围。

javascript
const name = "John";
const age = 30;

// 传统字符串拼接
const message = "My name is " + name + " and I am " + age + " years old.";

// 模板字符串
const message = `My name is ${name} and I am ${age} years old.`;

3. 解构赋值(Destructuring Assignment)

解构赋值允许从数组或对象中提取值,并赋给变量。

javascript
// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

// 对象解构
const { name, age, ...other } = { name: "John", age: 30, city: "New York" };
console.log(name); // "John"
console.log(age); // 30
console.log(other); // { city: "New York" }

4. 扩展运算符(Spread Operator)

扩展运算符允许将数组或对象展开为多个元素。

javascript
// 数组扩展
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]

// 对象扩展
const obj1 = { name: "John" };
const obj2 = { ...obj1, age: 30 };
console.log(obj2); // { name: "John", age: 30 }

5. let 和 const 声明

letconst 提供了块级作用域的变量声明,替代了 var

javascript
// let 声明(可变)
let count = 1;
count = 2; // 合法

// const 声明(不可变)
const PI = 3.14159;
PI = 3.14; // 错误

// 块级作用域
if (true) {
  let blockVar = "block"; // 只在块内可见
  const blockConst = "constant"; // 只在块内可见
}
console.log(blockVar); // 错误,blockVar 未定义

6. 类(Classes)

ES6 引入了类语法,提供了更清晰的面向对象编程方式。

javascript
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    return `Hello, my name is ${this.name}`;
  }
  
  static create(name, age) {
    return new Person(name, age);
  }
}

const person = new Person("John", 30);
console.log(person.greet()); // "Hello, my name is John"

const person2 = Person.create("Jane", 25);
console.log(person2.greet()); // "Hello, my name is Jane"

7. 模块(Modules)

ES6 引入了模块系统,允许将代码分割为独立的模块。

javascript
// 导出模块
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

export default {
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b
};

// 导入模块
// app.js
import math, { add, subtract } from './math.js';

console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
console.log(math.multiply(2, 3)); // 6

8. Promise

Promise 提供了一种处理异步操作的方法,避免了回调地狱。

javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: "John" };
      resolve(data);
    }, 1000);
  });
};

fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

9. 迭代器和生成器(Iterators and Generators)

迭代器和生成器提供了一种更灵活的遍历数据的方式。

javascript
// 生成器函数
function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = generator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

10. 其他新特性

  • 默认参数function greet(name = "World") { ... }
  • 剩余参数function sum(...numbers) { ... }
  • 增强的对象字面量const name = "John"; const obj = { name, greet() { ... } }
  • Symbol:一种新的原始数据类型,表示唯一的标识符
  • Map 和 Set:新的数据结构
  • WeakMap 和 WeakSet:弱引用的数据结构
  • Proxy 和 Reflect:用于拦截和自定义对象行为

2. 请解释 ES6 中的箭头函数与传统函数的区别

Details

ES6 引入的箭头函数与传统函数相比,有以下几个主要区别:

1. 语法简洁

箭头函数提供了更简洁的语法,特别是对于单行函数体。

javascript
// 传统函数
function add(a, b) {
  return a + b;
}

// 箭头函数
const add = (a, b) => a + b;

2. this 绑定

箭头函数没有自己的 this 绑定,它会继承外层作用域的 this

javascript
// 传统函数
const obj1 = {
  name: "John",
  greet: function() {
    setTimeout(function() {
      console.log(this.name); // undefined,因为 this 指向全局对象
    }, 1000);
  }
};

// 箭头函数
const obj2 = {
  name: "John",
  greet: function() {
    setTimeout(() => {
      console.log(this.name); // "John",继承 greet 方法的 this
    }, 1000);
  }
};

3. 不能作为构造函数

箭头函数不能使用 new 关键字调用,因为它没有 prototype 属性。

javascript
const Person = (name) => {
  this.name = name;
};

// 错误,箭头函数不能作为构造函数
const person = new Person("John");

4. 没有 arguments 对象

箭头函数没有 arguments 对象,但可以使用剩余参数来实现类似功能。

javascript
// 传统函数
function sum() {
  return Array.from(arguments).reduce((acc, num) => acc + num, 0);
}

// 箭头函数
const sum = (...args) => {
  return args.reduce((acc, num) => acc + num, 0);
};

5. 没有 prototype 属性

箭头函数没有 prototype 属性,因此不能向其添加方法。

javascript
const add = (a, b) => a + b;
console.log(add.prototype); // undefined

6. 不能使用 yield 关键字

箭头函数不能作为生成器函数,不能使用 yield 关键字。

javascript
// 错误,箭头函数不能使用 yield
const generator = () => {
  yield 1;
  yield 2;
};

总结

特性传统函数箭头函数
语法较冗长简洁
this 绑定动态绑定继承外层作用域
构造函数可以不可以
arguments 对象无(使用剩余参数)
prototype 属性
yield 关键字可以不可以

应用场景

  • 箭头函数:适用于需要保持 this 上下文的场景,如回调函数、事件处理函数等。
  • 传统函数:适用于需要独立 this 绑定的场景,如构造函数、方法等。

3. 请解释 ES6 中的模块系统

Details

ES6 引入了模块系统,允许将代码分割为独立的模块,每个模块可以导出(export)和导入(import)功能。

模块的基本概念

  • 模块:一个独立的 JavaScript 文件,包含变量、函数、类等。
  • 导出(Export):将模块中的内容暴露给其他模块。
  • 导入(Import):从其他模块中引入内容。

导出方式

1. 命名导出(Named Exports)

可以导出多个变量、函数或类,每个都有自己的名称。

javascript
// math.js
// 导出单个变量
export const PI = 3.14159;

// 导出函数
export function add(a, b) {
  return a + b;
}

// 导出类
export class Calculator {
  add(a, b) {
    return a + b;
  }
}

// 批量导出
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;

export { subtract, multiply };

2. 默认导出(Default Export)

每个模块只能有一个默认导出,通常用于导出模块的主要功能。

javascript
// math.js
// 默认导出对象
const math = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b
};

export default math;

// 或者直接导出
// export default {
//   add: (a, b) => a + b,
//   // ...
// };

导入方式

1. 导入命名导出

使用花括号 {} 导入命名导出的内容。

javascript
// app.js
// 导入单个命名导出
import { add, subtract } from './math.js';

// 导入所有命名导出
import * as math from './math.js';

// 重命名导入
import { add as sum, subtract as minus } from './math.js';

console.log(add(1, 2)); // 3
console.log(math.multiply(2, 3)); // 6
console.log(sum(3, 4)); // 7

2. 导入默认导出

直接导入默认导出的内容,不需要使用花括号。

javascript
// app.js
// 导入默认导出
import math from './math.js';

// 重命名导入
import calculator from './math.js';

console.log(math.add(1, 2)); // 3
console.log(calculator.multiply(2, 3)); // 6

3. 混合导入

可以同时导入默认导出和命名导出。

javascript
// app.js
import math, { PI, add } from './math.js';

console.log(PI); // 3.14159
console.log(add(1, 2)); // 3
console.log(math.multiply(2, 3)); // 6

模块的特点

  1. 自动严格模式:模块默认运行在严格模式下。
  2. 独立作用域:每个模块都有自己的作用域,变量不会泄漏到全局。
  3. 静态导入导出:导入和导出在编译时处理,不支持动态导入(ES2020 引入了动态导入)。
  4. 单例模式:每个模块只被加载一次,多次导入会使用同一个实例。

动态导入

ES2020 引入了动态导入,允许在运行时按需加载模块。

javascript
// 动态导入
async function loadModule() {
  const math = await import('./math.js');
  console.log(math.add(1, 2)); // 3
}

loadModule();

模块系统的优势

  1. 代码组织:将代码分割为独立的模块,提高代码的可维护性。
  2. 代码复用:可以在多个项目中重用模块。
  3. 命名空间:避免变量名冲突,每个模块有自己的命名空间。
  4. tree-shaking:打包工具可以移除未使用的代码,减少最终 bundle 的大小。

与 CommonJS 的区别

特性ES6 模块CommonJS
语法import/exportrequire()/module.exports
加载方式静态加载(编译时)动态加载(运行时)
作用域模块作用域函数作用域
this 值undefinedmodule.exports
适用环境浏览器和 Node.js主要用于 Node.js

4. 请解释 ES6 中的 Promise

Details

Promise 是 ES6 引入的一种处理异步操作的方法,它代表一个异步操作的最终完成(或失败)及其结果值。

Promise 的基本概念

  • Promise:一个对象,表示异步操作的最终状态(完成或失败)。
  • 状态
    • pending:初始状态,既不是成功,也不是失败。
    • fulfilled:操作成功完成。
    • rejected:操作失败。
  • 链式调用:Promise 支持链式调用,使代码更清晰。

Promise 的创建

javascript
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

Promise 的使用

javascript
promise
  .then(result => {
    console.log(result); // '操作成功'
    return '处理结果';
  })
  .then(processedResult => {
    console.log(processedResult); // '处理结果'
  })
  .catch(error => {
    console.error(error); // '操作失败'
  })
  .finally(() => {
    console.log('无论成功或失败都会执行');
  });

Promise 的方法

1. Promise.all()

并行执行多个 Promise,当所有 Promise 都完成时返回结果数组。

javascript
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // [1, 2, 3]
  });

2. Promise.race()

并行执行多个 Promise,返回第一个完成的 Promise 的结果。

javascript
const promise1 = new Promise(resolve => setTimeout(resolve, 100, 'first'));
const promise2 = new Promise(resolve => setTimeout(resolve, 50, 'second'));

Promise.race([promise1, promise2])
  .then(result => {
    console.log(result); // 'second'
  });

3. Promise.allSettled()

并行执行多个 Promise,返回所有 Promise 的结果,无论成功或失败。

javascript
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('error');

Promise.allSettled([promise1, promise2])
  .then(results => {
    console.log(results);
    // [
    //   { status: 'fulfilled', value: 1 },
    //   { status: 'rejected', reason: 'error' }
    // ]
  });

4. Promise.any()

并行执行多个 Promise,返回第一个成功的 Promise 的结果。

javascript
const promise1 = Promise.reject('error1');
const promise2 = Promise.resolve('success');
const promise3 = Promise.reject('error2');

Promise.any([promise1, promise2, promise3])
  .then(result => {
    console.log(result); // 'success'
  });

Promise 的优势

  1. 避免回调地狱:通过链式调用,使异步代码更清晰。
  2. 错误处理:集中处理错误,避免在每个回调中处理错误。
  3. 更好的可读性:代码结构更清晰,易于理解和维护。
  4. 支持并行执行:可以同时执行多个异步操作,提高性能。

Promise 与 async/await

ES2017 引入了 async/await,使异步代码更像同步代码。

javascript
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

fetchData();

5. 请解释 ES6 中的 Map 和 Set

Details

ES6 引入了两种新的数据结构:Map 和 Set。

Map

Map 是一种键值对集合,与对象类似,但有以下区别:

  • 键的类型:Map 的键可以是任何类型(包括对象、函数、基本类型),而对象的键只能是字符串或 Symbol。
  • 键的顺序:Map 中的键是有序的,按照插入顺序排列,而对象的键在 ES6 之前是无序的。
  • 大小:Map 有 size 属性,可以直接获取键值对的数量,而对象需要手动计算。
  • 迭代:Map 是可迭代的,可以使用 for...of 循环遍历,而对象需要通过 Object.keys() 等方法获取键数组后再遍历。

Map 的基本操作

javascript
// 创建 Map
const map = new Map();

// 添加键值对
map.set('name', 'John');
map.set(1, 'one');
map.set(true, 'boolean');

// 获取值
console.log(map.get('name')); // 'John'

// 检查键是否存在
console.log(map.has('name')); // true

// 删除键值对
map.delete('name');

// 清空 Map
map.clear();

// 获取大小
console.log(map.size); // 0

// 遍历 Map
const map2 = new Map([
  ['name', 'John'],
  ['age', 30]
]);

// 遍历键
for (const key of map2.keys()) {
  console.log(key);
}

// 遍历值
for (const value of map2.values()) {
  console.log(value);
}

// 遍历键值对
for (const [key, value] of map2.entries()) {
  console.log(`${key}: ${value}`);
}

// 使用 forEach
map2.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

Set

Set 是一种值的集合,其中每个值只能出现一次。

  • 唯一性:Set 中的值是唯一的,不会重复。
  • 值的类型:Set 可以存储任何类型的值(包括对象、函数、基本类型)。
  • 值的顺序:Set 中的值是有序的,按照插入顺序排列。
  • 大小:Set 有 size 属性,可以直接获取值的数量。
  • 迭代:Set 是可迭代的,可以使用 for...of 循环遍历。

Set 的基本操作

javascript
// 创建 Set
const set = new Set();

// 添加值
set.add('apple');
set.add('banana');
set.add('apple'); // 重复值,不会添加

// 检查值是否存在
console.log(set.has('apple')); // true

// 删除值
set.delete('apple');

// 清空 Set
set.clear();

// 获取大小
console.log(set.size); // 0

// 遍历 Set
const set2 = new Set(['apple', 'banana', 'cherry']);

// 使用 for...of
for (const value of set2) {
  console.log(value);
}

// 使用 forEach
set2.forEach(value => {
  console.log(value);
});

// 转换为数组
const array = [...set2];
console.log(array); // ['apple', 'banana', 'cherry']

// 数组去重
const duplicateArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(duplicateArray)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]

WeakMap 和 WeakSet

ES6 还引入了 WeakMap 和 WeakSet,它们与 Map 和 Set 的区别在于:

  • 弱引用:WeakMap 和 WeakSet 对键或值的引用是弱引用,不会阻止垃圾回收。
  • 键的类型:WeakMap 的键只能是对象,WeakSet 只能存储对象。
  • 不可迭代:WeakMap 和 WeakSet 不可迭代,没有 size 属性,也没有 keys()values()entries() 方法。

WeakMap 的基本操作

javascript
const weakMap = new WeakMap();
const obj = {};

// 添加键值对
weakMap.set(obj, 'value');

// 获取值
console.log(weakMap.get(obj)); // 'value'

// 检查键是否存在
console.log(weakMap.has(obj)); // true

// 删除键值对
weakMap.delete(obj);

WeakSet 的基本操作

javascript
const weakSet = new WeakSet();
const obj1 = {};
const obj2 = {};

// 添加值
weakSet.add(obj1);
weakSet.add(obj2);

// 检查值是否存在
console.log(weakSet.has(obj1)); // true

// 删除值
weakSet.delete(obj1);

6. 请解释 ES6+ 中的 async/await

Details

async/await 是 ES2017 引入的特性,它建立在 Promise 之上,提供了一种更简洁、更像同步代码的方式来处理异步操作。

async 函数

  • 定义:使用 async 关键字声明的函数。
  • 返回值:async 函数总是返回一个 Promise。如果函数返回一个值,Promise 会被解析为该值;如果函数抛出异常,Promise 会被拒绝。

await 表达式

  • 用法:只能在 async 函数内部使用。
  • 作用:暂停 async 函数的执行,等待 Promise 解析或拒绝,然后继续执行 async 函数并返回解析的值。
  • 错误处理:可以使用 try-catch 捕获 await 表达式的错误。

基本用法

javascript
// 定义 async 函数
async function fetchData() {
  try {
    // 等待 Promise 解析
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    // 捕获错误
    console.error('Error:', error);
    throw error;
  }
}

// 调用 async 函数
fetchData()
  .then(data => console.log('Processed data:', data))
  .catch(error => console.error('Error in fetchData:', error));

并行执行

javascript
async function fetchMultipleData() {
  // 并行执行多个异步操作
  const [data1, data2] = await Promise.all([
    fetch('https://api.example.com/data1').then(res => res.json()),
    fetch('https://api.example.com/data2').then(res => res.json())
  ]);
  
  console.log('Data 1:', data1);
  console.log('Data 2:', data2);
  
  return { data1, data2 };
}

串行执行

javascript
async function fetchSerialData() {
  // 串行执行异步操作
  const data1 = await fetch('https://api.example.com/data1').then(res => res.json());
  console.log('Data 1:', data1);
  
  const data2 = await fetch('https://api.example.com/data2').then(res => res.json());
  console.log('Data 2:', data2);
  
  return { data1, data2 };
}

async/await 的优势

  1. 代码可读性:代码结构更清晰,更像同步代码,易于理解和维护。
  2. 错误处理:可以使用 try-catch 统一处理错误,避免了 Promise 链式调用中的错误处理嵌套。
  3. 调试方便:可以在 async 函数中设置断点,逐行调试。
  4. 简化 Promise 链式调用:避免了 Promise 链式调用的嵌套,使代码更扁平。

注意事项

  1. await 只能在 async 函数中使用:在非 async 函数中使用 await 会导致语法错误。
  2. async 函数返回 Promise:调用 async 函数时,需要使用 .then() 或 await 来处理返回值。
  3. 错误处理:必须使用 try-catch 捕获 await 表达式的错误,否则会导致 Promise 被拒绝。
  4. 并行执行:当多个异步操作之间没有依赖关系时,应使用 Promise.all() 并行执行,以提高性能。

7. 请解释 ES6+ 中的可选链操作符(?.)和空值合并操作符(??)

Details

ES2020 引入了两个新的操作符:可选链操作符(?.)和空值合并操作符(??)。

可选链操作符(?.)

可选链操作符允许我们安全地访问对象的深层属性,而不需要检查每个中间属性是否存在。

基本用法

javascript
// 传统方式
const user = {
  profile: {
    name: 'John'
  }
};

// 传统方式:需要检查每个中间属性
const userName = user && user.profile && user.profile.name;
console.log(userName); // 'John'

// 使用可选链操作符
const userName2 = user?.profile?.name;
console.log(userName2); // 'John'

// 如果中间属性不存在,返回 undefined
const userAge = user?.profile?.age;
console.log(userAge); // undefined

// 可选链与函数调用
const user = {
  greet: function() {
    return 'Hello!';
  }
};

// 安全地调用方法
const greeting = user?.greet?.();
console.log(greeting); // 'Hello!'

// 如果方法不存在,返回 undefined
const farewell = user?.farewell?.();
console.log(farewell); // undefined

// 可选链与数组访问
const user = {
  hobbies: ['reading', 'coding']
};

// 安全地访问数组元素
const firstHobby = user?.hobbies?.[0];
console.log(firstHobby); // 'reading'

// 如果数组不存在或索引越界,返回 undefined
const thirdHobby = user?.hobbies?.[2];
console.log(thirdHobby); // undefined

空值合并操作符(??)

空值合并操作符用于提供默认值,当左侧表达式为 nullundefined 时,返回右侧表达式的值。

基本用法

javascript
// 传统方式:使用 || 操作符
const name = null || 'Default Name';
console.log(name); // 'Default Name'

// 问题:|| 操作符会将 falsy 值(如 0, '', false)也视为需要使用默认值的情况
const count = 0 || 10;
console.log(count); // 10,这可能不是我们想要的

// 使用空值合并操作符
const name2 = null ?? 'Default Name';
console.log(name2); // 'Default Name'

const count2 = 0 ?? 10;
console.log(count2); // 0,正确保留了 0 值

const emptyString = '' ?? 'Default String';
console.log(emptyString); // '',正确保留了空字符串

const falseValue = false ?? true;
console.log(falseValue); // false,正确保留了 false 值

组合使用

可选链操作符和空值合并操作符可以组合使用,以提供更简洁的代码。

javascript
const user = {
  profile: {
    name: 'John'
  }
};

// 安全地访问深层属性,并提供默认值
const userName = user?.profile?.name ?? 'Anonymous';
console.log(userName); // 'John'

const userAge = user?.profile?.age ?? 18;
console.log(userAge); // 18

const userEmail = user?.profile?.email ?? 'no-email@example.com';
console.log(userEmail); // 'no-email@example.com'

注意事项

  1. 可选链操作符的返回值:当访问的属性不存在时,可选链操作符返回 undefined,而不是抛出错误。
  2. 空值合并操作符的操作数:空值合并操作符只对 nullundefined 进行判断,对其他 falsy 值(如 0, '', false)会保留原值。
  3. 优先级:空值合并操作符的优先级低于大多数其他操作符,因此在复杂表达式中可能需要使用括号。
  4. 兼容性:可选链操作符和空值合并操作符在现代浏览器和 Node.js 14+ 中支持,旧环境需要使用 Babel 等工具进行转译。

8. 请解释 ES6+ 中的 BigInt

Details

BigInt 是 ES2020 引入的一种新的原始数据类型,用于表示任意精度的整数。

基本概念

  • BigInt:一种原始数据类型,表示任意精度的整数,可以表示超出 Number 类型范围的整数。
  • Number 类型的限制:Number 类型使用 64 位浮点数表示,最大安全整数为 Number.MAX_SAFE_INTEGER(9007199254740991)。
  • BigInt 的表示:在数字后面添加 n 后缀,或使用 BigInt() 构造函数。

基本用法

javascript
// 使用 n 后缀创建 BigInt
const bigInt1 = 123n;
console.log(typeof bigInt1); // 'bigint'

// 使用 BigInt() 构造函数创建 BigInt
const bigInt2 = BigInt(123);
console.log(bigInt2); // 123n

// 从字符串创建 BigInt
const bigInt3 = BigInt('12345678901234567890');
console.log(bigInt3); // 12345678901234567890n

// 超出 Number 范围的整数
const bigInt4 = 9007199254740991n; // Number.MAX_SAFE_INTEGER
const bigInt5 = 9007199254740992n; // 超出 Number 范围
console.log(bigInt5); // 9007199254740992n

运算操作

BigInt 支持大部分与 Number 相同的运算操作,但有一些限制:

javascript
// 加法
const sum = 1n + 2n;
console.log(sum); // 3n

// 减法
const difference = 5n - 3n;
console.log(difference); // 2n

// 乘法
const product = 2n * 3n;
console.log(product); // 6n

// 除法(向下取整)
const quotient = 7n / 3n;
console.log(quotient); // 2n

// 取模
const remainder = 7n % 3n;
console.log(remainder); // 1n

// 幂运算
const power = 2n ** 10n;
console.log(power); // 1024n

// 自增/自减
let counter = 1n;
counter++;
console.log(counter); // 2n

counter--;
console.log(counter); // 1n

比较操作

javascript
// 与 BigInt 比较
console.log(1n < 2n); // true
console.log(2n > 1n); // true
console.log(1n === 1n); // true

// 与 Number 比较(类型不同,=== 会返回 false)
console.log(1n == 1); // true
console.log(1n === 1); // false

// 与 Number 进行大小比较
console.log(1n < 2); // true
console.log(2n > 1); // true

注意事项

  1. 类型转换:BigInt 不能与 Number 直接进行算术运算,需要先转换类型。
javascript
// 错误:BigInt 与 Number 不能直接运算
// const result = 1n + 1; // TypeError: Cannot mix BigInt and other types, use explicit conversions

// 正确:先转换类型
const result1 = 1n + BigInt(1); // 2n
const result2 = Number(1n) + 1; // 2
  1. 不支持的操作

    • BigInt 不能使用一元加号操作符(+)
    • BigInt 不能与 Number 一起使用位运算符(除了 >>>)
    • BigInt 不能用于 Math 对象的方法
  2. 兼容性:BigInt 在现代浏览器和 Node.js 10.4+ 中支持,旧环境需要使用 polyfill。

  3. 使用场景

    • 需要处理超出 Number 范围的整数
    • 密码学计算
    • 高精度金融计算
    • 处理大整数 ID

9. 请解释 ES6+ 中的私有字段和方法

Details

ES2022 引入了类的私有字段和方法,允许在类中定义私有成员,只能在类内部访问。

私有字段

私有字段使用 # 前缀定义,只能在类内部访问。

javascript
class Person {
  // 私有字段
  #name;
  #age;
  
  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }
  
  // 公共方法,可以访问私有字段
  getName() {
    return this.#name;
  }
  
  getAge() {
    return this.#age;
  }
  
  // 私有方法,只能在类内部访问
  #validateAge(age) {
    return age >= 0 && age <= 120;
  }
  
  setAge(age) {
    if (this.#validateAge(age)) {
      this.#age = age;
    } else {
      throw new Error('Invalid age');
    }
  }
}

const person = new Person('John', 30);

// 访问公共方法
console.log(person.getName()); // 'John'
console.log(person.getAge()); // 30

// 设置年龄
person.setAge(31);
console.log(person.getAge()); // 31

// 尝试直接访问私有字段(会报错)
// console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class

// 尝试直接调用私有方法(会报错)
// person.#validateAge(30); // SyntaxError: Private field '#validateAge' must be declared in an enclosing class

私有方法

私有方法使用 # 前缀定义,只能在类内部调用。

javascript
class Calculator {
  // 私有方法
  #add(a, b) {
    return a + b;
  }
  
  #subtract(a, b) {
    return a - b;
  }
  
  // 公共方法,调用私有方法
  calculate(operation, a, b) {
    switch (operation) {
      case 'add':
        return this.#add(a, b);
      case 'subtract':
        return this.#subtract(a, b);
      default:
        throw new Error('Invalid operation');
    }
  }
}

const calculator = new Calculator();

// 调用公共方法
console.log(calculator.calculate('add', 1, 2)); // 3
console.log(calculator.calculate('subtract', 5, 3)); // 2

// 尝试直接调用私有方法(会报错)
// console.log(calculator.#add(1, 2)); // SyntaxError: Private field '#add' must be declared in an enclosing class

静态私有字段和方法

静态私有字段和方法使用 static # 前缀定义,只能在类的静态方法中访问。

javascript
class MathUtils {
  // 静态私有字段
  static #PI = 3.14159;
  
  // 静态私有方法
  static #validateNumber(num) {
    return typeof num === 'number' && !isNaN(num);
  }
  
  // 静态公共方法,访问静态私有字段和方法
  static calculateCircleArea(radius) {
    if (this.#validateNumber(radius) && radius > 0) {
      return this.#PI * radius * radius;
    }
    throw new Error('Invalid radius');
  }
  
  static calculateCircleCircumference(radius) {
    if (this.#validateNumber(radius) && radius > 0) {
      return 2 * this.#PI * radius;
    }
    throw new Error('Invalid radius');
  }
}

// 调用静态公共方法
console.log(MathUtils.calculateCircleArea(5)); // 78.53975
console.log(MathUtils.calculateCircleCircumference(5)); // 31.4159

// 尝试直接访问静态私有字段(会报错)
// console.log(MathUtils.#PI); // SyntaxError: Private field '#PI' must be declared in an enclosing class

// 尝试直接调用静态私有方法(会报错)
// console.log(MathUtils.#validateNumber(5)); // SyntaxError: Private field '#validateNumber' must be declared in an enclosing class

私有字段和方法的优势

  1. 封装性:将内部实现细节隐藏,只暴露公共接口,提高代码的可维护性。
  2. 安全性:防止外部代码修改类的内部状态,避免意外的副作用。
  3. 代码清晰:明确区分公共接口和私有实现,使代码结构更清晰。
  4. 避免命名冲突:私有字段和方法不会与外部作用域的变量或方法发生命名冲突。

注意事项

  1. 语法:私有字段和方法必须使用 # 前缀,不能使用 private 关键字。
  2. 声明:私有字段必须在类的顶层声明,不能在构造函数或方法中声明。
  3. 访问:私有字段和方法只能在类内部访问,外部代码无法访问。
  4. 继承:私有字段和方法不会被继承,子类无法访问父类的私有成员。
  5. 兼容性:私有字段和方法在现代浏览器和 Node.js 12+ 中支持,旧环境需要使用 Babel 等工具进行转译。

10. 请简述 ES6 之后的主要新特性(ES2016-2023)

Details

ES6(ECMAScript 2015)之后,JavaScript 每年都会发布一个新的版本,引入新的特性和改进。以下是 ES2016 到 2023 的主要新特性:

ES2016(ES7)

  1. 数组 includes() 方法:检查数组是否包含指定元素。
javascript
const array = [1, 2, 3, 4, 5];
console.log(array.includes(3)); // true
console.log(array.includes(6)); // false
  1. 指数操作符()**:用于计算幂。
javascript
console.log(2 ** 3); // 8
console.log(3 ** 2); // 9

ES2017(ES8)

  1. async/await:提供了更简洁的方式来处理异步操作。
javascript
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}
  1. Object.values():返回对象所有值的数组。
javascript
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj)); // [1, 2, 3]
  1. Object.entries():返回对象所有键值对的数组。
javascript
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
  1. Object.getOwnPropertyDescriptors():返回对象所有属性的描述符。
javascript
const obj = { a: 1 };
console.log(Object.getOwnPropertyDescriptors(obj));
// { a: { value: 1, writable: true, enumerable: true, configurable: true } }
  1. 字符串填充方法padStart()padEnd()
javascript
const str = '5';
console.log(str.padStart(2, '0')); // '05'
console.log(str.padEnd(2, '0')); // '50'
  1. 函数参数列表和调用中的尾逗号:允许函数参数和调用中使用尾逗号。
javascript
function foo(a, b, c,) {
  // ...
}

foo(1, 2, 3,);

ES2018(ES9)

  1. 异步迭代器和生成器:支持异步迭代。
javascript
async function* asyncGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

async function processAsyncGenerator() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}
  1. Promise.finally():无论 Promise 成功或失败,都会执行的回调。
javascript
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error))
  .finally(() => console.log('Request completed'));
  1. 对象展开运算符:扩展了对象的展开运算符,支持更多场景。
javascript
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3, d: 4 };
console.log(obj2); // { a: 1, b: 2, c: 3, d: 4 }
  1. 正则表达式改进
    • 命名捕获组
    • 反向断言
    • Unicode 属性转义

ES2019(ES10)

  1. Array.flat():将嵌套数组扁平化为指定深度。
javascript
const nestedArray = [1, [2, [3, [4]]]];
console.log(nestedArray.flat()); // [1, 2, [3, [4]]]
console.log(nestedArray.flat(2)); // [1, 2, 3, [4]]
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4]
  1. Array.flatMap():先映射数组,然后扁平化结果。
javascript
const array = [1, 2, 3];
console.log(array.flatMap(x => [x, x * 2])); // [1, 2, 2, 4, 3, 6]
  1. String.trimStart()String.trimEnd():去除字符串开头和结尾的空白字符。
javascript
const str = '  Hello World  ';
console.log(str.trimStart()); // 'Hello World  '
console.log(str.trimEnd()); // '  Hello World'
  1. Object.fromEntries():将键值对数组转换为对象。
javascript
const entries = [['a', 1], ['b', 2], ['c', 3]];
console.log(Object.fromEntries(entries)); // { a: 1, b: 2, c: 3 }
  1. Symbol.description:获取 Symbol 的描述。
javascript
const sym = Symbol('description');
console.log(sym.description); // 'description'
  1. Function.toString():返回函数的完整源代码,包括注释和空格。

ES2020(ES11)

  1. 可选链操作符(?.):安全地访问对象的深层属性。
javascript
const user = { profile: { name: 'John' } };
console.log(user?.profile?.name); // 'John'
console.log(user?.address?.street); // undefined
  1. 空值合并操作符(??):当左侧表达式为 nullundefined 时,返回右侧表达式的值。
javascript
const name = null ?? 'Default';
console.log(name); // 'Default'

const count = 0 ?? 10;
console.log(count); // 0
  1. BigInt:表示任意精度的整数。
javascript
const bigInt = 9007199254740991n;
console.log(bigInt); // 9007199254740991n
  1. 动态导入(import()):在运行时按需加载模块。
javascript
async function loadModule() {
  const module = await import('./module.js');
  module.doSomething();
}
  1. globalThis:全局对象的统一引用。
javascript
console.log(globalThis); // 在浏览器中是 window,在 Node.js 中是 global

ES2021(ES12)

  1. 逻辑赋值运算符&&=||=??=
javascript
// &&=
let a = 1;
a &&= 2;
console.log(a); // 2

// ||=
let b = null;
b ||= 'default';
console.log(b); // 'default'

// ??=
let c = null;
c ??= 'default';
console.log(c); // 'default'
  1. 数字分隔符:使用下划线(_)分隔数字,提高可读性。
javascript
const million = 1_000_000;
console.log(million); // 1000000

const pi = 3.141_592_653_59;
console.log(pi); // 3.14159265359
  1. String.replaceAll():替换字符串中所有匹配的子串。
javascript
const str = 'Hello World, World!';
console.log(str.replaceAll('World', 'JavaScript')); // 'Hello JavaScript, JavaScript!'
  1. Promise.any():返回第一个成功的 Promise 的结果。
javascript
const promise1 = Promise.reject('error1');
const promise2 = Promise.resolve('success');
const promise3 = Promise.reject('error2');

Promise.any([promise1, promise2, promise3])
  .then(result => console.log(result)); // 'success'

ES2022(ES13)

  1. 类的私有字段和方法:使用 # 前缀定义私有成员。
javascript
class Person {
  #name;
  
  constructor(name) {
    this.#name = name;
  }
  
  #privateMethod() {
    return 'Private method';
  }
  
  publicMethod() {
    return this.#privateMethod();
  }
}
  1. Top-level await:在模块的顶层使用 await,不需要包裹在 async 函数中。
javascript
// module.js
const data = await fetch('https://api.example.com/data').then(res => res.json());
export default data;

// app.js
import data from './module.js';
console.log(data);
  1. Array.at():通过索引访问数组元素,支持负索引。
javascript
const array = [1, 2, 3, 4, 5];
console.log(array.at(0)); // 1
console.log(array.at(-1)); // 5
  1. Object.hasOwn():检查对象是否拥有指定的属性。
javascript
const obj = { a: 1 };
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'b')); // false
  1. Error.cause:在创建 Error 对象时,可以指定错误的原因。
javascript
try {
  // 操作
} catch (error) {
  throw new Error('Operation failed', { cause: error });
}
  1. RegExp.exec() 的改进:在全局模式下,exec() 方法现在会正确设置 lastIndex。

  2. WebAssembly 扩展:增强了 WebAssembly 与 JavaScript 的互操作性。

ES2023(ES14)

  1. Array findLast() 和 findLastIndex():从数组末尾开始查找元素。
javascript
const array = [1, 2, 3, 4, 5];
console.log(array.findLast(n => n % 2 === 0)); // 4
console.log(array.findLastIndex(n => n % 2 === 0)); // 3
  1. Hashbang 语法:支持在脚本文件开头使用 #!/usr/bin/env node 作为 shebang。

  2. Symbol 作为 WeakMap 键:现在可以使用 Symbol 作为 WeakMap 的键。

  3. Promise.withResolvers():提供一种更简洁的方式来创建和控制 Promise。

javascript
function delay(ms) {
  const { promise, resolve } = Promise.withResolvers();
  setTimeout(resolve, ms);
  return promise;
}

总结

ES6+ 引入了许多新特性,使 JavaScript 变得更加强大和易用。这些特性包括:

  • 更简洁的语法(箭头函数、模板字符串、解构赋值等)
  • 更好的异步处理(Promise、async/await)
  • 更强大的数据结构(Map、Set、WeakMap、WeakSet)
  • 更完善的模块系统
  • 更安全的类型处理(BigInt)
  • 更优雅的代码组织(类、私有字段和方法)
  • 更便捷的操作符(可选链、空值合并、逻辑赋值等)
  • 更强大的数组和字符串方法
  • 更灵活的错误处理(Error.cause)
  • 更现代的语法特性(Top-level await、Hashbang 等)

这些特性大大提高了 JavaScript 的开发效率和代码质量,使它成为一门更加成熟和现代化的编程语言。