ES15 (ES2024) có gì mới?

Những tính năng mới trong ES15 (ES2024) mà bạn cần biết.


JavaScript liên tục phát triển như vũ bão, với hàng loạt tính năng mới được ra mắt mỗi năm. Vậy, trong năm 2024, chúng ta có thể sử dụng những tính năng JavaScript nào? Và những tính năng nào đang được hứa hẹn sẽ có tác động to lớn trong tương lai?

  • String.prototype.isWellFormedString.prototype.toWellFormed
  • Cờ /v cho RegExp

String.prototype.isWellFormedString.prototype.toWellFormed

Để mã hóa toàn bộ ký tự, biểu tượng được sử dụng trong các ngôn ngữ trên toàn thế giới, người ta sử dụng chuẩn mã hóa Unicode. Chuẩn Unicode mới nhất hiện này được viết dưới dạng \u{xxxxxx}, trong đó, mỗi chữ x biểu diễn 1 số HEX. Với chuẩn Unicode mới nhất, dải biễu diễn ký tự là từ \u0000 cho đến \u{10FFFF}. Tức là có thể biễu diễn được 1.114.112 ký tự.

  (10FFFF)₁₆ = (1 × 16⁵) + (0 × 16⁴) + (15 × 16³) + (15 × 16²) + (15 × 16¹) + (15 × 16⁰) = (1114111)₁₀

Người ta chia dải ký tự biểu diễn được bởi chuẩn Unicode thành 2 nhóm:

  • Basic Multilingual Plane (BMP): bao gồm các ký tự từ \u0000 đến \uFFFF.
  • Supplementary Planes: bao gồm các ký tự từ \u10000 đến \u{10FFFF}.

String trong Javascript được mã hóa dưới dạng UTF-16, điều này có nghĩa là mỗi ký tự Unicode có thể được mã hóa thành 1 hoặc 2 UTF-16 code units. Với các ký tự thuộc BMP, mỗi ký tự được mã hóa thành 1 UTF-16 code unit. Với các ký tự thuộc Supplementary Planes, mỗi ký tự được mã hóa thành 2 UTF-16 code units (được gọi là High Surrogate và Low Surrogate).

  • High Surrogate: từ \U+D800 đến \U+DBFF.
  • Low Surrogate: từ \U+DC00 đến \U+DFFF. Như vậy dải UTF-16 từ \U+D800 đến \U+DFFF không được sử dụng để mã hóa 1 ký tự nào. Mà phải dùng tới 2 UTF-16 code unit trong dải này để mã hóa 1 ký tự.

Khi các High SurrogateLow Surrogate đứng một mình trong chuỗi, không kết hợp với nhau để mã hóa 1 ký tự nào, ta gọi chuỗi đó là không hợp lệ. Và các High SurrogateLow Surrogate này được gọi là lone surogate. Như vậy một chuỗi không hợp lệ sẽ chứa các lone surrogate, sẽ dẫn tới lỗi khi mã hóa hoặc giải mã chuỗi khi sử dụng các hàm như encodeURI, TextEncoder, etc. do những hàm này sử dụng mã hóa UTF-8. Trong khi các lone surrogate không thể được mã hóa thành UTF-8 do chúng không phải là ký tự Unicode.

