Javascript相对较新的语法特性

122 阅读4分钟

JavaScript 的空值合并运算符(??

假设炭治郎和他的队友们有不同的攻击力值,但有些人的值可能未定义。我们可以使用空值合并运算符为未定义的攻击力赋予默认值。

const demonSlayers = {
  tanjiro: 95,
  nezuko: 85,
  zenitsu: 90,
  inosuke: null, // 攻击力未定义
};

function getAttackPower(name) {
  return demonSlayers[name] ?? 50; // 如果攻击力未定义,则使用默认值 50
}

console.log(`炭治郎的攻击力: ${getAttackPower('tanjiro')}`); // 输出: 炭治郎的攻击力: 95
console.log(`祢豆子的攻击力: ${getAttackPower('nezuko')}`); // 输出: 祢豆子的攻击力: 85
console.log(`善逸的攻击力: ${getAttackPower('zenitsu')}`); // 输出: 善逸的攻击力: 90
console.log(`伊之助的攻击力: ${getAttackPower('inosuke')}`); // 输出: 伊之助的攻击力: 50

在这个例子中,空值合并运算符(??)用于处理可能为 nullundefined 的攻击力值。如果攻击力未定义,运算符将返回默认值 50

动态导入 (dynamic import) 允许在代码运行时按需加载模块

这在按需加载功能或优化初始加载时间时非常有用。下面是一个以《鬼灭之刃》为背景的例子:

假设我们有一个模块文件 sword.js,其中定义了一个 Katana 类。炭治郎可以在另一个文件中导入这个模块,并使用 Katana 类。

sword.js

// sword.js
export class Katana {
  constructor() {
    this.type = '日轮刀';
  }

  slay() {
    console.log('斩鬼!');
  }
}

tanjiro.js

async function prepareForBattle() {
  console.log('炭治郎准备战斗...');
  const { Katana } = await import('./sword.js');
  const tanjiroSword = new Katana();
  console.log(`炭治郎的武器: ${tanjiroSword.type}`); // 输出: 炭治郎的武器: 日轮刀
  tanjiroSword.slay(); // 输出: 斩鬼!
}

prepareForBattle();

在这个例子中,import() 语句动态加载 sword.js 模块,只在 prepareForBattle 函数调用时才导入 Katana 类。这样可以优化初始加载时间,只有在需要时才加载模块。

JavaScript 类中的私有字段

假设我们有一个类 DemonSlayer,炭治郎不希望他的呼吸法和斩杀鬼的方法被外人知晓,所以将它们设为私有字段。

demonSlayer.js

class DemonSlayer {
  #breathingStyle; // 私有字段
  #slayMethod;     // 私有字段

  constructor(name, breathingStyle, slayMethod) {
    this.name = name;
    this.#breathingStyle = breathingStyle;
    this.#slayMethod = slayMethod;
  }

  getBreathingStyle() {
    return this.#breathingStyle;
  }

  slay() {
    console.log(`${this.name} 使用 ${this.#slayMethod} 斩杀鬼!`);
  }
}

const tanjiro = new DemonSlayer('炭治郎', '水之呼吸', '水面斩');
console.log(tanjiro.name); // 输出: 炭治郎
console.log(tanjiro.getBreathingStyle()); // 输出: 水之呼吸
tanjiro.slay(); // 输出: 炭治郎 使用 水面斩 斩杀鬼!

// 尝试访问私有字段会报错
console.log(tanjiro.#breathingStyle); // 语法错误: 私有字段 '#breathingStyle' 必须在封闭类中声明

在这个例子中,#breathingStyle#slayMethod 是私有字段,确保它们只能通过类的方法访问,从而保护炭治郎的秘密。

逻辑与赋值运算符(&&=

假设我们有一个对象 Hashira(柱),表示鬼杀队的柱成员。我们希望确保每个柱都有一个 breathingStyle(呼吸法)属性,并使用 &&= 来赋值。

let hashira = {
  name: '富冈义勇',
  breathingStyle: '水之呼吸'
};

// 使用 &&= 确保 breathingStyle 存在并赋值新的呼吸法
hashira.breathingStyle &&= '水之呼吸 流水之型';

// 输出对象,breathingStyle 保持不变
console.log(hashira); 
// 输出: { name: '富冈义勇', breathingStyle: '水之呼吸 流水之型' }

let anotherHashira = {
  name: '时透无一郎'
};

// 使用 &&= 确保 breathingStyle 存在并赋值新的呼吸法
anotherHashira.breathingStyle &&= '霞之呼吸';

// 输出对象,breathingStyle 未定义
console.log(anotherHashira); 
// 输出: { name: '时透无一郎' }

在这个例子中,hashira.breathingStyle &&= '水之呼吸 流水之型' 确保只有在 breathingStyle 属性存在的情况下才会进行赋值操作。对于 anotherHashira,由于 breathingStyle 未定义,所以不会进行赋值。

哈希邦语法(hashbang grammar)

在《鬼灭之刃》的背景下,假设我们有一个JavaScript脚本,用于检查和启动炭治郎的训练任务。在脚本的开头,我们使用哈希邦语法(hashbang grammar)来指定脚本应该由Node.js解释器执行。

trainTanjiro.js

#!/usr/bin/env node

console.log("炭治郎的训练开始!");

// 假设这是一个训练函数
function startTraining() {
  console.log("炭治郎正在练习水之呼吸...");
}

startTraining();

在这个例子中,#!/usr/bin/env node 是哈希邦语法,告诉操作系统使用Node.js解释器来运行这个脚本。这样炭治郎的训练任务可以在命令行中直接执行。

捕获错误(error.cause

在捕获错误时,我们可能会使用更具体或更加实用的信息对错误进行包装,再将其重新抛出。cause 属性就用于这一场景,以便仍然可以访问原始的错误。

在《鬼灭之刃》的背景下,我们可以使用 Error 对象的 cause 属性来捕获和传递错误的原因。假设我们有一个函数模拟炭治郎在训练时可能遇到的问题:

function trainTanjiro() {
  try {
    startBreathingTechnique();
  } catch (error) {
    throw new Error('炭治郎在训练中遇到问题', { cause: error });
  }
}

function startBreathingTechnique() {
  // 假设这里会抛出一个错误
  throw new Error('水之呼吸未能正确启动');
}

try {
  trainTanjiro();
} catch (error) {
  console.error(error.message); // 输出: 炭治郎在训练中遇到问题
  console.error(error.cause); // 输出: Error: 水之呼吸未能正确启动
}

在这个例子中,如果 startBreathingTechnique 函数中发生错误,会被捕获并重新抛出一个新错误,其中包含原始错误作为 cause。这样可以更好地跟踪和调试问题。