Tự học Node.js cơ bản để đi phỏng vấn (13 bài) – 3 – Nền tảng Node.js

Node.js là một trình chạy JavaScript được xây dựng trên công cụ JavaScript V8 của Chrome.


1093

“Node.js là một trình chạy JavaScript được xây dựng trên công cụ JavaScript V8 của Chrome.” ( Nguồn )

Lộ trình học Node.js

Bài này là một phần của loạt bài tự học Node.js, xem Học Node.js, Phần 1: Tổng quan về Lộ trình học tập Node.js để bắt đầu ngay từ đầu.

Nếu bạn tìm kiếm “sơ đồ kiến ​​trúc Node.js”, có khoảng 178 tỷ sơ đồ khác nhau cố gắng vẽ nên một bức tranh tổng thể về Node (từ bây giờ tôi sẽ gọi Node.js là Node).

Kiến trúc nút Hình 1. Ngăn xếp kiến ​​trúc Node.js

Trong các phần tiếp theo, tôi sử dụng Hình 1 làm cơ sở để thảo luận. Hãy xem xét từng thứ trong số này, bắt đầu với node runtime.

Đôi nét về ECMAScript

Hiệp hội Các nhà sản xuất máy tính châu Âu là tiêu chuẩn cơ thể chịu trách nhiệm đối với nhiều tiêu chuẩn, bao gồm ECMAScript, trong đó Javascript là phổ biến nhất.

Tiêu chuẩn ECMAScript (còn được gọi là ECMA-262) hiện là ES2017 (viết tắt của Ecma Script 2017). Node hỗ trợ ES2016, còn được gọi là ES6, vì nó là phiên bản thứ sáu của tiêu chuẩn được phát hành. Để tìm hiểu thêm về ECMAScript, hãy xem trang Wikipedia .

V8 JavaScript Engine của Chrome hỗ trợ ECMA-262 hiện tại (ES2017, hay còn gọi là ES8) và Node luôn hướng tới bất kỳ phiên bản ECMA-262 nào được V8 hỗ trợ. Node hiện tại hỗ trợ ECMAScript thông qua ES6 (ES2015), nhưng sẽ luôn hướng tới phiên bản ECMAScript mới nhất được hỗ trợ bởi V8. node.green trang theo dõi sự tiến bộ của nhóm Node trong việc hỗ trợ các phiên bản mới nhất của ECMAScript.

Mặc dù JavaScript là ngôn ngữ chúng tôi sử dụng để viết các ứng dụng Node, nhưng tôi muốn bạn lưu ý rằng đặc tả ECMAScript chi phối sự phát triển của JavaScript như một ngôn ngữ lập trình.

JavaScript trong Node.js – có thể không phải là JavaScript bạn đã quen

Một điều bạn sẽ nhận thấy khi bắt đầu viết các ứng dụng JavaScript trong Node: trong một số trường hợp hiếm hoi, JavaScript mà bạn quen viết không hoạt động như bạn mong đợi.

Nhiều lập trình viên JavaScript đã quen với việc sử dụng các biến toàn cục và nhận thấy khi chuyển sang Node, chúng dường như không hoạt động. Lý do cho điều này là mọi tệp JavaScript trong ứng dụng Node của bạn là thực thể có phạm vi riêng của nó được gọi là mô-đun . (Kiểm tra phần “trình bao bọc mô-đun” trong tài liệu API Mô-đun nếu bạn muốn tìm hiểu thêm về cách Node thực hiện điều này).

Đây là một tình huống bạn có thể gặp phải: bạn có hai tệp JavaScript A.js và B.js nơi bạn khai báo một biến bên trong A.js mà bạn mong đợi là toàn cục và cố gắng tham chiếu nó trong B.js:

A.js

var someNumber = 238;

Cho xem nhiều hơn

B.js

function sayIt() {
    alert('The value of the global variable "someNumber" is: ' + someNumber);
}

someNumber.html

<script src="./A.js"></script>
<script src="./B.js"></script>
<script>
    // Invoke the sayIt() function from B.js:
    sayIt();
</script>

Khi tôi chạy điều này trong Chrome, tôi thấy cảnh báo, giống như tôi mong đợi:

Kiểm tra biến toàn cục trong trình duyệt

Tuy nhiên, điều này không hoạt động trong Node. Các someNumberbiến có phạm vi trong A.jsvà không hiển thị trong B.js. Tôi sẽ chỉ cho bạn lý do tại sao nó không hoạt động sau trong khóa học.

Có một số khác biệt nhỏ trong cách bạn viết mã JavaScript cho trình duyệt, so với cách bạn viết JavaScript cho Node. Khi chúng tôi xem qua những điều đó trong khóa học, tôi sẽ đảm bảo và chỉ ra chúng.

Bây giờ chúng ta hãy quay lại Hình 1.

Node Runtime

Node Runtime là một thuật ngữ khác để chỉ các chương trình thực thi được thiết lập chạy các ứng dụng Node:

  • Node API : Các tiện ích JavaScript như File, I / O và network, cũng như một loạt các tiện ích khác, như mật mã và nén
  • Node core: một tập hợp các mô-đun JavaScript triển khai Node API. (Rõ ràng một số mô-đun phụ thuộc vào libuv và mã C ++ khác nhưng đó là chi tiết triển khai).
    • Công cụ JavaScript: Công cụ V8 của Chrome: Trình biên dịch mã JavaScript thành máy nhanh để tải, tối ưu hóa và chạy mã JavaScript của bạn
    • Vòng lặp sự kiện: được triển khai bằng cách sử dụng thư viện I / O hướng sự kiện, không chặn được gọi là libuv để làm cho nó nhẹ và hiệu quả (và có thể mở rộng)

API nút

Các Node API là một bộ tích hợp các module được cung cấp bởi Node.js, để bạn có thể xây dựng các ứng dụng của bạn. Nhiều mô-đun trong số này, như API hệ thống tệp (fs), nằm trên các chương trình cấp thấp hơn (Node Core) giao tiếp với hệ điều hành cơ bản. Khi bạn sử dụng mô-đun tích hợp sẵn Node API bằng cách nhập require()vào code của mình, sau đó gọi các function cần dùng, quá đã.

Một số mô-đun tích hợp mà bạn sử dụng trong khóa học này bao gồm:

Node API nằm trên cùng Node Core, mà chúng ta sẽ nói đến tiếp theo.

Node Core

Node Core là Node API cộng với một chương trình C ++, được xây dựng trên nhiều thư viện, liên kết với libuv (mà chúng ta sẽ nói ngay sau đây) và công cụ JavaScript (Chrome V8, mà chúng ta sẽ nói trong phần tiếp theo) .

Cơ sở hạ tầng

Cơ sở hạ tầng của Node runtime bao gồm hai thành phần chính:

  • Công cụ JavaScript
  • Thư viện I / O không chặn

Công cụ JavaScript

Công cụ JavaScript được Node sử dụng là công cụ V8 của Chrome, chạy tất cả mã JavaScript (của bạn, API Node và bất kỳ JavaScript nào trong các gói bạn nhận được từ sổ đăng ký npm). Khi bạn khởi động Node, nó sẽ chạy một phiên bản duy nhất của V8. Điều này có vẻ như là một hạn chế nghiêm trọng, nhưng Node làm cho nó hoạt động (thực sự là rất tốt), như bạn sẽ thấy.

V8 có thể được nhúng (hoặc liên kết ) vào bất kỳ chương trình C ++ nào như Node hoặc trình duyệt web như Chrome. Điều này có nghĩa là ngoài thư viện JavaScript thuần túy , V8 có thể được mở rộng để tạo các hàm hoàn toàn mới (hoặc các mẫu hàm ) bằng cách liên kết chúng với V8. Khi một hàm mới được đăng ký, một con trỏ đến một phương thức C ++ sẽ được chuyển và khi V8 chạy trên một trong các hàm JavaScript tùy chỉnh mới này, nó sẽ gọi phương thức Mẫu tương ứng. Đây là số lượng chức năng I / O của Node API được Node Core thực hiện

Chỉ V8 là JavaScript Engine?

Về lý thuyết, Node có thể được sửa đổi để sử dụng bất kỳ công cụ JavaScript nào, nhưng V8 là công cụ bạn sẽ sử dụng theo mặc định (và thành thật mà nói, nó bị ràng buộc khá chặt chẽ với V8). Điều đó nói rằng, những người khác đã khám phá các lựa chọn thay thế cho V8 và nếu bạn quan tâm đến Node trên các công cụ JavaScript hàng đầu khác, hãy xem dự án node-chakracore và dự án spidernode . 

Vòng lặp sự kiện – event loop

Một chip CPU có thể chạy các lệnh của chương trình của bạn nhanh hơn nhiều so với dữ liệu có thể được truy xuất từ ​​các thiết bị I / O như đĩa hoặc mạng. Tuy nhiên, nếu không có dữ liệu mà các thiết bị I / O này cung cấp, chương trình của bạn không thể thực hiện công việc của mình. Vì V8 đang chạy trong một luồng duy nhất (single thread), cho đến khi có dữ liệu, mọi thứ (chương trình của bạn, các chương trình khác, mọi thứ đang chạy trong phiên bản đó – hoặc ngữ cảnh – của động cơ V8) sẽ bị chặn cho đến khi hoạt động I / O hoàn tất.