Để giải quyết vấn đề này, ES15 (ES2024) đã giới thiệu 2 phương thức mới cho String:

  • String.prototype.isWellFormed: kiểm tra xem chuỗi có hợp lệ không (không chứa lone surogate). Ví dụ 1:
        const strings = [
        // Lone leading surrogate
        "ab\\uD800",
        "ab\\uD800c",
        // Lone trailing surrogate
        "\\uDFFFab",
        "c\\uDFFFab",
        // Well-formed
        "abc",
        "ab\\uD83D\\uDE04c",
      ];
     
      for (const str of strings) {
        console.log(str.isWellFormed());
      }
      // Logs:
      // false
      // false
      // false
      // false
      // true
      // true
    Ví dụ 2: Hàm encodeURI sẽ bắn lỗi nếu chuỗi không hợp lệ. Điều này có thể được giải quyết bằng cách kiểm tra chuỗi trước khi mã hóa.
        const illFormed = "https://example.com/search?q=\\uD800";
     
        try {
          encodeURI(illFormed);
        } catch (e) {
          console.log(e); // URIError: URI malformed
        }
     
        if (illFormed.isWellFormed()) {
          console.log(encodeURI(illFormed));
        } else {
          console.warn("Ill-formed strings encountered."); // Ill-formed strings encountered.
        }
     
  • String.prototype.toWellFormed: chuyển đổi chuỗi không hợp lệ thành chuỗi hợp lệ bằng cách thay thế lone surogate bằng ký tự (\U+FFFD). Ví dụ 1:
        const strings = [
        // Lone leading surrogate
        "ab\\uD800",
        "ab\\uD800c",
        // Lone trailing surrogate
        "\\uDFFFab",
        "c\\uDFFFab",
        "ab\\uD83D\\uDE04c",
      ];
     
      for (const str of strings) {
        console.log(str.toWellFormed());
      }
      // Logs:
      // ab�
      // ab�c
      // �ab
      // c�ab
      // ab😄c
    Ví dụ 2:
        const illFormed = "https://example.com/search?q=\\uD800";
     
        try {
          encodeURI(illFormed);
        } catch (e) {
          console.log(e); // URIError: URI malformed
        }
     
        console.log(encodeURI(illFormed.toWellFormed())); // "https://example.com/search?q=%EF%BF%BD"
     

Hỗ trợ thêm cờ v cho biểu thức chính quy RegExp (Regular Expression)

Biểu thức chính quy (Regular Expression) là một công cụ mạnh mẽ giúp tìm kiếm, thay thế, so khớp chuỗi. Trong ES15 (ES2024), biểu thức chính quy RegExp được bổ sung thêm cờ v để tìm kiếm các ký tự Unicode trong chuỗi.

Để tìm kiếm các ký tự Unicode trong chuỗi, từ phiên bản ES6, ta sử dụng cờ u, nhưng từ phiên bản ES15 này, với sự hỗ trọ của cờ v, ta có thể tìm kiếm với các tính năng nâng cao hơn, hỗ trợ đầy đủ các thuộc tính của Unicode.

  • Có thể dùng các toán tử &&, -- trong character class

Object.groupBy()

Hàm Object.groupBy() giúp nhóm các phần tử của mảng theo một tiêu chí nào đó và trả về một object với key là tiêu chí và value là mảng các phần tử thỏa mãn tiêu chí đó.

const arr = [
  { name: "Alice", age: 21 },
  { name: "Bob", age: 22 },
  { name: "Alice", age: 23 },
  { name: "Bob", age: 24 },
];
 
const grouped = Object.groupBy(arr, (item) => item.name);
console.log(grouped);
// {
//   Alice: [
//     { name: "Alice", age: 21 },
//     { name: "Alice", age: 23 },
//   ],
//   Bob: [
//     { name: "Bob", age: 22 },
//     { name: "Bob", age: 24 },
//   ],
// }

Map.groupBy()

Hàm Map.groupBy() giúp nhóm các phần tử của mảng theo một tiêu chí nào đó và trả về một Map với key là tiêu chí và value là mảng các phần tử thỏa mãn tiêu chí đó.

const arr = [
  { name: "Alice", age: 21 },
  { name: "Bob", age: 22 },
  { name: "Alice", age: 23 },
  { name: "Bob", age: 24 },
];
 
const grouped = Map.groupBy(arr, (item) => item.name);
 
console.log(grouped);
// Map(2) {
//   'Alice' => [
//     { name: "Alice", age: 21 },
//     { name: "Alice", age: 23 },
//   ],
//   'Bob' => [
//     { name: "Bob", age: 22 },
//     { name: "Bob", age: 24 },
//   ],
// }

Promise.withResolvers()

Hàm Promise.withResolvers() trả về 1 object chứa 1 promise và 2 hàm resolve, reject tương ứng. Tương tự như sử dụng new Promise() nhưng ngắn gọn hơn.

// without Promise.withResolvers()
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});
 
// with Promise.withResolvers()
const { promise, resolve, reject } = Promise.withResolvers();