Node.js đã trở thành một trong những nền tảng phổ biến nhất cho phát triển backend nhờ vào tính năng mạnh mẽ, hiệu suất cao và khả năng mở rộng. Để chuẩn bị tốt nhất cho các buổi phỏng vấn liên quan đến Node.js, bạn cần nắm vững cả kiến thức cơ bản và nâng cao.
Series này tổng hợp 126 câu hỏi phỏng vấn Node.js thường gặp, được thiết kế để giúp bạn tự tin đối mặt với mọi thử thách. Từ các khái niệm nền tảng như sự kiện (event), luồng (stream), và middleware, đến các vấn đề nâng cao như xử lý bất đồng bộ (asynchronous handling) hay bảo mật API…
1. Npm dùng để làm gì?
npm là viết tắt của Node Package Manager. npm cung cấp hai chức năng chính sau:
- Kho lưu trữ trực tuyến cho các gói/mô-đun Node.js, có thể tìm kiếm tại search.nodejs.org.
- Command-line để cài đặt packages, quản lý version và dependency của các packages Node.js.
Một công dụng quan trọng khác của npm là dependency management. Khi bạn có một dự án Node.js với tệp package.json, chỉ cần chạy lệnh npm install từ thư mục gốc của dự án, npm sẽ tự động cài đặt tất cả các dependencies được liệt kê trong tệp package.json.
2. Sự khác biệt giữa process.cwd() với __dirname?
- cwd là một phương thức của đối tượng toàn cục process, trả về một giá trị chuỗi đại diện cho thư mục làm việc hiện tại của tiến trình Node.js.
- __dirname là tên thư mục của script hiện tại, được trả về dưới dạng một giá trị chuỗi. __dirname không thực sự là một đối tượng toàn cục mà chỉ khả dụng cục bộ trong từng module.
Xem xét cấu trúc dự án sau:
Project
├── main.js
└──lib
└── script.js
Giả sử chúng ta có một tệp script.js bên trong thư mục con của dự án, tức là C:/Project/lib/script.js và chạy node main.js yêu cầu script.js
main.js
require('./lib/script.js')
console.log(process.cwd())
// C:\Project
console.log(__dirname)
// C:\Project
console.log(__dirname === process.cwd())
// true
script.js
console.log(process.cwd())
// C:\Project
console.log(__dirname)
// C:\Project\lib
console.log(__dirname === process.cwd())
// false
3. File package.json là gì?
Trong các dự án Node.js, hầu hết đều có một tệp quan trọng gọi là package.json, thường được đặt ở thư mục gốc của dự án. Tệp này chứa thông tin quan trọng về dự án và được npm sử dụng để quản lý các thư viện, gói phần mềm mà dự án sử dụng.
Cụ thể, package.json giúp:
- Liệt kê các thư viện phụ thuộc (dependencies) mà dự án cần
- Cung cấp thông tin mô tả về dự án
- Xác định phiên bản của dự án
- Chỉ định giấy phép sử dụng
- Định nghĩa các script để chạy dự án
Tệp package.json là “hộ chiếu” quan trọng của mỗi dự án Node.js, giúp npm hiểu và quản lý dự án một cách chính xác.
4. Nêu các đối tượng toàn cục(globals) tích hợp sẵn trong Node.js
Node.js có một số global identifiers (định danh toàn cục) tích hợp sẵn mà bất kỳ lập trình viên Node.js nào cũng nên biết. Một số là globals thực sự, khả dụng ở mọi nơi; số khác chỉ tồn tại ở cấp module nhưng có trong mọi module, vì vậy được gọi là pseudo-globals.
Danh sách true globals
- global – Namespace toàn cục. Việc gán thuộc tính vào namespace này sẽ khiến nó khả dụng trên toàn bộ tiến trình đang chạy.
- process – Module process tích hợp của Node.js, cung cấp các phương thức để tương tác với tiến trình Node.js hiện tại.
- console – Module console tích hợp, cung cấp các chức năng STDIO giống như trên trình duyệt.
- setTimeout(), clearTimeout(), setInterval(), clearInterval() – Các hàm quản lý bộ đếm thời gian tích hợp sẵn.
Danh sách các pseudo-globals có ở cấp module trong mọi module:
- module, module.exports, exports – Các đối tượng liên quan đến hệ thống module của Node.js.
- __filename – Chứa đường dẫn của file đang được thực thi. Lưu ý, không khả dụng khi chạy trong REPL của Node.js.
- __dirname – Chứa đường dẫn đến thư mục gốc của script đang thực thi, tương tự __filename. Cũng không khả dụng trong REPL của Node.js.
- require() – Hàm tích hợp, khả dụng ở mỗi module, dùng để import các module hợp lệ khác.
5. Bạn hiểu thế nào về Asynchronous APIs?
Tất cả các API trong thư viện Node.js đều là bất đồng bộ (asynchronous), tức là không chặn (non-blocking). Điều này có nghĩa là một máy chủ dựa trên Node.js sẽ không bao giờ chờ đợi một API trả về dữ liệu. Máy chủ sẽ chuyển sang gọi API tiếp theo sau khi gọi API hiện tại, và cơ chế thông báo qua Events của Node.js giúp máy chủ nhận phản hồi từ cuộc gọi API trước đó.
6. Lợi ích của việc sử dụng node.js là gì?
Các ưu điểm chính của việc sử dụng Node.js:
- Hiệu suất cao (High Performance)
Node.js sử dụng V8 JavaScript Engine của Google, giúp biên dịch và thực thi mã JavaScript rất nhanh.
Event-driven và non-blocking I/O giúp các ứng dụng Node.js xử lý hàng nghìn yêu cầu đồng thời mà không bị chặn, điều này cực kỳ hiệu quả đối với các ứng dụng có lượng truy cập lớn hoặc yêu cầu khả năng phản hồi nhanh. - Khả năng mở rộng (Scalability)
Node.js hỗ trợ mô hình single-threaded event loop, giúp ứng dụng xử lý nhiều kết nối mà không tạo ra các tài nguyên hệ thống quá tải.
Nó cũng dễ dàng mở rộng theo chiều ngang (scaling horizontally) khi cần tăng số lượng kết nối và xử lý nhiều yêu cầu đồng thời. - Xử lý đồng thời (Concurrency)
Với cơ chế non-blocking asynchronous I/O, Node.js có thể xử lý nhiều tác vụ đồng thời mà không làm chậm lại ứng dụng. Điều này giúp tăng hiệu suất của ứng dụng, đặc biệt khi cần xử lý các tác vụ dài hoặc nhiều I/O như đọc/ghi file hoặc truy vấn cơ sở dữ liệu. - Sử dụng JavaScript ở cả client và server
Với Node.js, bạn có thể sử dụng JavaScript ở cả phía client và server, giúp đơn giản hóa quy trình phát triển, cải thiện hiệu quả giao tiếp giữa các phần của ứng dụng, và giảm chi phí phát triển khi sử dụng chung một ngôn ngữ lập trình. - Thư viện và mô-đun phong phú
npm (Node Package Manager) là kho lưu trữ khổng lồ với hàng triệu gói thư viện mã nguồn mở, giúp lập trình viên dễ dàng tìm kiếm và sử dụng các mô-đun cho nhiều mục đích khác nhau, từ cơ sở dữ liệu, giao tiếp HTTP, đến bảo mật và kiểm thử. - Dễ dàng phát triển ứng dụng thời gian thực (Real-time applications)
Node.js cực kỳ phù hợp cho việc phát triển các ứng dụng thời gian thực như chat, game trực tuyến, thông báo thời gian thực, nhờ vào khả năng xử lý đồng thời và tính năng WebSockets hỗ trợ giao tiếp hai chiều giữa client và server. - Cộng đồng mạnh mẽ
Node.js có một cộng đồng phát triển rất lớn và năng động, với hàng nghìn dự án mã nguồn mở. Bạn dễ dàng tìm thấy sự hỗ trợ và tài liệu hướng dẫn khi gặp khó khăn trong quá trình phát triển. - Phù hợp với microservices architecture
Node.js phù hợp để xây dựng các ứng dụng với kiến trúc microservices vì khả năng xử lý nhanh các yêu cầu HTTP và có thể dễ dàng triển khai các dịch vụ nhỏ, độc lập mà không cần một hệ thống phức tạp. - Tiết kiệm tài nguyên hệ thống
Node.js không tạo ra nhiều luồng (threads) trong khi xử lý các yêu cầu, giúp giảm thiểu tài nguyên hệ thống và cải thiện khả năng xử lý đồng thời. - Tương thích với nhiều hệ điều hành
Node.js có thể chạy trên nhiều hệ điều hành như Windows, macOS, và Linux, giúp dễ dàng triển khai ứng dụng trên các nền tảng khác nhau. - Cập nhật và bảo trì dễ dàng
Việc phát triển và bảo trì ứng dụng Node.js trở nên dễ dàng hơn nhờ vào khả năng tự động cập nhật các thư viện và mô-đun thông qua npm, cũng như dễ dàng quản lý các phiên bản của thư viện.
Node.js đặc biệt hữu ích khi phát triển các ứng dụng yêu cầu hiệu suất cao, khả năng mở rộng tốt và xử lý đồng thời, đồng thời giúp đơn giản hóa quy trình phát triển khi sử dụng JavaScript ở cả phía client và server.
7. “callback hell” là gì?
Callback hell (hay còn gọi là Pyramid of Doom) là một vấn đề thường gặp trong các ứng dụng Node.js khi bạn sử dụng nhiều callback functions lồng nhau (nested callbacks). Đây là một tình trạng khó quản lý và đọc hiểu mã nguồn, khiến cho việc duy trì và mở rộng ứng dụng trở nên khó khăn.
Trong Node.js, do tính chất asynchronous (bất đồng bộ) của các hoạt động như đọc file, truy vấn cơ sở dữ liệu, hoặc gọi API, các hàm thường phải sử dụng callback functions để xử lý kết quả sau khi hoàn thành một tác vụ. Khi bạn cần thực hiện nhiều tác vụ bất đồng bộ theo thứ tự (chẳng hạn, đọc file, sau đó xử lý dữ liệu, rồi lưu vào cơ sở dữ liệu), bạn phải lồng các callback function vào nhau.
Ví dụ về callback hell:
fs.readFile('file1.txt', 'utf8', function(err, data1) {
if (err) {
console.log(err);
} else {
fs.readFile('file2.txt', 'utf8', function(err, data2) {
if (err) {
console.log(err);
} else {
fs.readFile('file3.txt', 'utf8', function(err, data3) {
if (err) {
console.log(err);
} else {
console.log(data1, data2, data3);
}
});
}
});
}
});
Trong ví dụ trên, mỗi thao tác đọc file lại yêu cầu một callback function lồng nhau. Khi số lượng callback tăng lên, mã nguồn trở nên khó đọc và khó bảo trì.
Để lại một bình luận