Giống như nhiều ngôn ngữ lập trình khác, JavaScript luôn tiến hóa. Mỗi năm, ngôn ngữ này trở nên mạnh mẽ hơn với những tính năng mới cho phép nhà phát triển viết mã nguồn ngắn gọn và thể hiện ý tưởng một cách rõ ràng hơn.
Hãy khám phá những tính năng mới nhất được thêm vào ECMAScript 2022 (ES13), và xem các ví dụ về cách sử dụng để hiểu rõ hơn về chúng.
Nội dung
- ES13 là gì
- 1. Class Field Declarations
- 2. Private Methods and Fields
- 3. await Operator at the Top Level
- 4. Static Class Fields and Static Private Methods
- 5. Class static Block
- 6. Ergonomic Brand Checks for Private Fields
- 7. at() Method for Indexing
- 8. RegExp Match Indices
- 9. Object.hasOwn() Method
- 10. Error Cause
- 11. Array Find from Last
- Phần kết luận
ES13 là gì
ES13 là phiên bản tiếp theo của chuẩn ECMAScript, được công bố vào năm 2022. ES13 cũng được gọi là ECMAScript 2022. Đây là phiên bản mới nhất của ECMAScript tính đến thời điểm hiện tại. Với một số tính năng mới được giới thiệu. Điều này cho phép nhà phát triển viết mã nguồn hiệu quả hơn và tiết kiệm thời gian hơn.
1. Class Field Declarations
Trước khi ra mắt phiên bản ES13, trường dữ liệu của lớp (class fields) chỉ có thể được khai báo trong constructor. Khác với nhiều ngôn ngữ lập trình khác, chúng ta không thể khai báo hoặc định nghĩa chúng ở phạm vi bên ngoài của lớp.
class Car {
constructor() {
this.color = 'blue';
this.age = 2;
}
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2
ES2022 loại bỏ giới hạn này. Bây giờ chúng ta có thể viết mã như thế này:
class Car {
color = 'blue';
age = 2;
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2
2. Private Methods and Fields
Trước đây, không thể khai báo thành viên (member) riêng tư (private) trong một lớp. Thành viên thường được đánh dấu với một dấu gạch dưới (_)
để chỉ ra rằng nó được coi là private, tuy nhiên vẫn có thể truy cập và sửa đổi từ bên ngoài lớp.
class Person {
_firstName = 'Joseph';
_lastName = 'Stevens';
get name() {
return `${this._firstName} ${this._lastName}`;
}
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// Members intended to be private can still be accessed
// from outside the class
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// They can also be modified
person._firstName = 'Robert';
person._lastName = 'Becker';
console.log(person.name); // Robert Becker
Với ES13, chúng ta hiện có thể thêm các trường và thành viên riêng tư vào một lớp bằng cách đặt ký hiệu hashtag (#)
vào trước tên của chúng. Việc cố gắng truy cập chúng từ bên ngoài lớp sẽ gây ra lỗi.
class Person {
#firstName = 'Joseph';
#lastName = 'Stevens';
get name() {
return `${this.#firstName} ${this.#lastName}`;
}
}
const person = new Person();
console.log(person.name);
// SyntaxError: Private field '#firstName' must be
// declared in an enclosing class
console.log(person.#firstName);
console.log(person.#lastName);
Lưu ý rằng lỗi được ném ra ở đây là một lỗi cú pháp (syntax error), xảy ra tại thời điểm biên dịch, do đó không có phần nào của mã chạy. Trình biên dịch không mong đợi bạn thậm chí cố gắng truy cập các trường riêng tư từ bên ngoài một lớp, vì vậy nó giả định rằng bạn đang cố khai báo một trường mới.
3. await Operator at the Top Level
Trong JavaScript, toán tử await
được sử dụng để tạm dừng thực thi cho đến khi một Promise
được giải quyết (thực hiện hoặc bị từ chối).
Trước đó, chúng ta chỉ có thể sử dụng toán tử này trong một hàm async
– một hàm được khai báo với từ khóa async
. Chúng ta không thể làm điều này ở phạm vi toàn cục.
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
// SyntaxError: await is only valid in async functions
await setTimeoutAsync(3000);
Với ES2022, bây giờ chúng ta có thể:
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
// Waits for timeout - no error thrown
await setTimeoutAsync(3000);
4. Static Class Fields and Static Private Methods
Chúng ta có thể khai báo các trường tĩnh và phương thức tĩnh riêng cho một lớp trong ES13. Các phương thức tĩnh có thể truy cập các thành viên tĩnh riêng hoặc công khai khác trong lớp bằng từ khóa this
, và các phương thức của thể hiện có thể truy cập chúng bằng this.constructor
.
class Person {
static #count = 0;
static getCount() {
return this.#count;
}
constructor() {
this.constructor.#incrementCount();
}
static #incrementCount() {
this.#count++;
}
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount());// 2
5. Class static Block
ES13 cho phép định nghĩa các khối static
sẽ được thực thi chỉ một lần, tại thời điểm tạo lớp. Điều này tương tự như các constructor static trong các ngôn ngữ hỗ trợ lập trình hướng đối tượng khác, như C # và Java.
Một lớp có thể có bất kỳ số lượng khối khởi tạo static {}
nào trong thân lớp của nó. Chúng sẽ được thực thi, cùng với bất kỳ trình khởi tạo trường tĩnh xen kẽ, theo thứ tự chúng được khai báo. Chúng ta có thể sử dụng thuộc tính super
trong khối static
để truy cập các thuộc tính của lớp cha.
class Vehicle {
static defaultColor = 'blue';
}
class Car extends Vehicle {
static colors = [];
static {
this.colors.push(super.defaultColor, 'red');
}
static {
this.colors.push('green');
}
}
console.log(Car.colors);// [ 'blue', 'red', 'green' ]
6. Ergonomic Brand Checks for Private Fields
Chúng ta có thể sử dụng tính năng mới của ES2022 này để kiểm tra xem một đối tượng có chứa một trường riêng tư cụ thể hay không, bằng cách sử dụng toán tử in
.
class Car {
#color;
hasColor() {
return #color in this;
}
}
const car = new Car();
console.log(car.hasColor());// true;
Toán tử in
có khả năng phân biệt các trường riêng tư có cùng tên từ các lớp khác nhau:
class Car {
#color;
hasColor() {
return #color in this;
}
}
class House {
#color;
hasColor() {
return #color in this;
}
}
const car = new Car();
const house = new House();
console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false
7. at() Method for Indexing
Thông thường trong JavaScript, chúng ta sử dụng dấu ngoặc vuông ([]
) để truy cập vào phần tử thứ N
của một mảng, thao tác này thường đơn giản. Chúng ta chỉ cần truy cập vào thuộc tính N - 1
của mảng.
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]);// b
Tuy nhiên, chúng ta phải sử dụng chỉ số là arr.length - N
nếu muốn truy cập vào phần tử thứ N
từ cuối mảng bằng cách sử dụng dấu ngoặc vuông.
const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr[arr.length - 1]); // d
// 2nd element from the end
console.log(arr[arr.length - 2]); // c
ES2022 đã thêm phương thức at()
cho phép chúng ta truy cập phần tử thứ N
của một mảng, chuỗi hoặc TypedArray
một cách ngắn gọn hơn. Để truy cập phần tử thứ N
từ cuối của mảng, chúng ta chỉ cần truyền giá trị âm -N
vào phương thức at()
.
Ví dụ:
const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr.at(-1)); // d
// 2nd element from the end
console.log(arr.at(-2)); // c
Ngoài mảng, chuỗi và đối tượng TypedArray
cũng có các phương thức at()
tương tự.
const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // t
const typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48
8. RegExp Match Indices
Tính năng mới này cho phép chúng ta chỉ định rằng chúng ta muốn lấy cả vị trí bắt đầu và kết thúc của các đối tượng kết hợp của một đối tượng RegExp
trong một chuỗi đã cho.
Trước đây, chúng ta chỉ có thể lấy vị trí bắt đầu của một kết hợp regex trong một chuỗi.
const str = 'sun and moon';
const regex = /and/;
const matchObj = regex.exec(str);
// [ 'and', index: 4, input: 'sun and moon', groups: undefined ]
console.log(matchObj);
Bây giờ với ES13, chúng ta có thể chỉ định một biểu thức chính quy với cờ d
để lấy cả hai chỉ số bắt đầu và kết thúc của các kết quả phù hợp trong chuỗi đã cho.
const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
/**
[
'and',
index: 4,
input: 'sun and moon',
groups: undefined,
indices: [ [ 4, 7 ], groups: undefined ]
]
*/
console.log(matchObj);
Với cờ d
được thiết lập, đối tượng trả về sẽ có một thuộc tính indices
chứa các chỉ mục bắt đầu và kết thúc.
9. Object.hasOwn() Method
Trong JavaScript, chúng ta có thể sử dụng phương thức Object.prototype.hasOwnProperty()
để kiểm tra một object có chứa một thuộc tính cụ thể hay không.
class Car {
color = 'green';
age = 2;
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false
Tuy nhiên, có một số vấn đề với phương pháp này. Một vấn đề là phương thức Object.prototype.hasOwnProperty()
không được bảo vệ – nó có thể bị ghi đè bằng cách định nghĩa một phương thức hasOwnProperty()
tùy chỉnh cho một lớp, điều này có thể có hành vi hoàn toàn khác với Object.prototype.hasOwnProperty()
.
class Car {
color = 'green';
age = 2;
// This method does not tell us whether an object of
// this class has a given property.
hasOwnProperty() {
return false;
}
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false
Một vấn đề khác là với các đối tượng được tạo với nguyên mẫu là null
(sử dụng Object.create(null)
), thử gọi phương thức này sẽ gây ra lỗi.
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));
Một cách để giải quyết những vấn đề này là sử dụng phương thức call()
trên thuộc tính Function
Object.prototype.hasOwnProperty
, như sau:
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false
Điều này không thuận tiện. Chúng ta có thể viết một hàm tái sử dụng của riêng mình để tránh lặp lại việc này:
function objHasOwnProp(obj, propertyKey) {
return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false
Không cần phải làm như vậy nữa, vì chúng ta có thể sử dụng phương thức tích hợp mới được thêm vào trong ES2022, đó là Object.hasOwn()
. Tương tự như hàm có thể tái sử dụng của chúng ta, nó lấy đối tượng và thuộc tính làm đối số và trả về true
nếu thuộc tính được chỉ định là thuộc tính trực tiếp của đối tượng. Nếu không, nó trả về false
.
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false
10. Error Cause
Đối tượng lỗi trong JavaScript bây giờ có thuộc tính cause
để chỉ định lỗi gốc gây ra lỗi sắp bị ném. Điều này cung cấp thêm thông tin ngữ cảnh cho lỗi và hỗ trợ chẩn đoán hành vi không mong muốn. Chúng ta có thể chỉ định nguyên nhân của một lỗi bằng cách thiết lập thuộc tính cause
trên một đối tượng được truyền như là đối số thứ hai cho hàm Error()
.
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
throw new Error('New error message', { cause: err });
}
}
try {
userAction();
} catch (err) {
console.log(err);
console.log(`Cause by: ${err.cause}`);
}
11. Array Find from Last
Trong JavaScript, chúng ta đã có thể sử dụng phương thức Array find()
để tìm một phần tử trong một mảng thoả điều kiện kiểm tra được chỉ định. Tương tự, chúng ta có thể sử dụng findIndex()
để tìm chỉ mục của một phần tử như vậy. Trong khi find()
và findIndex()
đều bắt đầu tìm kiếm từ phần tử đầu tiên của mảng, có các trường hợp nơi nó sẽ tốt hơn nếu bắt đầu tìm kiếm từ phần tử cuối cùng.
Có các tình huống nơi chúng ta biết rằng tìm kiếm từ phần tử cuối cùng có thể đạt được hiệu suất tốt hơn. Ví dụ: ở đây, chúng ta đang cố gắng lấy phần tử trong mảng có thuộc tính value
bằng y
. Với find()
và findIndex()
:
const letters = [
{ value: 'v' },
{ value: 'w' },
{ value: 'x' },
{ value: 'y' },
{ value: 'z' },
];
const found = letters.find((item) => item.value === 'y');
const foundIndex = letters.findIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3
Chức năng này hoạt động tốt, nhưng vì đối tượng cần tìm kiếm gần với cuối mảng, chúng ta có thể cải thiện hiệu suất của chương trình bằng cách sử dụng các phương thức findLast()
và findLastIndex()
mới được thêm vào trong ES2022 để tìm kiếm mảng từ cuối.
const letters = [
{ value: 'v' },
{ value: 'w' },
{ value: 'x' },
{ value: 'y' },
{ value: 'z' },
];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3
Một use case khác có thể yêu cầu chúng ta tìm kiếm mảng từ cuối để lấy phần tử chính xác. Ví dụ, nếu chúng ta muốn tìm số chẵn cuối cùng trong danh sách các số, find()
và findIndex()
sẽ cho ra một kết quả hoàn toàn sai:
const nums = [7, 14, 3, 8, 10, 9];
// gives 14, instead of 10
const lastEven = nums.find((value) => value % 2 === 0);
// gives 1, instead of 4
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);
console.log(lastEven); // 14
console.log(lastEvenIndex); // 1
Chúng ta có thể gọi phương thức reverse()
trên mảng để đảo ngược thứ tự các phần tử trước khi gọi find()
và findIndex()
. Tuy nhiên, phương pháp này sẽ gây ra sự biến đổi không cần thiết của mảng, vì reverse()
đảo ngược các phần tử của một mảng tại chỗ. Cách duy nhất để tránh biến đổi này là tạo một bản sao mới của toàn bộ mảng, điều này có thể gây ra vấn đề về hiệu suất đối với các mảng lớn.
Hơn nữa, findIndex()
sẽ vẫn không hoạt động trên mảng đã đảo ngược, vì đảo ngược các phần tử sẽ đồng nghĩa với việc thay đổi chỉ mục chúng có trong mảng ban đầu. Để lấy chỉ mục ban đầu, chúng ta sẽ cần thực hiện một phép tính bổ sung, điều này có nghĩa là phải viết thêm mã.
const nums = [7, 14, 3, 8, 10, 9];
// Copying the entire array with the spread syntax before
// calling reverse()
const reversed = [...nums].reverse();
// correctly gives 10
const lastEven = reversed.find((value) => value % 2 === 0);
// gives 1, instead of 4
const reversedIndex = reversed.findIndex((value) => value % 2 === 0);
// Need to re-calculate to get original index
const lastEvenIndex = reversed.length - 1 - reversedIndex;
console.log(lastEven); // 10
console.log(reversedIndex); // 1
console.log(lastEvenIndex); // 4
Đó là khi những phương thức findLast()
và findLastIndex()
trở nên hữu ích.
const nums = [7, 14, 3, 8, 10, 9];
const lastEven = nums.findLast((num) => num % 2 === 0);
const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0);
console.log(lastEven); // 10
console.log(lastEvenIndex); // 4
Mã này ngắn hơn và dễ đọc hơn. Quan trọng nhất, nó tạo ra kết quả chính xác.
Phần kết luận
Vậy là chúng ta đã xem qua những tính năng mới nhất mà ES13 (ES2022) mang đến cho JavaScript. Hãy sử dụng chúng để tăng năng suất làm việc và viết mã nguồn sạch hơn, ngắn gọn và rõ ràng hơn nhé.
Nguồn bài viết: https://codingbeautydev.com/blog/es13-javascript-features/
Xem thêm: