Bài 6.3: Template literals và modules trong JS

Bài 6.3: Template literals và modules trong JS
Chào mừng bạn đến với một bài học thú vị khác trong hành trình chinh phục Lập trình Web Front-end cùng FullhouseDev! Hôm nay, chúng ta sẽ cùng đào sâu vào hai tính năng cực kỳ hữu ích và hiện đại của JavaScript (từ phiên bản ES6 trở lên): Template Literals giúp chúng ta làm việc với chuỗi một cách thanh lịch hơn bao giờ hết, và Modules là cứu cánh cho bài toán tổ chức code JavaScript quy mô lớn, tránh xung đột và dễ dàng quản lý các phần phụ thuộc.
Hãy cùng bắt đầu nhé!
1. Template Literals: Làm việc với chuỗi chưa bao giờ dễ dàng đến thế!
Trước khi ES6 ra đời, việc kết hợp biến và chuỗi trong JavaScript thường khá lộn xộn và khó đọc, đặc biệt với các chuỗi dài hoặc có nhiều biến. Chúng ta phải dùng phép nối chuỗi +
liên tục.
Hãy xem ví dụ cũ:
let ten = "Nguyễn Văn A";
let tuoi = 30;
// Cách truyền thống (pre-ES6)
let thongBaoCu = "Chào bạn, tôi là " + ten + " và tôi " + tuoi + " tuổi.";
console.log(thongBaoCu);
Nhìn khá rắc rối phải không? Đặc biệt nếu bạn có nhiều biến hơn hoặc cần thêm các ký tự đặc biệt như dấu nháy kép, bạn sẽ phải làm thêm các bước escape ký tự (\"
).
Chào mừng đến với Template Literals!
Template Literals cho phép bạn định nghĩa chuỗi bằng cặp dấu backticks (``
) thay vì dấu nháy đơn (''
) hoặc nháy kép (""
). Điều kỳ diệu xảy ra bên trong cặp backticks này:
1.1. Nội suy biểu thức (Expression Interpolation)
Bạn có thể nhúng trực tiếp các biến, biểu thức hoặc thậm chí là các lời gọi hàm vào trong chuỗi bằng cú pháp ${ }
. JavaScript sẽ tự động đánh giá biểu thức bên trong ${}
và chuyển đổi kết quả thành chuỗi để chèn vào vị trí đó.
Hãy xem lại ví dụ trên với Template Literals:
let ten = "Nguyễn Văn A";
let tuoi = 30;
// Sử dụng Template Literals (ES6+)
let thongBaoMoi = `Chào bạn, tôi là ${ten} và tôi ${tuoi} tuổi.`;
console.log(thongBaoMoi);
Giải thích:
- Chúng ta dùng dấu backticks
``
để bao quanh toàn bộ chuỗi. - Bên trong chuỗi, thay vì dùng
+
, chúng ta dùng${ten}
và${tuoi}
. JavaScript sẽ tự động lấy giá trị của biếnten
vàtuoi
thay thế vào vị trí này. - Code trở nên ngắn gọn và dễ đọc hơn rất nhiều.
Không chỉ biến, bạn có thể nhúng cả biểu thức:
let gia = 100;
let giamGia = 0.1; // 10%
let tinNhan = `Giá sau giảm giá là ${gia * (1 - giamGia)} VNĐ.`;
console.log(tinNhan); // Output: Giá sau giảm giá là 90 VNĐ.
Giải thích:
- Bên trong
${}
, chúng ta đặt một biểu thức toán học:gia * (1 - giamGia)
. - JavaScript tính toán kết quả của biểu thức này (100 * 0.9 = 90) và chèn giá trị
90
vào chuỗi.
1.2. Chuỗi đa dòng (Multi-line Strings)
Một vấn đề đau đầu khác với chuỗi truyền thống là tạo chuỗi có nhiều dòng. Bạn phải dùng ký tự xuống dòng \n
hoặc nối nhiều chuỗi lại với nhau.
// Cách truyền thống tạo chuỗi đa dòng
let diaChiCu = "Đường A, Số nhà B\n" +
"Phường C, Quận D\n" +
"Thành phố E.";
console.log(diaChiCu);
Với Template Literals, bạn chỉ việc gõ chuỗi trên nhiều dòng như bình thường!
// Sử dụng Template Literals tạo chuỗi đa dòng
let diaChiMoi = `Đường A, Số nhà B
Phường C, Quận D
Thành phố E.`;
console.log(diaChiMoi);
Giải thích:
- JavaScript nhận diện và giữ nguyên các ký tự xuống dòng (
Enter
) mà bạn gõ bên trong cặp backticks. - Điều này cực kỳ hữu ích khi bạn cần tạo các đoạn văn bản dài, email, hoặc các cấu trúc HTML/CSS nhúng trong JavaScript.
1.3. Nhúng dấu ngoặc kép và dấu nháy đơn dễ dàng
Một lợi ích nhỏ nhưng tiện lợi của Template Literals là bạn không cần phải escape dấu nháy đơn ('
) hay dấu ngoặc kép ("
) nữa, miễn là chúng khác với ký tự bao quanh chuỗi (ở đây là backticks).
let cauNoi = `"Anh ấy nói: 'Xin chào!' một cách thân thiện."`; // Vẫn dùng nháy kép/đơn
console.log(cauNoi);
let cauNoiTL = `"Anh ấy nói: 'Xin chào!' một cách thân thiện."`; // Template Literals cũng làm được, không cần escape
console.log(cauNoiTL);
let viDuKhac = `Thật tuyệt khi dùng backticks \` trong chuỗi của bạn!`; // Chỉ cần escape backtick nếu muốn dùng nó trong chuỗi backtick
console.log(viDuKhac);
Template Literals không chỉ giúp code của bạn dễ đọc hơn mà còn giảm thiểu lỗi khi làm việc với chuỗi phức tạp. Chúng là một công cụ thiết yếu trong JavaScript hiện đại.
2. Modules: Tổ chức code JavaScript một cách chuyên nghiệp
Khi ứng dụng JavaScript của bạn lớn dần, việc đặt tất cả code vào một file duy nhất hoặc sử dụng các kỹ thuật cũ như IIFE (Immediately Invoked Function Expression) để mô phỏng module sẽ trở nên khó khăn và bất tiện. Các vấn đề như xung đột tên biến/hàm (global scope pollution) hay quản lý sự phụ thuộc giữa các phần code khác nhau sẽ xuất hiện.
Modules trong JavaScript (chuẩn ES Modules, ra mắt từ ES6) ra đời để giải quyết triệt để những vấn đề này. Chúng cho phép bạn:
- Chia code thành các file nhỏ hơn, có ý nghĩa: Mỗi file (module) tập trung vào một nhiệm vụ cụ thể.
- Tạo phạm vi riêng (scope) cho từng module: Các biến, hàm bên trong một module mặc định không truy cập được từ bên ngoài, tránh xung đột tên.
- Công khai (export) những phần cần thiết: Bạn chỉ xuất ra ngoài những biến, hàm, lớp mà các module khác cần sử dụng.
- Sử dụng (import) các thành phần từ module khác: Bạn nhập vào module hiện tại những gì đã được xuất từ module khác.
- Quản lý phụ thuộc rõ ràng: Dễ dàng biết module nào phụ thuộc vào module nào.
Hãy xem cú pháp export
và import
.
2.1. Export: Công khai những gì module cung cấp
Bạn sử dụng từ khóa export
để "công khai" các biến, hàm, lớp, hằng số... từ module của mình ra bên ngoài.
Ví dụ: Tạo một file utils.js
chứa các hàm tiện ích:
// utils.js
export const PI = 3.14159;
export function tinhTong(a, b) {
return a + b;
}
export class Nguoi {
constructor(ten) {
this.ten = ten;
}
xinChao() {
console.log(`Xin chào, tôi là ${this.ten}`);
}
}
// Bạn cũng có thể export nhiều thứ cùng lúc ở cuối file
const hangSoKhac = 100;
function hamKhac() { /* ... */ }
export { hangSoKhac, hamKhac };
Giải thích:
- Chúng ta dùng
export
trước các khai báo (const
,function
,class
) để biến chúng thành "named exports" (các mục được xuất có tên). - Cú pháp
export { item1, item2 }
cho phép export một danh sách các mục đã được định nghĩa trước đó trong file.
2.2. Import: Sử dụng những gì module khác cung cấp
Bạn sử dụng từ khóa import
để "nhập" các thành phần đã được export từ module khác vào module hiện tại.
Ví dụ: Sử dụng các tiện ích từ utils.js
trong một file khác, ví dụ main.js
:
// main.js
import { PI, tinhTong, Nguoi, hangSoKhac } from './utils.js';
console.log(PI); // Output: 3.14159
let ketQua = tinhTong(5, 7);
console.log(ketQua); // Output: 12
let teo = new Nguoi("Tèo");
teo.xinChao(); // Output: Xin chào, tôi là Tèo
console.log(hangSoKhac); // Output: 100
// Không thể truy cập hamKhac nếu không import nó
// hamKhac(); // Lỗi: hamKhac is not defined
Giải thích:
- Cú pháp
import { item1, item2 } from './path/to/module.js';
dùng để nhập các named exports. - Tên các mục trong dấu ngoặc nhọn
{}
phải trùng khớp với tên khiexport
. './utils.js'
là đường dẫn đến file module cần nhập. Đường dẫn có thể là tương đối (./
,../
) hoặc tuyệt đối (với các module Node.js hoặc các thư viện được cấu hình đặc biệt).
2.3. Export Default: Một module, một "đại diện" chính
Ngoài named exports, mỗi module có thể có duy nhất một export mặc định (default export). Điều này thường dùng để xuất ra thứ được coi là "quan trọng nhất" hoặc là "chức năng chính" của module đó (ví dụ: một lớp React Component, một hàm tạo chính, một object cấu hình...).
Bạn sử dụng export default
.
Ví dụ: Tạo file math.js
với một hàm tính trung bình là default export:
// math.js
export const motNua = 0.5; // Named export
const tinhTrungBinh = (arr) => {
if (arr.length === 0) return 0;
const tong = arr.reduce((sum, num) => sum + num, 0);
return tong / arr.length;
};
export default tinhTrungBinh; // Default export
Giải thích:
export default tinhTrungBinh;
xuất hàmtinhTrungBinh
làm default export. Lưu ý không cần dùng{}
.- Bạn có thể export default một biến, hàm, lớp, object, array...
2.4. Import Default: Nhập đại diện chính của module
Khi import default export, bạn không cần dùng dấu ngoặc nhọn {}
và có thể đặt cho nó bất kỳ tên nào bạn muốn.
Ví dụ: Sử dụng math.js
trong main.js
:
// main.js
import trungBinhCong from './math.js'; // Import default export và đặt tên là 'trungBinhCong'
import { motNua } from './math.js'; // Import named export
const soLieu = [10, 20, 30, 40];
const ketQuaTB = trungBinhCong(soLieu);
console.log(`Trung bình: ${ketQuaTB}`); // Output: Trung bình: 25
console.log(`Một nửa: ${motNua}`); // Output: Một nửa: 0.5
// Bạn có thể import cả default và named export cùng lúc
import defaultExport, { namedExport1, namedExport2 } from './module.js';
Giải thích:
import trungBinhCong from './math.js';
nhập default export từmath.js
và gọi nó làtrungBinhCong
. TêntrungBinhCong
ở đây hoàn toàn do bạn quyết định.import { motNua } from './math.js';
vẫn nhập named exportmotNua
như bình thường.
2.5. Import mọi thứ (Namespace Import)
Trong một số trường hợp, bạn muốn nhập tất cả những gì một module export (cả named và default) vào một object duy nhất. Bạn dùng cú pháp import * as tenAlias from './module.js'
.
// main.js
import * as utilities from './utils.js';
console.log(utilities.PI); // Truy cập named export PI
console.log(utilities.tinhTong(2, 3)); // Truy cập named export tinhTong
// Default export (nếu có trong utils.js) sẽ nằm dưới key 'default' của object alias
// import * as mathModule from './math.js';
// console.log(mathModule.default([1, 2, 3])); // Truy cập default export
// console.log(mathModule.motNua); // Truy cập named export
Giải thích:
import * as utilities from './utils.js';
tạo một objectutilities
chứa tất cả các named exports từutils.js
dưới dạng các thuộc tính.- Cách này giúp tránh xung đột tên nếu bạn nhập nhiều thứ từ nhiều module khác nhau.
2.6. Dynamic Import (Nhập động)
Các import
thông thường là static, nghĩa là chúng được xử lý khi code được tải lên (parse time). Đôi khi, bạn muốn tải một module chỉ khi cần thiết (ví dụ: khi người dùng click vào một nút, hoặc khi một điều kiện nào đó xảy ra). Đây gọi là dynamic import.
Cú pháp là import('./path/to/module.js')
. Nó trả về một Promise.
// main.js
const nutLoadModule = document.getElementById('load-module');
nutLoadModule.addEventListener('click', async () => {
try {
// Dynamic import trả về một Promise
const mathModule = await import('./math.js');
const soLieu = [1, 2, 3, 4, 5];
const avg = mathModule.default(soLieu); // Truy cập default export qua .default
const half = mathModule.motNua; // Truy cập named export
console.log(`Kết quả trung bình sau khi load động: ${avg}`);
console.log(`Một nửa từ module load động: ${half}`);
} catch (error) {
console.error("Lỗi khi tải module:", error);
}
});
Giải thích:
- Hàm
import()
được gọi bên trong một sự kiện click, nghĩa là modulemath.js
chỉ được tải khi nút được click. - Chúng ta dùng
await
(trong một hàmasync
) hoặc.then()
để xử lý Promise trả về. - Object
mathModule
chứa tất cả các exports của module. Default export nằm dưới keydefault
, named exports là các thuộc tính khác.
Dynamic import rất hữu ích cho việc lazy loading (tải theo nhu cầu), giúp giảm thời gian tải ban đầu của ứng dụng.
2.7. Sử dụng Modules trong HTML
Để trình duyệt hiểu và xử lý cú pháp import
/export
, bạn cần thêm thuộc tính type="module"
vào thẻ <script>
của mình:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sử dụng Modules JS</title>
</head>
<body>
<h1>Ví dụ về Modules</h1>
<button id="load-module">Load Module Động</button>
<!-- File script chính của bạn sử dụng modules -->
<script type="module" src="./main.js"></script>
<!-- Các script thông thường (không phải module) -->
<!-- <script src="./other-script.js"></script> -->
</body>
</html>
Lưu ý quan trọng:
- Các file
.js
sử dụngimport
hoặcexport
cần được đặt ở chế độ module bằngtype="module"
. - Module chạy ở chế độ nghiêm ngặt (strict mode) theo mặc định.
- Modules thường được tải defer theo mặc định (không chặn parse HTML), tương tự như
<script defer>
. - Đường dẫn trong
import
(ví dụ:./utils.js
) phải chính xác và phân biệt chữ hoa chữ thường trên một số hệ điều hành. - Module chỉ hoạt động khi chạy trên một máy chủ web (ví dụ: sử dụng Live Server trong VS Code, hoặc deploy lên một host). Mở file HTML trực tiếp bằng
file://
có thể gặp lỗi liên quan đến CORS khi tải các module con.
Template Literals và Modules là hai bước tiến lớn trong sự phát triển của JavaScript, giúp code trở nên sạch sẽ hơn, dễ bảo trì hơn và có cấu trúc tốt hơn. Nắm vững chúng là điều cần thiết để xây dựng các ứng dụng web hiện đại!
Chúc mừng bạn đã hoàn thành bài học này! Hãy thực hành viết code sử dụng Template Literals và Modules để làm quen với cú pháp và cảm nhận sự tiện lợi của chúng nhé!
Comments