NodeJS – Những bí mật phải biết khi đi phỏng vấn

Bài viết này liệt kê những điều cần phải biết, để có thể hiểu rõ mọi điều về NodeJs trước khi tiếp tục tự học từ cơ bản đến nâng cao.


1062

Call Stack là gì và nó có phải là thành phần của V8 NodeJS?

Call Stack chắc chắn là thành phần của V8 NodeJS rồi. Nó chính là cấu trúc dữ liệu mà V8 dùng để theo dõi các lệnh gọi hàm. Mỗi khi chúng ta gọi một function, V8 đặt một reference đến function đó bên trong 1 call stack và nó tiếp tục làm như vậy cho mỗi lần gọi lồng nhau của các function khác. Điều này cũng bao gồm các function tự gọi mình(đệ quy).

Call Stack NodeJS
Call Stack NodeJS

Khi các function gọi lồng nhau kết thúc, V8 sẽ bật một function tại một thời điểm và sử dụng giá trị trả về ở chỗ đó.

Vì sao điều này lại quan trọng cho việc hiểu về NodeJS? Bởi vì bạn chỉ có thể có MỘT Call Stack trong một process của NodJSe. Nếu giữ Call Stack đó busy, cái process Node đó cũng busy. Hãy nhớ lấy

Event Loop là gì? Nó có phải là thành phần của V8

Bạn nghĩ đâu là event loop trong cái diagram này?

Event Loop NodeJS
Event Loop NodeJS

Event Loop được cung cấp bởi thư viện libuv. Nó không phải là thành phần của V8

Vòng sự kiện là thực thể xử lý các sự kiện bên ngoài và chuyển đổi chúng thành lời kêu gọi gọi lại. Đó là một vòng lặp mà chọn các sự kiện từ hàng đợi sự kiện và đẩy callbacks của họ vào Call Stack. Nó cũng là một vòng lặp đa pha.

Event Loop là một thực thể xử lý các event ngoại vi và chuyển đổi chúng vào trong lời gọi callback. Nó là một vòng lặp chọn các event từ danh sách event đang chờ và đẩy callback của các event này vào Call Stack. Nó cũng là vòng lặp multi-phase

Nếu đây là lần đầu tiên bạn nghe về Event Loop, những định nghĩa này sẽ không hữu ích. Event Loop là một phần của bức tranh lớn hơn nhiều:

Event Loop NodeJS
ScreensEvent Loop NodeJShot

Bạn cần phải hiểu bức tranh lớn này để hiểu về Event Loop. Bạn cũng cần phải hiểu vị trí của V8, biết về Node APIs, và biết về cách nào mà những thứ được xếp vào hàng chờ để được thực hiện bởi V8

Node APIs là những function như setTimeout or fs.readFile. Bản thân nó không phải là một phần của Javascript. Chúng là những function được cung cấp bởi Node.

Event Loop ngồi ngay giữa của bức tranh(một phiên bản thực sự phức tạp hơn của nó) và hành động như một nhà tổ chức. Khi V8 Call Stack rỗng, event loop có thể quyết định những gì được thực thi tiếp theo

NodeJS sẽ làm gì khi Call Stack và hàng chờ event loop rỗng?

Nó chỉ đơn giản là exit.

Khi bạn chạy một chương trình Node, nó sẽ tự động khởi chạy event loop và khi event loop đó nhàn rỗi và chẳng còn gì để làm, process đó sẽ kết thúc.

Để giữ cho process Node tiếp tục chạy, bạn phải đặt một cái gì đó ở đâu đó trong hàng chờ event. Ví dụ, khi chạy một timer hoặc một server HTTP, về cơ bản bạn nói cho event loop biết rằng hãy tiếp tục chạy và kiểm tra những event này.

Bên cạnh V8 và Libuv, NodeJS còn có những dependency ngoại vi nào nữa?

Những thư viện sau mà Node process có thể sử dụng

  • http-parser
  • c-ares
  • OpenSSL
  • zlib

Tất cả chùng đều nằm bên ngoài Node. Chúng có mã nguồn riêng, license riêng. Node chỉ sữ dụng chúng thôi

Bạn phải nhớ điều này vì bạn cần biết cách mà những chương trình đang chạy. Nếu bạn nén dữ liệu, bạn có thể gặp rắc rối đâu đó trong thư viện zlib. Bạn sẽ đối mặt với lỗi trong zlib, không liên quan gì đến Node

Có thể chạy Node process nếu không có V8?