Node sử dụng libuv làm phần triển khai vòng lặp sự kiện. Để sử dụng một API không đồng bộ Node, bạn chuyển một hàm gọi lại làm đối số cho hàm API đó và trong vòng lặp sự kiện, lệnh gọi lại của bạn sẽ được thực thi.

Vòng lặp sự kiện bao gồm các giai đoạn khác nhau nơi các lệnh gọi lại được gọi:

  • Giai đoạn hẹn giờ : setInterval()và các lệnh setTimeout()gọi lại của bộ hẹn giờ đã hết hạn được chạy
  • Giai đoạn thăm dò ý kiến: Hệ điều hành được thăm dò để xem liệu có bất kỳ hoạt động I / O nào đã hoàn tất hay không và nếu có, các lệnh gọi lại đó có được chạy hay không
  • Giai đoạn kiểm tra: các lệnh setImmediate()gọi lại được chạy

Mã JavaScript bạn viết thực thi ở một trong hai “dòng” thực thi:

  • Dòng chính – đây là JavaScript chạy khi Node chạy chương trình của bạn lần đầu tiên. Nó sẽ chạy từ đầu đến cuối và khi kết thúc, nhường quyền kiểm soát cho vòng lặp sự kiện
  • Vòng lặp sự kiện – đây là nơi tất cả các lệnh gọi lại của bạn được chạy

Một quan niệm sai lầm phổ biến là các lệnh gọi lại V8 và vòng lặp sự kiện chạy trên các luồng khác nhau (multi thread). Tất cả mã JavaScript được chạy bởi V8 trong cùng một luồng – single thread.

Cho đến khi bạn bị cuốn vào cách Node sử dụng V8 để chạy mã JavaScript của bạn, nó sẽ khiến bạn không khỏi đau lòng khi cố gắng gỡ lỗi các vấn đề kỳ lạ.

The thread pool

Vòng lặp sự kiện là thứ cho phép Node cung cấp khả năng mở rộng ấn tượng, chủ yếu thông qua mô hình I / O không chặn của nó. Nhưng giả sử Node API cung cấp một số chức năng không chuyên sâu vào I / O mà là chuyên sâu về CPU. Vòng lặp sự kiện có thể giúp được gì không? Chắc chắn rồi! libuv sử dụng một nhóm các luồng được gọi là nhóm workers (hay còn gọi là nhóm luồng để giảm tải cả I / O và các tác vụ đòi hỏi nhiều CPU.)

Chúng ta sẽ tìm hiểu sâu hơn về thread pool trong Bài 5 của series này

REPL

Khi bạn cài đặt node, bạn cũng sẽ tự động nhận được môi trường (REPL).

Mở cửa sổ dòng lệnh hoặc dấu nhắc lệnh, nhập nodevà nhấn Enter. Bạn sẽ thấy một cái gì đó như thế này:

$ node
>

Nhập .help và nhấn Entervà bạn sẽ thấy cái này:

> .help
.break    Sometimes you get stuck, this gets you out
.clear    Alias for .break
.editor   Enter editor mode
.exit     Exit the repl
.help     Print this help message
.load     Load JS from a file into the REPL session
.save     Save all evaluated commands in this REPL session to a file
>

Bạn có thể nhập một trong hai thứ vào dòng REPL mà nó hiểu: một dòng mã JavaScript hoặc một trong các lệnh trên. Chúng khá dễ hiểu, nhưng nếu bạn muốn tìm hiểu thêm về chúng, hãy xem tài liệu API Node REPL .

Nhập các dòng sau vào REPL, từng dòng một (đảm bảo nhấn Enterphím giữa mỗi dòng):

Ví dụ 1. Hello World

let hello = "Hello"
let world = "world"
hello + ' ' + world

Bạn sẽ thấy kết quả sau:

> let hello = "hello"
undefined
> let world = "world"
undefined
> hello + ' ' + world
'hello world'
>

REPL in undefined cho hai câu lệnh đầu tiên (vì chúng không đánh giá bất cứ điều gì). Khi bạn nhập biểu thức hello + ' ' + world , REPL sẽ đánh giá điều đó thành chuỗi ký tự 'hello world'.

Để thoát khỏi REPL. Nhập .exit và nhấn Enter.

REPL có chế độ “editor”. Quay lại REPL, sau đó nhập .editor và nhấn Enter. Sau đó nhập lại mã Ví dụ 1. Khi bạn đã sẵn sàng để REPL chạy nó, hãy nhấn Ctrl+D. Bạn sẽ thấy đầu ra như thế này:

