UnderstandJavaScript - Systax Parsers, Excution Contexts and Lexical Environments

1 . Systax Parser (Trình phân tích cú pháp)

=>Là một chương trình đọc mã của bạn đầu tiên nó sẽ kiểm tra xem bạn khai báo cú pháp có đúng cách hay không và xác định những gì bạn ra lệnh tất nhiên là trong trường hợp bạn khai báo cú pháp hợp lệ (tóm lại nó kiểm tra tính hợp lệ cú pháp của bạn)

Khi bạn viết mã JS bạn không trực tiếp ra lệnh nói dễ hiểu hơn là ra lệnh cho máy tính phải làm gì nó không đơn giản như vậy . Mã JS bạn viết ra sẽ được phân tích và dịch ra mã máy để máy tính có thể hiểu được những chương trình kiểu này được gọi là compilers (trình biên dịch) , nó sẽ đảm bảo code bạn viết đúng cấu trúc , cú pháp hợp lệ rồi dịch sang mã máy.

Để hiểu hơn về Syntax Parser mình nói thêm rằng lấy ví dụ trong browser V8 là một Javascript Engine sẽ đóng vai trò là một trình biên dịch (just - in - time) dịch code JS sang byte-code , bạn có thể hiểu rằng Syntax Parser kiểm tra tính hợp lệ của cú pháp rồi chuyển code JS sau khi kiểm tra cho V8 để nó dịch.

Hoạt động :Mình sẽ nói sơ qua về cách thức hoạt động của Syntax Parser nhé
- Bạn có thể thấy rằng JS luôn chạy trong một môi trường nhất định ví dụ môi trường trình duyệt(Hay còn được gọi là Runtime) , phía máy chủ (browser , node js)  nhưng trong bài viết này mình sẽ chỉ tập trung ở phía browser.
- Khi bạn viết code js mà muốn nó được thực thi có rất nhiều thứ xảy ra phía hậu trường của nó nhưng mình muốn nhấn mạnh vài điều quan trọng như sau.

=> Có một thứ được gọi là JavaScript Engine , nhiệm vụ của nó đơn giản là lấy code của bạn và thực thi , JavaScriptEngine là một chương trình được nhúng vào trong trình duyệt , nó cũng được nhúng vào trong NodeJS để thực thi code JS ở phía server nữa.Một trong những JSE phổ biến là V8 ngoài ra còn rất nhiều JSE nữa.
JSE lấy code JS của bạn rồi thực thi nó


=> Khi JSE lấy code của bạn điều đầu tiên nó làm là kiểm tra tính hợp lệ của cú pháp bằng cách cho SyntaxParser kiểm tra code , Syntax Parser đọc từng dòng code , điều này cũng đồng nghĩa rằng Syntax Parser biết được các quy tắc để tạo ra code JS , biết được phải viết như thế nào cho đúng, nếu bạn viết sai thì nó sẽ ném ra lỗi và JSE sẽ dừng thực thi ngay lập tức.



=> Nếu mọi thứ bạn viết là đúng thì SyntaxParser sẽ tạo ra một loại cấu trúc dữ liệu , được gọi là Abstract Syntax Tree (cây cú pháp trừu tượng) , sau đó cấu trúc dữ liệu này được dịch thành mã máy , bây giờ thì đoạn mã đầu vào đã không còn là code JS nữa rồi, bây giờ khối code đầu vào trở thành một bộ hướng dẫn cho CPU để thực thi các hành động.



, như mình vừa nói bên trên thì nó đọc code của bạn và kiểm tra tính hợp lệ của cú pháp nhưng bạn có thể hiểu đơn  giản rằng nó sẽ cố gắng hiểu rằng bạn đang muốn làm gì ?? nó dự đoán câu lệnh bạn muốn viết
Ví dụ : nó sẽ chạy qua từng ký tự bạn gõ : nó đọc được rằng bạn đang gõ r,e,t,u,r như vậy nó xác định rằng bạn định gõ return , nhưng nếu bạn không gõ như vậy , nó sẽ ném ra lỗi vì bạn đi ngược lại giả định của nó.

=> Bên trái là code của bạn , bạn có một hàm bên trong có biến , chúng sẽ được biểu diễn trong bộ nhớ . Có một trình biên dịch hoặc thông dịch một phần trong đó là trình phân tích nó sẽ duyệt qua từng kí tự trong code của bạn , từng từ khóa  cú pháp nó sẽ dịch code bạn viết cho máy tính kiểu như
nó đọc và nhận ra từ function và nói ohhhh! đây là một hàm và chúng ta sẽ cho thằng ranh này một số lượng ô nhớ trong ram....



2.Lexical Environment : (Môi trường từ vựng)

Có thể bạn đã từng nghe rất nhiều về biến thể khác nhau của từng này như Lexical Scope hay Lexical Analysis, Lexical Scoping...mới học mình cũng thấy mông lung.
Hãy hiểu mọi thứ thật đơn giản
=> Trước hết hãy hiểu : Lexical Environment hiểu đơn giản là nơi bạn viết một cái gì đó.
Một mô tả dễ hiểu hơn : Lexical Environment gắn liền với mỗi Executoin Context được tạo ra, nó như một vũ trụ thu nhỏ bạn tạo ra mỗi khi Execution Context được tạo ra.





- Mỗi khi chạy code trình biên dịch sẽ thực hiện quá trình Lexical Analysis, quá trình này chỉ đơn giản kiểm tra xem những thứ chúng ta viết nó (biến hàm) nó nằm ở đâu và Lexical Environment cho biết thông tin đó, thông tin này giúp chúng ta xác định được rất nhiều thứ, lợi dụng những thông tin này giúp V8 xác định được cách thực thi code
=> Mỗi một hàm khi được invoked sẽ tạo ra một Lexical Environment mới.
=> Lexical Environment đầu tiên được tạo ra là Global Lexical Environment.
Như các bạn biết JSE thực thi mã trên nguyên tắc đọc từng dòng rồi thực thi, xác định được nơi mà bạn viết đoạn code này để thực thi rất quan trọng. Đi sâu vào trong V8 hơn có trình biên dịch và trình thông dịch , thông tin giúp xác định đoạn code đang được thực thi nằm ở đâu giúp trình thông dịch và biên dịch biết nhiều điều về đoạn code đó, điều này ảnh hưởng mật thiết đến cách đoạn code được thực thi như thế nào.
Để hiểu rõ hơn Lexical Environment là gì bạn cần hiểu Scope là gì ? 
Scope hay nhiều tài liệu nói rằng là Lexical Context xác định cách dữ liệu của bạn được truyền đi khắp chương trình của bạn thông qua câu lệnh và biểu thức . 
- Lexical Environment : Khi bạn tạo ra một Scope , JS hiểu rằng bạn đang tạo ra Lexical Environment , nó là một khái niệm mô tả những gì thực sự được tạo ra khi bạn tạo ra một Scope, nó cho biết khối code của bạn nằm trên chương trình , được viết ở đâu và có những gì xung quanh nó ? Syntax Parser quan tâm đến Lexical Environment để xác dịnh code của bạn chạy như thế nào.




Không có gì đặc biệt trong đoạn code trên , đơn giản là mình khai báo một hàm lồng nhau
Như bạn đã biết khi chương trình của bạn bắt đầu chạy trong JS nó sẽ chạy trong một EC nào đó Global Environment đã được tạo a và foo sẽ được lưu trữ trong đó . Khi chúng ta gọi hàm foo bên dưới , một Execution Context mới sẽ được tạo ra và lưu trữ biến b trong Execution Context Environment của hàm foo.Và khi chạy đến dòng gọi hàm bar chúng ta cũng tạo ra một Ec mới cho nó.
=> Bất cứ khi nào chúng ta gọi một hàm thì một EC mới được sinh ra và đẩy và Execution Context Stack với một môi trường biến mới (Execution Context Environment) của nó.
Bên trong hàm bar chúng ta thử log ra xem để kiểm tra các biến mà chúng ta tạo có thể nhìn thấy hay không và liệu chúng ta có thể truy cập nó trong môi trường của nó không ?
Rõ ràng một cách hiển nhiên rằng c được ghi vào bar environment (môi trường của hàm bar) vì chúng ta khai báo nó trong bar, vậy nó sẽ sẽ được log ra thành công.
Nhưng khi chúng ta vẫn log ra các biến a và b thì lại có vấn đề phát sinh vì chúng ta đâu có define nó bên trong bar environment đâu ?

Hãy cùng xem xét :
Ở dòng thứ 2 mình yêu cầu biến b mặc dù không hề define nó trong phạm vi hàm bar.Vì vậy js thực hiện tìm kiếm trong phạm vi nội bộ hàm cho đến khi không tìm được nó sẽ tìm bên trong Outer Environment. Trong trường hợp trên , js đã tìm thấy biến b tại hàm foo vì hàm bar có tham chiếu đến hàm foo , mặc dù vậy EC hàm foo vẫn bị ngừng.
Tương tự khi yêu cầu đến biến a nó cũng được hiển thị thành công vì biến a được lưu trong Global Execution Context  , bất kì một hàm hay một thành phần trong code cũng đều có quyền truy cập các nội dung trong scope toàn cầu.

Excution Context : (Bối cảnh thực hiện)

Ở Syntax Parser mình đã giới thiệu rằng làm thế nào để JSE có thể lấy code và kiểm tra tính hợp lệ của cú pháp và dịch code sang mã máy để CPU có thể hiểu và thực thi , nhưng code được thực thi như thế nào ? thứ tự thực thi của nó ra sao ?, để trả lời những câu hỏi này bạn cần hiểu được khái niệm Execution Context.
- Execution Context (bối cảnh thực hiện) như một môi trường mà trong đó code của hàm được thực thi, nó là một môi trường bao bọc mỗi hàm mỗi khi hàm được invoked , nó quản lý code trong hàm
=> Rõ ràng trong một chương trình sẽ có rất nhiều Execution Context được tạo ra nhiều môi trường bao bọc hàm mỗi khi hàm được invoked, các môi trường này là độc lập với nhau, có một vài sự liên kết giữa các môi trường nhưng mình sẽ đề cập đến vấn đề này sau.
=> Bạn cứ tưởng tượng rằng Execution Context như một cái hộp chứa code JS và mỗi khi có EC được tạo ra thì JSE chạy code nằm trong EC này , EC như một container ,  nó lưu trữ các biến và hảm (EC được tạo ra khi nào , tạo ra như thế nào mình sẽ nói trong những bài sau)
=> Execution Context là một trình bao bọc giúp quản lý đoạn code đang chạy mỗi khi hàm được invoked, nói đơn giảng thì nó là một môi trường nơi code JS được ước tính và thực thi
=> Bất cứ khi nào code JS được thực thi nó sẽ luôn luôn được thực thi trong một EC nào đó
- Khi một chương trình được chạy một EC luôn được tạo ra đó là EC mặc định hay còn được gọi là Execution Context Global , ECG nắm giữ tất cả đoạn code trong nó nhưng rõ ràng nó không chịu trách nhiệm thực thi code của bất kì hàm nào bên trong nó.

Execution Context được tạo ra và chạy như thế nào ?

Mình đã giải thích EC là gì ? vậy nó được tạo ra như thế nào ?
Để dễ hiểu hơn bạn có thể tưởng tượng EC như một đối tượng , đối tượng này có 3 thuộc tính 
+ Variable Object (VO).
+ Scope chain.
+ "This" variable.


=> Sau khi đã tạo ra Execution Context tiếp tục chạy đến giai đoạn 2 đó là Execution Phase.đây là giai đoạn mà code được thực thi.

- Một góc nhìn khác để bạn có thể hiểu được về EC là  bạn có thể tạo ra rất nhiều Scope nên có thể sẽ có rất nhiều Lexical Environment trong chương trình của bạn . EC như một trình bao bọc để giúp quản lý mã đang chạy  .Mỗi khi bạn Invoked một hàm sẽ có một EC mới được tạo ra .Hơn nữa rất nhiều lexicalEnvironment làm sao để biết cái nào đang chạy và ExcutionContext sẽ quản lý vấn đề này. Tóm lại ExcutionContext bao bọc code của bạn và quản lý nó.
- Trong môi trường JavaScript có 2 loại Excution Context (Bối cảnh thực thi) chính đó là Execution Context và Global Execution Context.

=> JavaScript sẽ tự động tạo một Golbal Excution Context và tự động đẩy nó vào CallStack ngay khi chương trình bắt đầu chạy, Global Execution Context bao trùm tất cả chương trình của bạn.

Trong quá trình tạo Global Execution Context JS sẽ tạo ra 2 biến đó là Global Object và biến This
(2 biến này được tạo ra trong Global Execution Context thì Global Object sẽ được gán bằng với biến This  ).

=> Thứ hai : Funtion Execution Context (Bối cảnh thực thi hàm) được tạo ra khi bạn gọi hàm do bạn tạo ra . Mỗi lần bạn gọi là hàm nó sẽ tạo ra một Function Execution Context mới.

Ví dụ :

Khi code của bạn chạy , JavaScriptEngine sẽ tạo Global Execution Context(Bối cảnh thực hiện toàn cầu) và đẩy nó vào Execution Context Stack (ngăn xếp của bối cảnh thực thi) trong mọi trường hợp thì JSE

=> Execution Context Stack là nơi bối cảnh thực thi toàn cầu và bối cảnh thực thi của hàm được xử lý và thao tác.

Trong ví dụ trên khi chúng ta gọi foo .
1. Global Execution Context đã bị tạm dừng do JavaScript là một môi trường đơn luồng , nó chỉ có thể chạy một tác vụ tại một thời điểm.

2. Sau đó JavaScript sẽ tạo một Funtion Execution Context mới cho thằng foo và đẩy nó vào Excution Context Stack khi hàm foo được thực thi chúng ta sẽ gọi hàm bar được định nghĩa bên trong hàm foo.

3 JavaScriptEngine đã tạm dừng bối cảnh thực thi  (như đã nói bên trên JavaScript là một môi trường đơn luồng tại một thời điểm nó chỉ thực hiện được 1 tác vụ)  trong hàm foo và tạo bối cảnh thực thi (Function Execution Context) mới cho hàm bar và tiếp tục đẩy nó vào ngăn xếp.

4. Sau khi hàm bar được thực hiện xong , nó sẽ được đẩy vào bên trong Execution Context Stack (ngăn xếp của bối cảnh thực thi) và chúng ta quay trở lại hàm foo và tiếp tục thực hiện các câu lệnh trong hàm này.

Làm thế nào để đẩy thằng Execution Context vào trong Execution Context Stack ?

Chúng ta đã hiểu sơ qua về cách JavaScript Execution Context hoạt động như thế nào , hãy cùng đi sâu vào vào Lexical Environment và giải thích cách chúng hoạt động.


Rất có thể lexical environment hoạt động trong việc lồng mã khi bạn viết một hàm vào bên trong hàm khác lúc này bạn có một hàm khác.

Không có gì đặc biệt trong đoạn mã này , mình khai báo một hàm được lồng bên trong một hàm khác . Ban đầu khi mã chạy Global Environment đã được tạo ra biến a và hàm foo đã được tạo ra và lưu trữ trong đó.

Khi mình gọi đến hàm foo() , một environtment (môi trường) mới (environment của foo) đã được tạo ra và lưu trữ biến b trong môi trường này , hàm bar bên trong hàm foo cũng có thể được truy suất vào b vì bar là một hàm bên trong môi trường của foo.


" Bất cứ khi nào chúng ta gọi một hàm , một FunctionExecutionContext sẽ được tạo ra và bị đẩy vào ExecutionContextStack cùng với một LexicalEnvironment mới được tạo ra."

Bên trong hàm bar chúng sẽ thử log ra xem các biến mình tạo có hiển thị hay không hay liệu chúng ta có thể truy cập nó trong môi trường mà mình khởi tạo chúng hay không ?

Khi biến c được ghi vào môi trường của hàm Bar , nó được hiển thị thành công rõ ràng vì nó nằm trong môi trường bar nhưng vấn đề phát sinh ở đây. Khi chúng ta logging ra biến b và biến a tại sao nó hiển thị thành công mặc dù khác môi trường ?

 Khi chúng ta gọi đến một biến trong  javascript sẽ tìm nó bên trong nội hàm nếu không tìm thấy nó sẽ tìm kiếm nó trong OuterEnvironment (môi trường bên ngoài) cho đến khi nó tìm thấy biến đó.


Trong trường hợp trên ở dòng log thứ 2 biến b không được khai báo trong môi trường của hàm bar , js đã tìm thấy biến b trong hàm foo vì hàm bar có tham chiến đến hàm foo nói cách khác mô trường của hàm foo (the foo function environment) không bật ra trong bối cảnh thực thi.
Khi biến a được khai báo và lưu trữ trong Global Execution Context  mọi người đều có thể truy cập nó.


Name/Value (Pairs) : cặp tên / giá trị

- Là một cái tên ánh xạ tới một giá trị duy nhất , cái tên có thể được định nghĩa nhiều lần nhưng trong một bối cảnh nhất định chỉ có duy nhất một giá trị ứng với tên đó thôi.


Object (Đối tượng)

- Là một tập hợp các cặp name/value đó là định nghĩa đơn giản nhất có thể của một đối tượng.

Nhận xét

Bài đăng phổ biến