Đây có thể là câu hỏi mẹo. Bạn cần VM để chạy Node process, nhưng không chỉ V8, bạn có thể chạy với cả Chakra.

Vào Github repo này để theo dõi dự án node-chakra

nodejs/node-chakracore
node-chakracore – Node.js on ChakraCore :sparkles::turtle::rocket::sparkles:github.com

Sự khác nhau giữa module.exports và exports?

Bạn luôn có thể dùng module.exports để export ra API của các module. Cũng có thể sử dụng exports trừ một trường hợp:

module.exports.g = ...  // Ok
exports.g = ...         // Ok
module.exports = ...    // Ok
exports = ...           // Not Ok

Vì sao ư?

exports chỉ là reference hoặc alias của module.exports. Khi thay đổi exports bạn cũng đã thay đổi reference đó và không bao giờ thay đổi được API chính chủ ( module.exports). Bạn chỉ nhận được một biến local trong phạm vi module.

Các biến top-level không phải là global?

Nếu bạn có module1 được đặt như là một biến top-level g:

// module1.js
var g = 42;

Và bạn có module2 đang require module1 và thử truy cập vào biến g, bạn sẽ nhận được g is not defined.

Tại sao ư? Nếu làm điều tương tự trên trình duyệt, bạn có thể truy cập vào các biến top-level bên trong tất cả các script đã include phía sau định nghĩa của chúng.

Mỗi tập tin Node đều sở hữu IIFE (Immediately Invoked Function Expression) phía sau. Tất cả các biến được khai báo bên trong tập tin Node đều thuộc phạm vi của IIFE đó.

Câu hỏi liên quan: WhaOutput là gì nếu chạy tập tin Node với chỉ một dòng sau:

// script.js
console.log(arguments);

Bạn sẽ nhận được:

top-level

Vì sao?

Bởi vì những gì NodeJS chạy chính là function. NodeJS sẽ gói code của bạn thành function và chạy function đó .

Các đối tượng exportsrequire, và module là global trong mọi tập tin nhưng lại khác nhau trong từng tập tin. Bằng cách nào?

Khi bạn cần dùng đối tượng require, bạn chỉ cần dùng nó trực tiếp như thể nó là biến global vậy. Tuy nhiên, nếu xem xét require trong hai tập tin, chúng lại là hai đối tượng khác nhau. Bằng cách nào?

Bởi vì IIFE:

exports
exportsrequire, và module

Như bạn thấy, IIFE gắn vào code của bạn năm argument sau: exportsrequiremodule__filename, and __dirname.

Năm biến này hiển thị là global khi dùng trong NodeJS, nhưng chúng thực ra chỉ là các argument của function

Circular dependency của module trong NodeJS là gì? 

Nếu bạn có module1 require đến module2 và module2 lại require module1, Chuyện gì sẽ xảy ra? Lỗi chăng?

// module1
require('./module2');
// module2
require('./module1');

Sẽ không có lỗi. NodeJS cho phép làm điều đó

Vì module1 require đến module2, nhưng khi module2 cần module1 và module1 lại chưa xong, module1 sẽ chỉ nhận được một phần của module2. Bạn hãy chú ý, vì điều này có thể dẫn đến “tràn ram” server của bạn – memory leak.

Khi nào thì sử dụng được các method *Sync (kiểu như readFileSync)

Mỗi phương thức fs trong Node có một phiên bản bất đồng bộ. Vì sao bạn sẽ dùng một phương thức đồng bộ thay vì một phương thức bất đồng bộ?

Đôi khi dùng phương thức đồng bô sẽ tốt. Ví dụ, nó có thể dùng như một bước khởi tạo trong lúc chờ cho server load lên. Thường là trong trường hợp mà mọi bước sau đều phụ thuộc vào data mà bạn nhận được ở bước này. Sử dụng phương thức bất đồng bộ cũng được, miễn là cho những thứ làm chỉ trong một lần.

Tuy nhiên, sẽ rất sai nếu bạn dùng phương thức bất đồng bộ bên trong một xử lý request callback kiểu như HTTP server.

Kết luận

Qua bài viết bạn đã biết hơn những điều về NodeJS. Hãy tận dụng để tối ưu hóa code và app của bạn.

Để nâng cao thêm kiến thức về NodeJS, hãy tham khảo thêm 19 bí mật để trở thành dev NodeJS xịn xò 2020 – phần 1


Like it? Share with your friends!

1062

0 Comments