$ node
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
let hello = "Hello"
let world = "world"
hello + ' ' + world
'Hello world'
>

Ví dụ 2. Câu lệnh nhiều dòng

var array = ['Hello' , ' ', 'there', ' ', 'REPL'];
var message = '';
for (let word of array) {
    message += word;
}
message += '!';

Bạn sẽ thấy đầu ra như thế này:

$ node
> var array = ['Hello' , ' ', 'there', ' ', 'REPL'];
undefined
> var message = '';
undefined
> for (let word of array) {
...     message += word;
... }
'Hello there REPL'
> message += '!';
'Hello there REPL!'
>

Nếu bạn muốn chạy một tập lệnh mà bạn có tệp JavaScript, bạn có thể sử dụng .load để tải nó vào REPL.

Lưu ý: Khi bạn chạy .load lệnh trên example2.js, bạn sẽ thấy tuyên bố bản quyền Apache 2.0 mà tôi đã xóa để xem xét dung lượng khỏi danh sách ở trên để tiết kiệm dung lượng.

Đã đến lúc viết chương trình đầu tiên của bạn sử dụng Node API.

Thoát và sau đó bắt đầu REPL một lần nữa để có được một phiên bản Node mới.

Nhập các dòng mã sau vào REPL, từng dòng một hoặc ở chế độ soạn thảo (hoặc bạn có thể tải nó example3.jsbằng cách sử dụng .loadlệnh):

var fs = require('fs');
var fileContents = fs.readFileSync('../data/50Words.txt', 'utf8');
var numberOfWords = fileContents.split(/[ ,.\n]+/).length;

Yêu cầu REPL đánh giá numberOfWords biến, sau đó là fileContents biến. Bạn sẽ thấy đầu ra như thế này (tôi đang sử dụng chế độ biên tập cho ví dụ này):

$ node
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
var fs = require('fs');
var fileContents = fs.readFileSync('../data/50Words.txt', 'utf8');
var numberOfWords = fileContents.split(/[ ,.\n]+/).length;
undefined
> numberOfWords
51
> fileContents
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent scelerisque libero nec nulla aliquet, faucibus efficitur massa sollicitudin. Aliquam hendrerit hendrerit est, sed dictum lectus. Nulla eu placerat elit, at volutpat dui. Mauris gravida tortor quis tempus posuere. Vestibulum ultrices leo quis nisi suscipit pellentesque. Nullam congue maximus odio, eu.'
>

Như bạn có thể thấy, REPL in ra giá trị của numberOfWordsvà fileContentskhi bạn yêu cầu nó (lưu ý: giá trị trả về của readFileSync()là một Chuỗi chứa nội dung của tệp). Bạn nghĩ điều gì sẽ xảy ra nếu bạn yêu cầu REPL đánh giá fsbiến số? Nếu bạn trả lời rằng nó sẽ in ra biểu diễn chuỗi tốt nhất có thể của đối tượng ở định dạng JavaScript Object Notation (JSON), thì bạn đã đúng. Hãy tiếp tục và thử nó (tôi sẽ không hiển thị đầu ra ở đây để tiết kiệm dung lượng).

Để tóm tắt REPL, đó là:

  • Một môi trường tương tác, không đồ họa
  • Tốt cho việc học và tạo mẫu

Để biết thêm thông tin về REPL, hãy xem tài liệu REPL API .

Non-Blocking I/O

Chúng tôi đã xem xét ngắn gọn mô hình I / O không chặn và cách vòng lặp sự kiện gọi ra lệnh gọi lại mà bạn cung cấp khi nó kết thúc bất kỳ hoạt động nào bạn đã yêu cầu API Node thực hiện.

Asynchronous I/O

  1. Trong VSCode, tạo ra example3.js, trông giống như thế này

Ví dụ 3. Đọc một tệp (đồng bộ)

var fs = require('fs');
console.log('Starting program...');
var fileContents = fs.readFileSync('../data/50Words.txt', 'utf8');
var numberOfWords = fileContents.split(/[ ,.\n]+/).length;
console.log('There are ' + numberOfWords + ' words in this file.');
console.log('Program finished.');

Trong cửa sổ debug sẽ hiện như sau

. . .
Debugger attached.
Starting program...
There are 51 words in this file.
Program finished.
  • Dòng 2: Cuộc console.log()gọi trên dòng chính hiển thị trên bảng điều khiển
  • Dòng 3: Yêu cầu Node đọc tệp một cách đồng bộ, do đó, chuỗi V8 sẽ bị chặn cho đến khi tệp đã được đọc và nội dung của tệp được trả về từ lệnh readFileSync()
  • Dòng 5-6: Câu console.log() để in ra giá trị

