+5

Những điều bạn có thể chưa biết về Node

1. Call Stack là gì, nó có phải là một phần của V8?

Chuẩn, Call Stack chắc chắn là một phần của V8. Nó là một cấu trúc dữ liệu mà V8 dùng để lưu vết lại những function được gọi. Mỗi khi chúng ta invoke một function, V8 đặt reference của function đấy vào call stack và cứ tiếp tục làm như vậy đối với mỗi lần invoke các function nest trong function đó. Điều này áp dụng với cả những function gọi đệ quy chính bản thân nó.

Khi chạy đến nested function cuối cùng, V8 sẽ pop từng function một và thay thế giá trị return của nó vào code.

Điểm đáng lưu ý là ta chỉ có MỘT Call stack với mỗi Node process, vậy nên nếu để call stack stuck, cả Node process sẽ bị stuck. Hãy ghi nhớ điều này.

2: Event loop là gì, nó có phải một phần của V8?

Theo bạn thì Event loop nằm ở đâu trong biểu đồ này?

Event loop được cung cấp bởi thư viện libuv, và nó ko phải là một phần của V8. Event loop là một entity xử lý các external event và chuyển chúng thành việc gọi callback. Nó là một vòng lặp mà nhặt lấy cái event từ event queue, sau đó đẩy các callback của chúng vào Call stack. Nó cũng là một vòng loop gồm nhiều giai đoạn. Event loop là một phần của một bức tranh tổng thể:

Bạn cẩn phải hiểu đc bức tranh tổng thể để có thể hiểu được Event loop. Bạn cần phải biết đc vai trò của V8, biết về Node API và biết cách mọi thứ được queue và xử lý bằng V8. Những Node API là những function kiểu như setTimeOut hay fs.readFile. Chúng ko phải là hàm của Javascript, chúng chỉ là những hàm đc cung cấp bởi Node. Event loop đứng ở giữa và hoạt động kiểu như một người tổ chức. Khi mà call stack trống, event loop sẽ quyết định cái gì sẽ được xử lý tiếp theo.

3: Node sẽ làm gì khi Call stack trống và event queue trống?

Đơn giản là Node sẽ exit. Khi ta chạy một Node program, Node sẽ tự động chạy event loop và khi mà event loop ko còn gì để làm, process sẽ exit. Để giữ cho Node process chạy, ta cần phải đặt một cái gì đấy vào đâu đó ở event queue. Ví dụ, khi chạy một timer hoặc một HTTP server, về căn bản, chúng ta đã bảo event loop tiếp tục chạy và kiểm tra những sự kiện đó.

4: Ngoài V8 và Libuv, Node còn có những external dependencies nào nữa?

Dưới đây là tất cả các library mà Node process có thể sử dụng: http-parser c-ares OpenSSL zlib Tất cả chúng đều ko thuộc Node. Chúng có source code riêng, bản quyền riêng, Node chỉ đơn giản là dùng chúng.

5: Có thể chạy một Node process mà ko cần V8 ko?

Đúng là chúng ta cần một VM để chạy Node process, nhưng V8 ko phải VM duy nhất mà ta có thể dùng. Chakra là một ví dụ. https://github.com/nodejs/node-chakracore

6: Sự khác biệt giữa module.exports và exports là gì?

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

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

Tại sao? exports chỉ là một reference hoặc alias của module.exports. Khi mà bạn thay đổi exports, bạn đã thay đổi reference của nó và nó ko còn liên quan đến API chính thống ( là module.exports). Nó sẽ chỉ là một local variable thuộc module scope thôi.

7: Tại sao top-level variable lại ko phải là global?

Nếu bạn có module1 định nghĩa top-level variable g:

// module1.js
var g = 42;

Sau đó bạn có module2 require module1 và thử access biến g, bạn sẽ nhận đc: g is not defined Tại sao? Nếu bạn làm cách tương tự ở trên browser, bạn có thể access top-level variable ở tất cả script. Tuy nhiên, mỗi Node file có một IIFE (Immediately Invoked Function Expression) của riêng nó, và tất cả variable chỉ đc scope trong IIFE đó thôi.

8: Những object export, require và module tồn tại ở khắp mọi nơi, nhưng tại sao chúng lại khác nhau ở mỗi file?

Khi bạn cần require một object, bạn sẽ chỉ cần sử dụng nó nhưng một global variable. Tuy nhiên, nếu bạn inspect require ở 2 file khác nhau, bạn sẽ thấy chúng là 2 object khác nhau. Tại sao? Là bởi vì cái IIFE được nói ở trên:

Như bạn thấy, IIFE truyền cho cho code 5 argument sau: exports, require, module, filename, dirname. 5 biến trên trông có vẻ như là tồn tại global khi bạn sử dụng Node, nhưng chúng thực chất chỉ là những argument của function mà thôi.

9: Circular module dependencies trong Node là gì?

Nếu bạn có module1 require module2 và module2 lại require lại module1, điều gì sẽ xảy ra?

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

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

Bạn sẽ ko gặp lỗi, Node cho phép điều này. Vậy nên nếu module1 require module2, nhưng module2 lại cần module1, và module1 chưa được hoàn thành, module1 sẽ chỉ nhận được một phần của module2 Tuy nhiên, bạn sẽ đc cảnh báo.

10: Khi nào thì có thể sử dụng hàm *Sync của file hệ thống? (ví dụ như readFileSync)

Tất cả các hàm fs trong Node đều có phiên bản synchronous, tại sao chúng ta cần sử dụng hàm sync thay vì hàm async? Đôi khi, việc sử dụng hàm sync là chấp nhận được. Ví dụ, nó có thể sử dụng ở các bước khởi tạo khi mà server đang load. Vì thông thường, những việc mà bạn làm sau bước khởi tạo sẽ phụ thuộc vào data của bước khởi tạo. Thế nên thay vì dùng callback, sử dụng hàm synchronous cũng ok. Tuy nhiên, nếu bạn sử dụng hàm synchronous ở trong một handler kiểu HTTP server on-request callback, đó sẽ là sai 100%, đừng làm thế.

Nguồn: https://medium.com/@samerbuna/you-dont-know-node-6515a658a1ed


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí