您应该了解的即将推出的 JavaScript 功能

2025-06-09

您应该了解的即将推出的 JavaScript 功能

写了 20 年 JavaScript 之后,我见证了许多变化——从回调地狱到 async/await。但即将推出的 JavaScript 特性将彻底改变我们编写代码的方式。

我们已经使用转译器和 polyfill 测试了这些提案,结果令人印象深刻:原来需要 30 行的代码现在只需 10 行,复杂的逻辑变得一目了然,甚至初级开发人员(和 Devin😅)也可以理解我们代码库的复杂部分。

如果您读过我之前关于“你不知道自己需要的 HTML5 元素”“CSS 模态窗口”的文章,您就会知道我们Lingo.dev喜欢不同寻常的技术。这些即将推出的 JavaScript 功能解决了困扰开发人员多年的实际问题。

管道运算符提高代码可读性

JavaScript 中复杂的数据转换通常会导致函数调用深度嵌套,难以阅读和维护。开发人员必须从内到外追踪嵌套函数,并在括号之间跳转才能理解数据流。

管道运算符(|>)通过允许数据以清晰的、自上而下的方式流经一系列操作来解决此问题:

// Instead of this nested mess
const result = saveToDatabase(
  validateUser(
    normalizeData(
      enrichUserProfile(user)
    )
  )
);

// You'll write this
const result = user
  |> enrichUserProfile
  |> normalizeData
  |> validateUser
  |> saveToDatabase;
Enter fullscreen mode Exit fullscreen mode

让我们看一个更复杂的实际示例。考虑一个随时间增长的图像处理服务:

// Before: Nested function hell
function processImage(image) {
  return compressImage(
    addWatermark(
      optimizeForWeb(
        applyFilter(
          resizeImage(image, { width: 800, height: 600 }),
          'sepia'
        )
      ),
      'Copyright 2025'
    ),
    0.8
  );
}

// After: Clean, readable pipeline
function processImage(image) {
  return image
    |> (img) => resizeImage(img, { width: 800, height: 600 })
    |> (img) => applyFilter(img, 'sepia')
    |> optimizeForWeb
    |> (img) => addWatermark(img, 'Copyright 2025')
    |> (img) => compressImage(img, 0.8);
}
Enter fullscreen mode Exit fullscreen mode

这是另一个实际示例,展示了管道运算符如何简化分析数据处理:

// Before: Hard to follow the data flow
function analyzeUserData(users) {
  return generateReport(
    groupByMetric(
      filterInactiveUsers(
        normalizeUserData(users)
      ),
      'registrationMonth'
    )
  );
}

// After: Clear data transformation steps
function analyzeUserData(users) {
  return users
    |> normalizeUserData
    |> filterInactiveUsers
    |> (data) => groupByMetric(data, 'registrationMonth')
    |> generateReport;
}
Enter fullscreen mode Exit fullscreen mode

管道操作符还可以更轻松地在步骤之间插入调试或日志记录:

function processPayment(payment) {
  return payment
    |> validatePayment
    |> (result) => {
        console.log(`Validation result: ${JSON.stringify(result)}`);
        return result;
      }
    |> processTransaction
    |> (result) => {
        console.log(`Transaction result: ${JSON.stringify(result)}`);
        return result;
      }
    |> sendReceipt;
}
Enter fullscreen mode Exit fullscreen mode

如何立即使用

截至 2025 年 5 月,管道操作符目前处于 TC39 流程的第 2 阶段。虽然它还不是官方 JavaScript 规范的一部分,但您现在就可以开始使用它:

使用 Babel(通用设置):

# Install the pipeline operator plugin
npm install --save-dev @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode

添加到您的.babelrc

{
  "plugins": [
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "hack", "topicToken": "%"}]
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 Vite:

