Bạn chẳng biết gì về NodeJS cả!

  • 15918
Tourist Đệ Quy 2017-10-03 23:31:03

Trong sự kiện Forward.js(về Javascript) năm nay, tôi đã có một bài nói "Bạn không biết gì về Node". Lúc đó, tôi đã thử thách khán giả bằng một vài câu hỏi về Nodejs và phần lớn những khán giả là dân tech cũng bó tay hầu hết các câu hỏi

Một vài người dũng cảm tiếp cận tôi sau đó và thú nhận với thực tế này.

Đây là vấn đề khiến tôi tham gia chia sẻ buổi đó. Tôi không nghĩ rằng chúng ta giảng dạy Node theo cách đúng đắn! Hầu hết các giáo trình về Nodejs tập trung vào các package chứ không phải runtime của Node. Phần lớn các package này tự chính nó bọc các module bên trong Node runtime(như http hay stream). Khi gặp vấn đề, chúng có thể nằm bên trong runtime và nếu không biết về Node runtime, bạn gặp rắc rối rồi đó

VẤN ĐỀ: HẦU HẾT CÁC GIÁO TRÌNH VỀ NODEJS TẬP TRUNG VÀO CÁC PACKAGE CHỨ KHÔNG PHẢI RUNTIME CỦA NODE.

Tôi đưa ra vài câu hỏi và câu trả lời vào trong bài viết này. Hảy thử trả lời chúng trong đầu trước nhé!

Câu hỏi #1: Call Stack là gì và nó có phải là thành phần của V8?

Call Stack chắc chắn là thành phần của V8 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).

Screenshot được chụp lại từ khóa học trên Pluralsight — Node.js nâng cao

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ề Node? Bởi vì bạn chỉ có thể có MỘT Call Stack trong một process của Node. Nếu giữ Call Stack đó busy, cái process Node đó cũng busy. Hãy nhớ lấy

Câu hỏi #2: 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?

Screenshot được chụp lại từ khóa học trên Pluralsight — Node.js nâng cao

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:

Screenshot được chụp lại từ khóa học trên Pluralsight — Node.js nâng cao

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

Câu hỏi #3: Node 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.

Câu hỏi #4: Bên cạnh V8 và Libuv, Node 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âu hỏi #5: 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

Câu hỏi #6: 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âu hỏi #7: 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:

Vì sao?

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

Câu hỏi #8: Các đối tượng exports, require, 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:

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

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

Câu hỏi #9: Vòng tròn dependency của module trong Node 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. Node 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ú ý.

Câu hỏi #10: 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.

Cám ơn bạn vì đã đọc!

0 Bình luận

Cùng một tác giả