Xuất và Nhập Mô-đun (Module)
ES6 cung cấp cú pháp export và import để chia sẻ mã giữa các tệp. Có hai kiểu xuất chính:
export default– xuất một giá trị mặc định cho mô-đun, phía nhập có thể đặt tên tùy ý.export(có tên) – xuất nhiều giá trị với tên rõ ràng, phía nhập phải dùng đúng tên hoặc dùng alias.
Ví dụ với export default:
// math.js
const PI = 3.14;
export default function circleArea(r) {
return PI * r * r;
}
// main.js
import computeArea from './math.js';
console.log(computeArea(5));
Với export có tên:
// utils.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// main.js
import { add, multiply as mul } from './utils.js';
console.log(add(2, 3), mul(4, 5));
So sánh với CommonJS (Node.js)
Trước đây, Node.js sử dụng CommonJS với module.exports và require(). Đây là hai hệ thống mô-đun khác nhau:
- CommonJS đồng bộ, dùng trong Node.js.
- ES Modules bất đồng bộ tĩnh, hỗ trợ phân tích cây cú pháp tối ưu hơn.
// CommonJS
// logger.js
const prefix = '[APP]';
module.exports = function log(msg) {
console.log(prefix, msg);
};
// app.js
const logger = require('./logger.js');
logger('Khởi tạo');
exports chỉ là tham chiếu đến module.exports, cần thận trọng khi gán lại giá trị.
Đối tượng Reflect
Reflect là đối tượng tích hợp, cung cấp các phương thức tĩnh để thao tác với đối tượng một cách nhất quán, tương tự như Proxy. Nó giúp thay thế một số thao tác truyền thống (ví dụ delete obj[key] ⇒ Reflect.deleteProperty(obj, key)) và trả về giá trị boolean cho biết thành công hay thất bại, giúp code rõ ràng hơn.
const user = { name: 'Lan', age: 25 };
if (Reflect.has(user, 'age')) {
Reflect.deleteProperty(user, 'age');
}
Reflect.set(user, 'city', 'Hà Nội');
console.log(Reflect.ownKeys(user)); // ['name', 'city']
Các vòng lặp: for...in, for...of và for await...of
Mỗi vòng lặp phục vụ mục đích khác nhau:
for...in: duyệt qua tất cả thuộc tính có thể liệt kê của đối tượng, bao gồm cả kế thừa từ prototype. Thường dùng cho đối tượng thông thường.for...of: duyệt qua các phần tử của một iterable (Array, Map, Set, String, ...). Chỉ lấy giá trị của từng phần tử.for await...of: duyệt qua async iterable, chỉ sử dụng được trong hàmasync.
// for...in
const proto = { origin: 'default' };
const obj = Object.create(proto);
obj.name = 'example';
obj.status = 'active';
for (let key in obj) {
console.log(key); // 'name', 'status', 'origin'
}
// for...of
const numbers = [10, 20, 30];
for (let num of numbers) {
console.log(num); // 10, 20, 30
}
// for await...of
async function* generateMessages() {
yield await Promise.resolve('Xin chào');
yield await Promise.resolve('Tạm biệt');
}
(async () => {
for await (let msg of generateMessages()) {
console.log(msg); // 'Xin chào', 'Tạm biệt'
}
})();
Kiểu dữ liệu Symbol
Symbol là một kiểu nguyên thủy (primitive) mới trong ES6. Mỗi giá trị Symbol là duy nhất, không thể trùng lặp, thường làm khóa cho thuộc tính đối tượng nhằm tránh xung đột.
const s1 = Symbol('id');
const s2 = Symbol('id');
console.log(s1 === s2); // false
const person = {
[s1]: 12345,
name: 'Mai'
};
console.log(person[s1]); // 12345
console.log(Object.keys(person)); // ['name'] – bỏ qua thuộc tính Symbol
Symbol trong Registry toàn cục
Phương thức Symbol.for(key) tạo hoặc lấy lại một Symbol từ một registry toàn cục dựa trên khóa. Khác với Symbol(), nó đảm bảo cùng khóa sẽ cùng Symbol.
const globalId1 = Symbol.for('user.id');
const globalId2 = Symbol.for('user.id');
console.log(globalId1 === globalId2); // true
// Lấy khóa ngược
console.log(Symbol.keyFor(globalId1)); // 'user.id'
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
Ảnh hưởng của tính liệt kê (enumerability)
Tính liệt kê của thuộc tính ảnh hưởng đến các phương thức sau:
for...in– lấy mọi thuộc tính liệt kê, kể cả kế thừa.Object.keys()– chỉ trả về các thuộc tính riêng, liệt kê.JSON.stringify()– chỉ serialize các thuộc tính riêng, liệt kê.Object.assign()– sao chép các thuộc tính riêng, liệt kê.Object.getOwnPropertyNames()– trả về tất cả thuộc tính riêng, không phân biệt liệt kê.