Cách dùng Import / Export Module trong javascript
Trong bài này chúng ta sẽ tìm hiểu module trong javascript, qua đó bạn cũng sẽ biết cách sử dụng hai từ khóa import và export trong javascript.
Khi bạn xây dựng một ứng dụng nhỏ thì việc đặt mã javascript ở đâu, phân chia cấu trúc file như thế nào cho phù hợp, đó là điều mà ít người quan tâm. Nhưng khi dự án của bạn bự lên thì khác, nếu không phân chia cấu trúc thì rất khó cho việc nâng cấp và bảo trì sau này.
Nếu bạn đã từng học qua bộ môn kỹ thuật lập trình cơ bản thì sẽ có khái niệm lập trình hướng module. Đây là kỹ thuật phân chia các file trong dự án một cách khóa học nhất, giúp dự án trở nên sạch sẽ, dễ dàng mở rộng chức năng sau này.
Vậy module là gì mà lại quan trọng đến thế? Và module trong javascript được sử dụng như thế nào? Chúng ta cùng tìm hiểu ngay nhé.
Bài viết này được đăng tại [free tuts .net]
1. Module trong javascript là gì?
Trong javascript, một module là một file javascript bình thường, chỉ đơn giản là file đó sẽ đặt một cái tên có ý nghĩa, và các dòng code bên trong cũng phục vụ cho ý nghĩa đó.
Ví dụ, bạn đang xây dựng ứng dụng quản lý sinh viên thì sẽ tạo ra các module như:
- Module
sinhvien.js
, là tập hợp những lệnh dùng để xử lý sinh viên. - Module
khoa.js
, là tập hợp những dòng lệnh dùng để xử lý các khoa của trường.
Việc phân chia thành từng module (tức là từng file) và mỗi module có một công dụng giúp cho quá trình code nhanh hơn, mạch lạc hơn, nhìn chuyên nghiệp hơn.
Chẳng bạn đang quản lý thư viện, bạn sẽ phân chia sách thành nhiều kệ khác nhau, mỗi kệ là tập hợp những cuốn sách cùng chủ đề. Ví dụ như sách lớp 6, 7, 8, 9 sẽ chia làm 4 kệ khác nhau. Mỗi kệ như vậy ta gọi là một module.
Khái niệm module đã xuất hiện từ lâu, nhưng trong javascript thì nó chỉ được công nhận là có tính năng module này kể từ phiên bản ES5 2015. Trước đây, chúng ta vẫn thường tìm những giải pháp khác để hiện thực hóa module trên javascript, điển hình là các thư viện như Module Pattern, CommonJS, Asynchronous Module Definition (AMD).
2. Khai báo module trong Javascript
Để khai báo và sử dụng module thì ta phải quan tâm đến hai từ khóa, thứ nhất là lệnh import trong js và thứ hai là lệnh export trong js.
Giả sử mình có một file tên là greet.js
chứa đoạn code dưới đây.
// exporting a function export function greetPerson(name) { return `Hello ${name}`; }
Trong đó từ khóa export
dùng để khai báo dữ liệu được chia sẻ, và module khác muốn dùng hàm này thì chỉ việc gọi đến tên hàm là được. Nếu bạn không đặt từ khóa export thì dữ liệu sẽ ở phạm vi cục bộ trong module này.
Ngoài ra, bạn cũng có thể khai báo như sau:
export { greetPerson }; // exporting a function function greetPerson(name) { return `Hello ${name}`; }
Bây giờ, để sử dụng hàm greetPerson
bên trong module greet.js
thì có hai trường hợp.
TH1: Nếu dùng trong một file javascript thì sẽ làm như sau:
// import hàm greetPerson của file greet.js import { greetPerson } from './greet.js'; // Sau đó có thể sử dụng hàm greetPerson() let displayName = greetPerson('Cuong'); console.log(displayName); // Hello Cuong
TH2: Nếu dùng trong file html thì bạn phải khai báo thẻ script
với thuộc tính type="module"
.
<script type="module"> // import hàm greetPerson của file greet.js import { greetPerson } from './greet.js'; // Sau đó có thể sử dụng hàm greetPerson() let displayName = greetPerson('Cuong'); console.log(displayName); // Hello Cuong </script>
Như vậy chúng ta:
- Sử dụng lệnh
import
để gọi đến dữ liệu từ một module khác. - Sử dụng lệnh export để đặt đằng trước hàm hoặc biến cho phép file khác sử dụng.
Ví dụ như đây là một trường hợp lỗi, vì ta đã gọi đến tên dữ liệu không tồn tại.
<script type="module"> // import hàm greetPersonABC của file greet.js import { greetPersonABC } from './greet.js'; </script>
Nếu bạn muốn import nhiều dữ liệu thì hãy đặt chúng cách nhau bởi dấu phẩy.
Ví dụ ta có file module.js
với nội dung như sau:
// exporting variable export const name = 'JavaScript Program'; // exporting function export function sum(x, y) { return x + y; }
Để sử dụng nó trong một file khác thì ta sẽ làm như sau:
import { name, sum } from './module.js'; console.log(name); let add = sum(4, 9); console.log(add); // 13
3. Đổi tên dữ liệu của module trong javascript
Có một số trường hợp bạn phải thay đổi tên cho thuộc tính và phương thức từ module, bởi vì trong chương trình chính đã tồn tại tên như vây, hoặc tên cũ quá dài nên bạn muốn rút gọn lại. Lúc này, tại file chính bạn có thể thay đổi tên để tránh vấn đề trùng tên (conflict name).
Có hai cách để xử lý vấn đề này. Thứ nhất là rename ngay tại module, và thứ hai là rename trong file chương trình chính.
Đổi tên trong module
Để đổi tên dữ liệu nào đó thì trong module bạn hãy khai báo như sau:
// Đổi tên ngay trong module module.js export { function1 as newName1, function2 as newName2 };
Trong đó function1 và function là tên gốc, còn newName1 và newName 2 là tên mới.
Lúc này, trong file chương trình bạn sẽ sử dụng cú pháp như sau:
import { newName1, newName2 } from './module.js';
Ví dụ: Mình sẽ đổi tên cho hàm greetPerson trong module greet.js như sau.
export { greetPerson as person }; // exporting a function function greetPerson(name) { return `Hello ${name}`; }
Lúc này, trong file chương trình ta sẽ gọi như sau:
<script type="module"> // import hàm greetPerson của file greet.js import { Person } from './greet.js'; // Sau đó có thể sử dụng hàm greetPerson() let displayName = Person('Cuong'); console.log(displayName); // Hello Cuong </script>
Đổi tên trong file import
Cách này thường hay sử dụng nhất. Thường thì mỗi người sẽ có một quy tắc khai báo tên biến và tên hàm khác nhau, nên để họ tự đặt lại tên mới là giải pháp tốt nhất.
// Khai báo export hai hàm export { function1, function2 };
Đoạn code trên mình đã khai báo export hai hàm, đó là function1
và function2
.
Bây giờ để sử dụng trong chương trình thì bạn sẽ làm như sau:
// import và đổi tên luôn import { function1 as newName1, function2 as newName2 } from './module.js';
Tóm lại: Để đổi tên dữ liệu trong module thì ta sẽ sử dụng từ khóa as.
4. Default export module trong javascript
Default export là cách khai báo một dữ liệu export mặc định, trong trường gọi đến mà không có thì module sẽ trả về dữ liệu default này.
Ví dụ trong file greet.js
có nội dung như sau:
// default export export default function greet(name) { return `Hello ${name}`; } export const age = 23;
Trong đó hàm greet mình đã thiết lập nó là default export. Lúc này trong file chính sẽ gọi đến một dữ liệu trong module greet.js như sau:
import random_name from './greet.js';
Như bạn thấy, random_name
không hề tồn tại trong module greet.js
. Vì vậy, javascript sẽ lấy default export trả về cho random_name
.
<script type="module"> // import hàm random_name của file greet.js import random_name from './greet.js'; // Sử dụng let displayName = random_name('Cuong'); console.log(displayName); // Hello Cuong </script>
5. Một vài đặc điểm của module trong Javascript
ES6 ra đời đánh dấu một mốc lịch sử rất lớn của ngôn ngữ javascript. Có nhiều tính năng được bổ sung vào mà trước đây ta mơ ước cũng không có, điển hình là các khái niệm về lập trình hướng đối tượng class, extends, static, và module. Sự nâng cấp luôn tạo ra một giá trị mới hữu ích hơn cái cũ.
Module cũng vậy, nó ra đời nhằm giải quyết sự rườm ra của javascript, nên sẽ có những lợi ích sau đây:
Chế độ strict mode
Strict mode là chế độ biên dịch nghiêm ngặt, bắt buộc mọi thứ phải viết theo đúng chuẩn của javascript.
Bình thường thì bạn có thể chạy một chương trình mà trong đó các câu lệnh không có kết thúc bằng dấu hai chấm, hoặc sử dụng một biến mà không cần phải khai báo, .. đó là vì chế độ strict mode chưa được bật.
Khi sử dụng module thì nó rất nghiêm túc trong việc tuân thủ theo quy tắc này. Vì vậy, bạn hãy tuân thủ theo cú pháp mà javascript đưa ra nhé.
Nếu bạn chưa biết strict mode là gì thì xem tại bài viết Strict Mode trong javascript.
Dữ liệu trong các module là riêng biệt
Nếu bạn sử dụng lênh script để import một file mà không thiết lập thuộc tính module thì dữ liệu ở các module có thể sử dụng lẫn nhau. Nhưng khi bạn sử dụng dạng module thì lại khác, dữ liệu ở mỗi module sẽ tách biệt hoàn toàn.
Ví dụ: Sử dụng thẻ script để import module.
<script type="module" src="user.js"></script> <script type="module" src="hello.js"></script>
Lúc này hai file user.js
và hello.js
là riêng biệt, không thể sử dụng dữ liệu của nhau.
Thậm chí khi bạn viết trên cùng một file html thì vẫn không sử dụng được của nhau.
<script type="module"> var a = 12; </script> <script type="module"> alert(a); // biến a chưa được định nghĩa </script>
Chỉ import một lần
Nếu một module đã import rồi, thì nếu những lần sau bạn vẫn tiếp tục import thì nó sẽ không làm gì cả, bởi dữ liệu của module đó đã được load.
Điều này làm tăng tốc độ xử lý, bởi trình biên dịch sẽ không mất thời gian xử lý cái mà nó đã load.
Ví dụ: Giả sử mình có module alert.js
như sau.
alert("Module được tải!");
Bây giờ mình sẽ import ở một file khác.
// 1.js import `./alert.js`; // Module được tải! // 2.js import `./alert.js`; // (không hiển thị gì cả)
Lệnh import thứ hai sẽ không làm gì cả, bởi module alert.js đã được load ở lệnh đầu tiên.
Con trỏ this trong module là undefined
Trong mỗi module, con trỏ this sẽ có giá trị là undefined. Lý do module luôn ở chế độ strict mode, mà ở chế độ này thì this là một biến chưa được khai báo.
Ví dụ dưới đây cho thấy nếu bạn sử dụng this trong module thì giá trị của nó là undefined, còn ở trong thẻ script bình thường thì nó là đối tượng windows.
script> alert(this); // window </script> <script type="module"> alert(this); // undefined </script>
Module tải theo dạng defer
Khi load một module thì nó luôn ở dạng defer, tức giống như bạn đặt thêm thuộc tính defer khi load một file javascript. Load theo dạng defer tức là dữ liệu javascript sẽ được load ngầm trong lúc trình duyệt vẫn đang load mã HTML và CSS.
Tóm lại, quy trình load của module sẽ tuân theo quy tắc sau:
- Module sẽ được load ngầm, không ảnh hưởng đến tốc độ load của HTML.
- Mã script của js sẽ được biên dịch khi trình duyệt đã load xong HTML nên không ảnh hưởng đến hệ thống DOM. Vì vậy, các đoạn mã trong module luôn luôn truy cập đến các đối tượng HTML cho dù bạn đặt ở đầu file hay cuối file.
- Thứ tự load vẫn luôn được bảo toàn, file nào load trước thì sẽ được thực thi trường.
Thiết lập async cho module
Có một số trường hợp bạn phải sử dụng load bất đồng bộ async cho các module, ví dụ load những mã quảng cáo hoặc những chương trình không có sử dụng hệ tài nguyên trên website.
Nói qua một chút về load bất đồng bộ async và defer. Nếu defer là load ngầm và sẽ chờ khi toàn bộ mã HTML của trình duyệt đã load xong thì mới biên dịch module. Còn asycn thì khác, trình biên dịch sẽ load script và load đến đâu sẽ biên dịch đến đó.
Ví dụ dưới đây là mình load một module về analytics theo dạng async.
<script async type="module"> import {counter} from './analytics.js'; counter.count(); </script>
6. Hai cách dùng export trong javascript
Bây giờ mình sẽ nói thêm một chút về cách dùng lệnh export trong javascript nhé.
Đầu tiên, nếu muốn export một hàm hoặc biến thì bạn chỉ cần đặt lệnh export phía trước hàm và biến đó là được.
// export an array export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // export a constant export const MODULES_BECAME_STANDARD_YEAR = 2015; // export a class export class User { constructor(name) { this.name = name; } }
Lưu ý là bạn không cần đặt dấu chấm phẩy đằng sau function hoặc class.
Thứ hai, bạn có thể sử dụng từ khóa export riêng biệt để khai báo dữ liệu.
// say.js function sayHi(user) { alert(`Hello, ${user}!`); } function sayBye(user) { alert(`Bye, ${user}!`); } export {sayHi, sayBye}; // a list of exported variables
Như bạn thấy, mình đã viết hai function bình thường, sau đó đặt nó trong lệnh export.
Thứ ba, bạn có thể đặt lại tên cho dữ liệu ngay lệnh export bằng cách sử dụng từ khóa as.
export {sayHi as hi, sayBye as bye};
7. Hai cách dùng lệnh import trong javascript
Tương tự, chúng ta cũng sẽ có một số cách dùng lệnh import trong js như sau.
Thứ nhất, để import một vài dữ liệu thôi thì ta sử dụng cú pháp sau.
import {sayHi, sayBye} from './say.js';
Các dữ liệu sẽ được ngăn cách bởi dấy phẩy.
Thứ hai, nếu bạn muốn import toàn bộ dữ liệu thì hãy sử dụng dấu sao *.
import * as say from './say.js';
Thứ ba, nếu bạn muốn đổi tên của dữ liệu trong lệnh import thì làm như sau.
import {sayHi as hi, sayBye as bye} from './say.js';
8. Lời kết
Như vậy là chúng ta đã tìm hiểu xong cách sử dụng module trong javascript. Qua bài này bạn cũng học thêm được hai từ khóa, đó là export và import trong js. Đây là bài khá quan trọng, sau này khi học sang các thư viện hoặc framework khác thì gặp lại khái niệm module rất nhiều.