Ví dụ 4. Đọc tệp (không đồng bộ)

var fs = require('fs');
console.log('Starting program...');
fs.readFile('../data/50Words.txt', 'utf8', function(err, fileContents) {
  if (err) throw err;
  let numberOfWords = fileContents.split(/[ ,.\n]+/).length;
  console.log('There are ' + numberOfWords + ' words in this file.');
});
console.log('Program finished');
  1. Nhấp vào Chế độ xem gỡ lỗi (debug) ở phía bên trái của VSCode và đảm bảo example4.jsđang mở và hoạt động trong trình chỉnh sửa.
  2. Sau đó nhấp vào nút Run . Cửa sổ Trình gỡ lỗi bật lên ở cuối VScode
  • Dòng 1: Tham chiếu đến fsmô-đun được truy xuất thông qua require()
  • Dòng 2: Cuộc console.log()gọi trên dòng chính hiển thị trên bảng điều khiển
  • Dòng 3: Cuộc gọi dòng chính đến fs.readFile()được thực hiện, truyền tệp cần đọc ( ../data/50Words.txt), mã hóa của tệp ( 'utf8')
  • Dòng 8: Vì tất cả mã trên dòng chính thực thi từ đầu đến cuối trước khi vòng lặp sự kiện chạy, console.log()dòng 8 thực thi trên dòng chính.
  • Dòng 4-6: Khi tệp đã được đọc, vòng lặp sự kiện sẽ gọi lại, cuối cùng sẽ gọi console.log().

Khi nào sử dụng I / O đồng bộ

I / O đồng bộ chặn luồng V8 cho đến khi hoạt động I / O hoàn tất, như bạn đã thấy trong ví dụ 3.

Tại sao bạn lại sử dụng lệnh gọi I / O chặn luồng V8?

Đôi khi thực hiện I / O đồng bộ là tốt. Trên thực tế, thường thì I / O đồng bộ nhanh hơn I / O không đồng bộ vì chi phí liên quan đến việc thiết lập và sử dụng callback, thăm dò hệ điều hành về trạng thái I / O,…

Giả sử bạn đang sử dụng Node để viết tiện ích một lần chỉ xử lý một tệp (bạn làm điều này trong Phần 6). Bạn kích hoạt Node từ dòng lệnh và chuyển nó vào tên tệp của tiện ích JavaScript của bạn. Tiện ích của bạn là thứ duy nhất đang chạy, vì vậy nếu nó chặn luồng V8, ai quan tâm? Trong trường hợp này, sử dụng I / O đồng bộ là ổn.

Tôi đề xuất quy tắc chung này: Nếu mã khác cần chạy ở chế độ nền trong khi hoạt động I / O của bạn đang chạy, hãy sử dụng I / O không đồng bộ. Nếu không, hãy sử dụng I / O đồng bộ. Nếu bạn không chắc chắn, hãy sử dụng I / O không đồng bộ. Trên thực tế, triết lý thiết kế Node là ” một API phải luôn không đồng bộ ngay cả khi nó không cần thiết “.

Hệ sinh thái npm

npm là từ viết tắt của Node Package Manager. Có lẽ vẫn ổn khi nghĩ về npm theo cách đó (mặc dù nói rõ ràng, npm KHÔNG phải là một từ viết tắt ),

Theo trang web npmjs.com: “npm là trình quản lý gói cho JavaScript”.

NPM

Bạn càng sử dụng Node, bạn càng tin tưởng vào sự đóng góp của các nhà phát triển Node đồng nghiệp của bạn, những người đã đóng góp hàng nghìn mô-đun cho cơ quan đăng ký trung tâm tại npmjs.com. Để sử dụng các mô-đun này (còn được gọi là gói) trong dự án Node của bạn, bạn cài đặt chúng (sử dụng npm install) và sau đó require()chúng trong bất kỳ chương trình JavaScript nào của bạn cần chúng.

Kết luận cho Phần 3

Trong hướng dẫn này, bạn đã học về:

  • Kiến trúc của Node.js và cách nó bao gồm:
    • Thời gian chạy Node
    • Userland
  • Bạn đã thấy thời gian chạy Node bao gồm:
    • Vòng lặp sự kiện (libuv)
    • Công cụ JavaScript V8 của Chrome
  • Bạn đã làm việc với REPL
  • Bạn đã học một chút về npm

Trong Phần 4, bạn đi sâu hơn vào phong cách lập trình không đồng bộ của Node.


Like it? Share with your friends!

1093