# Install dependencies
npm install --save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    babel({
      babelConfig: {
        plugins: [
          ['@babel/plugin-proposal-pipeline-operator', { proposal: 'hack', topicToken: '%' }]
        ]
      }
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

使用 Next.js:

# Install the pipeline operator plugin
npm install --save-dev @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode
// .babelrc
{
  "presets": ["next/babel"],
  "plugins": [
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "hack", "topicToken": "%" }]
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 tsup:

# Install dependencies
npm install --save-dev tsup @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode
// tsup.config.ts
import { defineConfig } from 'tsup';
import * as babel from '@babel/core';
import fs from 'fs';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  esbuildPlugins: [
    {
      name: 'babel',
      setup(build) {
        build.onLoad({ filter: /\.(jsx?|tsx?)$/ }, async (args) => {
          const source = await fs.promises.readFile(args.path, 'utf8');
          const result = await babel.transformAsync(source, {
            filename: args.path,
            presets: [
              ['@babel/preset-env', { targets: 'defaults' }],
              '@babel/preset-typescript'
            ],
            plugins: [
              ['@babel/plugin-proposal-pipeline-operator', { proposal: 'hack', topicToken: '%' }]
            ]
          });

          return {
            contents: result?.code || '',
            loader: args.path.endsWith('x') ? 'jsx' : 'js'
          };
        });
      }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

使用 Remix(使用 Vite):

# Install dependencies
npm install --save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from 'vite';
import { vitePlugin as remix } from '@remix-run/dev';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    babel({
      babelConfig: {
        plugins: [
          ['@babel/plugin-proposal-pipeline-operator', { proposal: 'hack', topicToken: '%' }]
        ]
      }
    }),
    remix()
  ]
});
Enter fullscreen mode Exit fullscreen mode

模式匹配简化了复杂的条件

在大型代码库中,复杂的 if/else 语句和 switch case 很快就会变得难以处理。嵌套 if/else 语句块的函数会检查各种对象属性,这使得验证所有边缘情况几乎不可能。

模式匹配为这个问题提供了直接的解决方案。

此功能为 JavaScript 带来了函数式编程功能,使您可以通过单个操作匹配和解构复杂数据:

function processMessage(message) {
  return match (message) {
    when String(text) => `Text message: ${text}`,
    when [String(sender), String(content)] => `Message from ${sender}: ${content}`,
    when { type: 'error', content: String(text), code: Number(code) } => 
      `Error: ${text} (Code: ${code})`,
    when _ => 'Unknown message format'
  };
}
Enter fullscreen mode Exit fullscreen mode

如果没有模式匹配,您将需要多个带有类型检查的 if/else 语句:

function processMessage(message) {
  // Check if it's a string
  if (typeof message === 'string') {
    return `Text message: ${message}`;
  }

  // Check if it's an array with specific structure
  if (Array.isArray(message) && 
      message.length === 2 && 
      typeof message[0] === 'string' && 
      typeof message[1] === 'string') {
    return `Message from ${message[0]}: ${message[1]}`;
  }

  // Check if it's an object with specific properties
  if (message && 
      typeof message === 'object' && 
      message.type === 'error' && 
      typeof message.content === 'string' && 
      typeof message.code === 'number') {
    return `Error: ${message.content} (Code: ${message.code})`;
  }

  // Default case
  return 'Unknown message format';
}
Enter fullscreen mode Exit fullscreen mode

模式匹配在处理现代 Web 应用程序中的复杂状态转换时表现出色:

function handleUserAction(state, action) {
  return match ([state, action]) {
    when [{ status: 'idle' }, { type: 'FETCH_START' }] => 
      ({ status: 'loading', data: state.data }),

    when [{ status: 'loading' }, { type: 'FETCH_SUCCESS', payload }] => 
      ({ status: 'success', data: payload, error: null }),

    when [{ status: 'loading' }, { type: 'FETCH_ERROR', error }] => 
      ({ status: 'error', data: null, error }),

    when [{ status: 'success' }, { type: 'REFRESH' }] => 
      ({ status: 'loading', data: state.data }),

    when _ => state
  };
}
Enter fullscreen mode Exit fullscreen mode

没有模式匹配的等效代码明显更加冗长:

function handleUserAction(state, action) {
  // Check idle state + fetch start
  if (state.status === 'idle' && action.type === 'FETCH_START') {
    return { status: 'loading', data: state.data };
  }

  // Check loading state + fetch success
  if (state.status === 'loading' && action.type === 'FETCH_SUCCESS') {
    return { status: 'success', data: action.payload, error: null };
  }

  // Check loading state + fetch error
  if (state.status === 'loading' && action.type === 'FETCH_ERROR') {
    return { status: 'error', data: null, error: action.error };
  }

  // Check success state + refresh
  if (state.status === 'success' && action.type === 'REFRESH') {
    return { status: 'loading', data: state.data };
  }

  // Default: return unchanged state
  return state;
}
Enter fullscreen mode Exit fullscreen mode

模式匹配还提供详尽性检查——如果您遗漏了某个可能的情况,编译器会发出警告。这消除了困扰传统条件逻辑的一整类错误。

这是解析配置格式的另一个实际示例:

function parseConfig(config) {
  return match (config) {
    when { version: 1, settings: Object(settings) } => 
      parseV1Settings(settings),

    when { version: 2, config: Object(settings) } => 
      parseV2Settings(settings),

    when String(jsonString) => 
      parseConfig(JSON.parse(jsonString)),

    when [String(env), Object(overrides)] =>
      mergeConfigs(getEnvConfig(env), overrides),

    when _ => 
      throw new Error(`Invalid configuration format: ${JSON.stringify(config)}`)
  };
}
Enter fullscreen mode Exit fullscreen mode

如何立即使用

截至 2025 年 5 月,模式匹配目前处于 TC39 流程的第 1 阶段,这意味着它仍处于提案阶段,语法和语义方面的讨论仍在进行中。不过,您现在就可以尝试一下:

使用 Babel(通用设置):

# Install the pattern matching plugin
npm install --save-dev babel-plugin-proposal-pattern-matching
Enter fullscreen mode Exit fullscreen mode

添加到您的.babelrc

{
  "plugins": ["babel-plugin-proposal-pattern-matching"]
}
Enter fullscreen mode Exit fullscreen mode

使用 Vite:

# Install dependencies
npm install --save-dev vite-plugin-babel @babel/core babel-plugin-proposal-pattern-matching
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    babel({
      babelConfig: {
        plugins: [
          'babel-plugin-proposal-pattern-matching'
        ]
      }
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

使用 Next.js:

# Install the pattern matching plugin
npm install --save-dev babel-plugin-proposal-pattern-matching
Enter fullscreen mode Exit fullscreen mode
// .babelrc
{
  "presets": ["next/babel"],
  "plugins": [
    "babel-plugin-proposal-pattern-matching"
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 tsup:

# Install dependencies
npm install --save-dev tsup @babel/core babel-plugin-proposal-pattern-matching
Enter fullscreen mode Exit fullscreen mode
// tsup.config.ts
import { defineConfig } from 'tsup';
import * as babel from '@babel/core';
import fs from 'fs';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  esbuildPlugins: [
    {
      name: 'babel',
      setup(build) {
        build.onLoad({ filter: /\.(jsx?|tsx?)$/ }, async (args) => {
          const source = await fs.promises.readFile(args.path, 'utf8');
          const result = await babel.transformAsync(source, {
            filename: args.path,
            presets: [
              ['@babel/preset-env', { targets: 'defaults' }],
              '@babel/preset-typescript'
            ],
            plugins: [
              'babel-plugin-proposal-pattern-matching'
            ]
          });

          return {
            contents: result?.code || '',
            loader: args.path.endsWith('x') ? 'jsx' : 'js'
          };
        });
      }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

生产替代方案:

对于今天的生产代码,请使用 ts-pattern 库:

npm install ts-pattern
Enter fullscreen mode Exit fullscreen mode
import { match, P } from 'ts-pattern';

function processMessage(message: unknown) {
  return match(message)
    .with(P.string, text => `Text message: ${text}`)
    .with([P.string, P.string], ([sender, content]) => 
      `Message from ${sender}: ${content}`)
    .with({ type: 'error', content: P.string, code: P.number }, 
      ({ content, code }) => `Error: ${content} (Code: ${code})`)
    .otherwise(() => 'Unknown message format');
}
Enter fullscreen mode Exit fullscreen mode

Temporal API 解决日期处理问题

JavaScript 的内置Date对象长期以来一直是开发者们苦恼的根源。它易变(日期可能会被意外修改),月份索引混乱(一月是 0!),而且时区处理也存在问题。这些问题导致了 Moment.js、date-fns 和 Luxon 等库的广泛使用。

Temporal API 通过重新构想 JavaScript 中的日期和时间处理为这些问题提供了完整的解决方案。

以下是跨时区安排会议的实际示例:

// Current approach with Date (likely to have bugs)
function scheduleMeeting(startDate, durationInMinutes, timeZone) {
  const start = new Date(startDate);
  const end = new Date(start.getTime() + durationInMinutes * 60000);

  return {
    start: start.toISOString(),
    end: end.toISOString(),
    timeZone: timeZone // Not actually used in calculations
  };
}

// With Temporal API
function scheduleMeeting(startDateTime, durationInMinutes, timeZone) {
  const start = Temporal.ZonedDateTime.from(startDateTime);
  const end = start.add({ minutes: durationInMinutes });

  return {
    start: start.toString(),
    end: end.toString(),
    timeZone: start.timeZoneId // Properly tracked
  };
}
Enter fullscreen mode Exit fullscreen mode

对于国际航班预订系统,计算跨时区的航班时间变得非常简单:

// Current approach with Date (error-prone)
function calculateFlightDuration(departure, arrival, departureTimeZone, arrivalTimeZone) {
  // Convert to milliseconds and calculate difference
  const departureTime = new Date(departure);
  const arrivalTime = new Date(arrival);

  // This doesn't account for timezone differences correctly
  const durationMs = arrivalTime - departureTime;

  return {
    hours: Math.floor(durationMs / (1000 * 60 * 60)),
    minutes: Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60)),
    // No easy way to get arrival in departure's timezone
  };
}

// With Temporal API
function calculateFlightDuration(departure, arrival) {
  const departureTime = Temporal.ZonedDateTime.from(departure);
  const arrivalTime = Temporal.ZonedDateTime.from(arrival);

  // Accurate duration calculation across time zones
  const duration = departureTime.until(arrivalTime);

  return {
    hours: duration.hours,
    minutes: duration.minutes,
    inLocalTime: arrivalTime.toLocaleString(),
    inDepartureTime: arrivalTime.withTimeZone(departureTime.timeZoneId).toLocaleString()
  };
}
Enter fullscreen mode Exit fullscreen mode

下面是另一个示例,展示了 Temporal API 如何处理重复事件,这对于当前的 Date 对象来说非常困难:

// Current approach with Date (complex and error-prone)
function getNextMeetingDates(startDate, count) {
  const dates = [];
  const current = new Date(startDate);

  for (let i = 0; i < count; i++) {
    dates.push(new Date(current));

    // Add 2 weeks - error-prone due to month boundaries, DST changes, etc.
    current.setDate(current.getDate() + 14);
  }

  return dates;
}

// With Temporal API
function getNextMeetingDates(startDate, count) {
  const start = Temporal.PlainDate.from(startDate);
  const dates = [];

  for (let i = 0; i < count; i++) {
    const nextDate = start.add({ days: i * 14 });
    dates.push(nextDate);
  }

  return dates;
}
Enter fullscreen mode Exit fullscreen mode

Temporal API 提供了几个主要优点:

  1. 不变性:所有 Temporal 对象都是不可变的,防止意外修改
  2. 针对不同用例使用不同的类型:PlainDate、PlainTime、ZonedDateTime 等。
  3. 直观的方法:清晰、可链接的日期运算方法
  4. 适当的时区处理:内置时区和夏令时支持
  5. 一致的行为:在所有浏览器中以相同的方式工作

如何立即使用

截至 2025 年 5 月,Temporal API 已处于 TC39 流程的第三阶段,这意味着它即将完成,并且正在开发相关实现。以下是目前的使用方法:

使用官方 Polyfill:

npm install @js-temporal/polyfill
Enter fullscreen mode Exit fullscreen mode
// Import in your code
import { Temporal } from '@js-temporal/polyfill';

// Get the current date and time
const now = Temporal.Now.plainDateTimeISO();
console.log(`Current time: ${now.toString()}`);
Enter fullscreen mode Exit fullscreen mode

使用 Vite:

# Install the Temporal API polyfill
npm install @js-temporal/polyfill
Enter fullscreen mode Exit fullscreen mode
// main.js or any entry file
import { Temporal } from '@js-temporal/polyfill';

// Now you can use Temporal API in your code
const now = Temporal.Now.plainDateTimeISO();
console.log(`Current time: ${now.toString()}`);
Enter fullscreen mode Exit fullscreen mode

使用 Next.js:

# Install the Temporal API polyfill
npm install @js-temporal/polyfill
Enter fullscreen mode Exit fullscreen mode
// pages/_app.js
import { Temporal } from '@js-temporal/polyfill';

// Make Temporal available globally if needed
if (typeof window !== 'undefined') {
  window.Temporal = Temporal;
}

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

使用 tsup:

# Install the Temporal API polyfill
npm install @js-temporal/polyfill
Enter fullscreen mode Exit fullscreen mode
// src/index.ts
import { Temporal } from '@js-temporal/polyfill';

// Export it if you want to make it available to consumers of your package
export { Temporal };

// Your other code here
Enter fullscreen mode Exit fullscreen mode

浏览器支持:

  • Chrome/Edge:在“实验性 JavaScript”标志后可用
  • Firefox:Firefox 139 及更高版本中可用
  • Safari:尚未实现

要检查 Temporal 是否受本机支持:

if (typeof Temporal !== 'undefined') {
  console.log('Temporal API is natively supported');
} else {
  console.log('Using polyfill');
  // Import polyfill
}
Enter fullscreen mode Exit fullscreen mode

资源管理消除内存泄漏

JavaScript 长期以来缺乏确定性的资源清理机制。在处理文件、数据库连接或硬件访问时,开发人员必须手动确保资源得到正确释放——即使在出现错误的情况下也是如此。

这种限制会导致内存泄漏和资源耗尽错误。典型的模式涉及 try/finally 块,这些块很快就会变得难以处理:

async function processFile(path) {
  let file = null;
  try {
    file = await fs.promises.open(path, 'r');
    const content = await file.readFile({ encoding: 'utf8' });
    return processContent(content);
  } finally {
    if (file) {
      await file.close();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

newusingawait using语句在 JavaScript 中提供确定性的资源管理:

async function processFile(path) {
  await using file = await fs.promises.open(path, 'r');
  const content = await file.readFile({ encoding: 'utf8' });
  return processContent(content);
  // File is automatically closed when the block exits
}
Enter fullscreen mode Exit fullscreen mode

让我们看一个更复杂的例子,其中有多个资源必须按照特定的顺序进行管理:

// Current approach: Nested try/finally blocks
async function processData(dbConfig, filePath) {
  let db = null;
  let file = null;

  try {
    db = await Database.connect(dbConfig);

    try {
      file = await fs.promises.open(filePath, 'r');
      const data = await file.readFile({ encoding: 'utf8' });
      const processed = processRawData(data);

      return db.store(processed);
    } finally {
      if (file) {
        await file.close();
      }
    }
  } finally {
    if (db) {
      await db.disconnect();
    }
  }
}

// With resource management: Clean and safe
async function processData(dbConfig, filePath) {
  await using db = await Database.connect(dbConfig);
  await using file = await fs.promises.open(filePath, 'r');

  const data = await file.readFile({ encoding: 'utf8' });
  const processed = processRawData(data);

  return db.store(processed);
  // Resources are automatically cleaned up in reverse order:
  // 1. file is closed
  // 2. db is disconnected
}
Enter fullscreen mode Exit fullscreen mode

对于数据库密集型应用程序,此功能可以改变连接池管理:

class DatabaseConnection {
  constructor(config) {
    this.config = config;
    this.connection = null;
  }

  async connect() {
    this.connection = await createConnection(this.config);
    return this;
  }

  async query(sql, params) {
    return this.connection.query(sql, params);
  }

  async [Symbol.asyncDispose]() {
    if (this.connection) {
      await this.connection.close();
      this.connection = null;
    }
  }
}

// Using the connection with automatic cleanup
async function getUserData(userId) {
  await using db = await new DatabaseConnection(config).connect();
  return db.query('SELECT * FROM users WHERE id = ?', [userId]);
  // Connection is automatically closed when the function exits
}
Enter fullscreen mode Exit fullscreen mode

下面是另一个示例,展示如何管理 WebUSB 设备等硬件资源:

// Current approach: Manual cleanup required
async function readFromUSBDevice(deviceFilter) {
  let device = null;
  try {
    const devices = await navigator.usb.getDevices();
    device = devices.find(d => d.productId === deviceFilter.productId);

    if (!device) {
      device = await navigator.usb.requestDevice({ filters: [deviceFilter] });
    }

    await device.open();
    await device.selectConfiguration(1);
    await device.claimInterface(0);

    const result = await device.transferIn(1, 64);
    return new TextDecoder().decode(result.data);
  } finally {
    if (device) {
      try {
        await device.close();
      } catch (e) {
        console.error("Error closing device:", e);
      }
    }
  }
}

// With resource management: Automatic cleanup
class USBDeviceResource {
  constructor(device) {
    this.device = device;
  }

  static async create(deviceFilter) {
    const devices = await navigator.usb.getDevices();
    let device = devices.find(d => d.productId === deviceFilter.productId);

    if (!device) {
      device = await navigator.usb.requestDevice({ filters: [deviceFilter] });
    }

    await device.open();
    await device.selectConfiguration(1);
    await device.claimInterface(0);

    return new USBDeviceResource(device);
  }

  async read() {
    const result = await this.device.transferIn(1, 64);
    return new TextDecoder().decode(result.data);
  }

  async [Symbol.asyncDispose]() {
    try {
      await this.device.close();
    } catch (e) {
      console.error("Error closing device:", e);
    }
  }
}

async function readFromUSBDevice(deviceFilter) {
  await using device = await USBDeviceResource.create(deviceFilter);
  return device.read();
  // Device is automatically closed when the function exits
}
Enter fullscreen mode Exit fullscreen mode

如何立即使用

截至 2025 年 5 月,显式资源管理提案 (using/await using) 已进入 TC39 流程的第 3 阶段,这意味着它即将实现标准化。以下是目前的使用方法:

使用 Babel(通用设置):

# Install the resource management plugin
npm install --save-dev @babel/plugin-proposal-explicit-resource-management
Enter fullscreen mode Exit fullscreen mode

添加到您的.babelrc

{
  "plugins": ["@babel/plugin-proposal-explicit-resource-management"]
}
Enter fullscreen mode Exit fullscreen mode

使用 Vite:

# Install dependencies
npm install --save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-explicit-resource-management
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    babel({
      babelConfig: {
        plugins: [
          '@babel/plugin-proposal-explicit-resource-management'
        ]
      }
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

使用 Next.js:

# Install the resource management plugin
npm install --save-dev @babel/plugin-proposal-explicit-resource-management
Enter fullscreen mode Exit fullscreen mode
// .babelrc
{
  "presets": ["next/babel"],
  "plugins": [
    "@babel/plugin-proposal-explicit-resource-management"
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 tsup:

# Install dependencies
npm install --save-dev tsup @babel/core @babel/plugin-proposal-explicit-resource-management
Enter fullscreen mode Exit fullscreen mode
// tsup.config.ts
import { defineConfig } from 'tsup';
import * as babel from '@babel/core';
import fs from 'fs';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  esbuildPlugins: [
    {
      name: 'babel',
      setup(build) {
        build.onLoad({ filter: /\.(jsx?|tsx?)$/ }, async (args) => {
          const source = await fs.promises.readFile(args.path, 'utf8');
          const result = await babel.transformAsync(source, {
            filename: args.path,
            presets: [
              ['@babel/preset-env', { targets: 'defaults' }],
              '@babel/preset-typescript'
            ],
            plugins: [
              '@babel/plugin-proposal-explicit-resource-management'
            ]
          });

          return {
            contents: result?.code || '',
            loader: args.path.endsWith('x') ? 'jsx' : 'js'
          };
        });
      }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

使物体可丢弃:

为了使您的对象能够使用using,请实现该Symbol.dispose方法:

class MyResource {
  [Symbol.dispose]() {
    // Cleanup code here
  }
}
Enter fullscreen mode Exit fullscreen mode

对于异步资源,实现Symbol.asyncDispose

class MyAsyncResource {
  async [Symbol.asyncDispose]() {
    // Async cleanup code here
  }
}
Enter fullscreen mode Exit fullscreen mode

符号的 Polyfill:

// polyfill.js
if (!Symbol.dispose) {
  Symbol.dispose = Symbol("Symbol.dispose");
}

if (!Symbol.asyncDispose) {
  Symbol.asyncDispose = Symbol("Symbol.asyncDispose");
}
Enter fullscreen mode Exit fullscreen mode

装饰器无需改变核心逻辑即可添加功能

JavaScript 装饰器提供了一种简洁的方式来修改类和方法,并赋予它们额外的功能。如果您使用过 TypeScript 或 Angular 等框架,那么您一定对这个概念很熟悉。现在,装饰器即将登陆原生 JavaScript。

装饰器解决了一个基本问题:如何在不扰乱业务逻辑的情况下添加横切关注点(如日志记录、验证或性能监控)。

这是一个实际的例子:

class UserController {
  @authenticate
  @rateLimit(100)
  @validate(userSchema)
  @logAccess
  async updateUserProfile(userId, profileData) {
    // The method is automatically wrapped with:
    // 1. Authentication check
    // 2. Rate limiting (100 requests per hour)
    // 3. Input validation against userSchema
    // 4. Access logging

    const user = await this.userRepository.findById(userId);
    Object.assign(user, profileData);
    return this.userRepository.save(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

如果没有装饰器,您将需要手动包装每个方法,从而导致代码更难阅读和维护:

class UserController {
  async updateUserProfile(userId, profileData) {
    // Authentication check
    if (!isAuthenticated()) {
      throw new Error('Unauthorized');
    }

    // Rate limiting
    if (isRateLimited(this.constructor.name, 'updateUserProfile', 100)) {
      throw new Error('Rate limit exceeded');
    }

    // Input validation
    const validationResult = validateWithSchema(userSchema, profileData);
    if (!validationResult.valid) {
      throw new Error(`Invalid data: ${validationResult.errors.join(', ')}`);
    }

    // Logging
    logAccess(this.constructor.name, 'updateUserProfile', userId);

    // Actual business logic
    const user = await this.userRepository.findById(userId);
    Object.assign(user, profileData);
    return this.userRepository.save(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

以下是使用装饰器进行性能监控的真实示例:

class DataProcessor {
  @measure
  @cache
  processLargeDataset(data) {
    // Complex and time-consuming operation
    return data.map(item => /* complex transformation */)
               .filter(item => /* complex filtering */)
               .reduce((acc, item) => /* complex aggregation */);
  }
}

// Performance measurement decorator
function measure(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    const start = performance.now();
    const result = original.apply(this, args);
    const end = performance.now();
    console.log(`${name} took ${end - start}ms to execute`);
    return result;
  };
  return descriptor;
}

// Caching decorator
function cache(target, name, descriptor) {
  const original = descriptor.value;
  const cacheStore = new Map();

  descriptor.value = function(...args) {
    const key = JSON.stringify(args);
    if (cacheStore.has(key)) {
      console.log(`Cache hit for ${name}`);
      return cacheStore.get(key);
    }

    console.log(`Cache miss for ${name}`);
    const result = original.apply(this, args);
    cacheStore.set(key, result);
    return result;
  };

  return descriptor;
}
Enter fullscreen mode Exit fullscreen mode

装饰器对于 API 开发尤为重要。以下是一个使用装饰器实现 RESTful API 并进行适当错误处理的示例:

class ProductAPI {
  @route('GET', '/products')
  @paginate
  @handleErrors
  async getAllProducts(req) {
    return this.productRepository.findAll();
  }

  @route('GET', '/products/:id')
  @handleErrors
  async getProductById(req) {
    const product = await this.productRepository.findById(req.params.id);
    if (!product) {
      throw new NotFoundError(`Product with ID ${req.params.id} not found`);
    }
    return product;
  }

  @route('POST', '/products')
  @validate(productSchema)
  @handleErrors
  async createProduct(req) {
    return this.productRepository.create(req.body);
  }
}

// Route decorator
function route(method, path) {
  return function(target, name, descriptor) {
    if (!target.constructor._routes) {
      target.constructor._routes = [];
    }

    target.constructor._routes.push({
      method,
      path,
      handler: descriptor.value,
      name
    });

    return descriptor;
  };
}

// Error handling decorator
function handleErrors(target, name, descriptor) {
  const original = descriptor.value;

  descriptor.value = async function(req, res) {
    try {
      const result = await original.call(this, req);
      return res.json(result);
    } catch (error) {
      if (error instanceof NotFoundError) {
        return res.status(404).json({ error: error.message });
      }
      if (error instanceof ValidationError) {
        return res.status(400).json({ error: error.message });
      }
      console.error(`Error in ${name}:`, error);
      return res.status(500).json({ error: 'Internal server error' });
    }
  };

  return descriptor;
}
Enter fullscreen mode Exit fullscreen mode

如何使用装饰器

截至 2025 年 5 月,装饰器已进入 TC39 流程的第 3 阶段,主流浏览器正在逐步实现。以下是目前的使用方法:

使用 Babel(通用设置):

# Install the decorators plugin
npm install --save-dev @babel/plugin-proposal-decorators
Enter fullscreen mode Exit fullscreen mode

添加到您的.babelrc

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "version": "2023-05" }]
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 Vite:

# Install dependencies
npm install --save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-decorators
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    babel({
      babelConfig: {
        plugins: [
          ['@babel/plugin-proposal-decorators', { version: '2023-05' }]
        ]
      }
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

使用 Next.js:

# Install the decorators plugin
npm install --save-dev @babel/plugin-proposal-decorators
Enter fullscreen mode Exit fullscreen mode
// .babelrc
{
  "presets": ["next/babel"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "version": "2023-05" }]
  ]
}
Enter fullscreen mode Exit fullscreen mode

使用 TypeScript:

在以下位置启用装饰器tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "experimentalDecorators": true
  }
}
Enter fullscreen mode Exit fullscreen mode

使用 tsup:

# Install dependencies
npm install --save-dev tsup @babel/core @babel/plugin-proposal-decorators
Enter fullscreen mode Exit fullscreen mode
// tsup.config.ts
import { defineConfig } from 'tsup';
import * as babel from '@babel/core';
import fs from 'fs';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  esbuildPlugins: [
    {
      name: 'babel',
      setup(build) {
        build.onLoad({ filter: /\.(jsx?|tsx?)$/ }, async (args) => {
          const source = await fs.promises.readFile(args.path, 'utf8');
          const result = await babel.transformAsync(source, {
            filename: args.path,
            presets: [
              ['@babel/preset-env', { targets: 'defaults' }],
              '@babel/preset-typescript'
            ],
            plugins: [
              ['@babel/plugin-proposal-decorators', { version: '2023-05' }]
            ]
          });

          return {
            contents: result?.code || '',
            loader: args.path.endsWith('x') ? 'jsx' : 'js'
          };
        });
      }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

重要提示:

装饰器提案已经历了多次迭代。请确保您使用的是最新语法(2023-05 版),因为旧版本不兼容。当前规范为装饰器定义了三项功能:

  • 它们可以用匹配值替换装饰值
  • 它们可以提供对装饰值的访问
  • 它们可以初始化装饰值

测试您的配置

设置配置后,创建一个简单的测试文件来验证功能是否正常工作:

// test-features.js

// Test pipeline operator
const double = x => x * 2;
const add = x => x + 1;
const square = x => x * x;

const result = 5
  |> double
  |> add
  |> square;

console.log("Pipeline result:", result); // Should be 121

// Test pattern matching
function processValue(value) {
  return match (value) {
    when String(s) => `String: ${s}`,
    when Number(n) => `Number: ${n}`,
    when { type, data } => `Object with type ${type}`,
    when _ => 'Unknown'
  };
}

console.log("Pattern matching:", processValue("test"), processValue(42), processValue({ type: "user", data: {} }));

// Test Temporal API
import { Temporal } from '@js-temporal/polyfill';
const now = Temporal.Now.plainDateTimeISO();
console.log("Current time:", now.toString());

// Test resource management
class Resource {
  constructor(name) {
    this.name = name;
    console.log(`Resource ${name} created`);
  }

  [Symbol.dispose]() {
    console.log(`Resource ${this.name} disposed`);
  }
}

{
  using resource = new Resource("test");
  console.log("Using resource");
} // Resource should be disposed here

// Test decorators
function log(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`Calling ${name} with ${JSON.stringify(args)}`);
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @log
  add(a, b) {
    return a + b;
  }
}

const calc = new Calculator();
console.log("Decorator result:", calc.add(2, 3));
Enter fullscreen mode Exit fullscreen mode

结论

这些即将推出的 JavaScript 功能不仅仅是语法糖,它们从根本上改变了我们编写代码的方式。在Lingo.dev,我们已经见证了它们如何将复杂、易错的代码转化为简洁、易于维护的解决方案。

最棒的是,您无需等待。有了合适的工具,您现在就可以开始使用这些功能。从资源管理到正确处理日期,每个功能都能解决开发人员日常面临的实际问题。

你对即将推出的 JavaScript 功能最感兴趣吗?你有没有找到其他方法来解决代码库中的这些问题?

分享您的想法!


有用的链接:

鏂囩珷鏉ユ簮锛�https://dev.to/maxprilutskiy/upcoming-javascript-features-you-should-know-about-178h
PREV
五大反对无障碍的(错误)论点的反驳
NEXT
GitHub 作品集的技巧