Hoang  Ha

Hoang Ha

1660561620

Phạm Vi Hoạt Động Như Thế Nào Trong JavaScript

Phạm vi là một khái niệm quan trọng quản lý sự sẵn có của các biến. Phạm vi nằm ở phần đóng cơ sở, xác định ý tưởng về các biến cục bộ và toàn cục.

Nếu bạn muốn viết mã bằng JavaScript, thì việc hiểu phạm vi của các biến là điều bắt buộc.

Trong bài đăng này, tôi sẽ giải thích từng bước, chuyên sâu, cách phạm vi hoạt động trong JavaScript.

1. Phạm vi

Trước khi đi sâu vào phạm vi là gì, chúng ta hãy thử một thí nghiệm chứng minh phạm vi biểu hiện như thế nào.

Giả sử bạn xác định một biến message:

const message = 'Hello';
console.log(message); // 'Hello'

Sau đó, bạn có thể dễ dàng ghi lại biến này trong dòng tiếp theo sau khai báo. Không có câu hỏi nào ở đây.

Bây giờ, hãy di chuyển phần khai báo messagebên trong một ifkhối mã:

if (true) {
  const message = 'Hello';
}
console.log(message); // ReferenceError: message is not defined

Lần này, khi cố gắng ghi lại biến, JavaScript ném ReferenceError: message is not defined.

Tại sao nó xảy ra?

Khối ifmã tạo một phạm vi cho messagebiến. Và messagebiến chỉ có thể được truy cập trong phạm vi này.

Phạm vi JavaScript

Ở cấp độ cao hơn, khả năng truy cập của các biến bị giới hạn bởi phạm vi nơi chúng được tạo. Bạn có thể tự do truy cập vào biến được xác định trong phạm vi của nó. Nhưng bên ngoài phạm vi của nó, biến không thể truy cập được.

Bây giờ, hãy đặt ra một định nghĩa chung về phạm vi:

Phạm vi là một chính sách quản lý khả năng truy cập của các biến.

2. Phạm vi khối

Một khối mã trong JavaScript xác định phạm vi cho các biến được khai báo bằng cách sử dụng letconst:

if (true) {
  // "if" block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

Đầu tiên console.log(message)ghi chính xác biến vì messageđược truy cập từ phạm vi nơi nó được xác định.

Nhưng lỗi thứ hai console.log(message)gây ra lỗi tham chiếu vì messagebiến được truy cập bên ngoài phạm vi của nó: biến không tồn tại ở đây.

Khối mã của câu lệnh if, forcũng whiletạo ra một phạm vi.

Trong ví dụ sau, forvòng lặp xác định phạm vi:

for (const color of ['green', 'red', 'blue']) {
  // "for" block scope
  const message = 'Hi';
  console.log(color);   // 'green', 'red', 'blue'
  console.log(message); // 'Hi', 'Hi', 'Hi'
}
console.log(color);   // throws ReferenceError
console.log(message); // throws ReferenceError

colormessagecác biến tồn tại trong phạm vi của whilekhối mã.

Tương tự như cách khối mã của whilecâu lệnh tạo phạm vi cho các biến của nó:

while (/* condition */) {
  // "while" block scope
  const message = 'Hi';
  console.log(message); // 'Hi'
}
console.log(message); // => throws ReferenceError

messageđược định nghĩa bên trong while()phần thân, do đó messagechỉ có thể truy cập được bên trong while()phần thân.

Trong JavaScript, bạn có thể xác định các khối mã độc lập. Các khối mã độc lập cũng phân định phạm vi:

{
  // block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

2.1 var không phải là phạm vi khối

Như đã thấy trong phần trước, khối mã tạo một phạm vi cho các biến được khai báo bằng cách sử dụng constlet. Tuy nhiên, đó không phải là trường hợp của các biến được khai báo bằng cách sử dụng var.

Đoạn mã dưới đây khai báo một biến countbằng varcâu lệnh:

if (true) {
  // "if" block scope
  var count = 0;
  console.log(count); // 0
}
console.log(count); // 0

countnhư mong đợi, có thể truy cập được trong phạm vi của ifkhối mã. Tuy nhiên, countbiến cũng có thể truy cập bên ngoài!

Một khối mã không tạo phạm vi cho varcác biến, nhưng một thân hàm thì có . Đọc lại câu trước và cố gắng ghi nhớ nó.

Hãy tiếp tục về phạm vi chức năng trong phần tiếp theo.

3. Phạm vi chức năng

Một hàm trong JavaScript xác định phạm vi cho các biến được khai báo bằng cách sử dụng var, letconst.

Hãy khai báo một varbiến trong thân hàm:

function run() {
  // "run" function scope
  var message = 'Run, Forrest, Run!';
  console.log(message); // 'Run, Forrest, Run!'
}
run();
console.log(message); // throws ReferenceError

run()cơ quan chức năng tạo ra một phạm vi. Biến messagecó thể truy cập bên trong phạm vi hàm, nhưng không thể truy cập bên ngoài.

Tương tự như vậy, một thân hàm tạo ra một phạm vi letconstthậm chí cả các khai báo hàm.

function run() {
  // "run" function scope
  const two = 2;
  let count = 0;
  function run2() {}
  console.log(two);   // 2
  console.log(count); // 0
  console.log(run2);  // function
}
run();
console.log(two);   // throws ReferenceError
console.log(count); // throws ReferenceError
console.log(run2);  // throws ReferenceError

4. Phạm vi mô-đun

Mô-đun ES2015 cũng tạo một phạm vi cho các biến, hàm, lớp.

Mô-đun circlexác định một hằng số pi(đối với một số cách sử dụng nội bộ):

// "circle" module scope
const pi = 3.14159;
console.log(pi); // 3.14159
// Usage of pi

pibiến được khai báo trong phạm vi của circlemô-đun. Ngoài ra, biến pikhông được xuất ra khỏi mô-đun.

Sau đó, circlemô-đun được nhập:

import './circle';
console.log(pi); // throws ReferenceError

Không thể truy cập biến pibên ngoài circlemô-đun (trừ khi được xuất rõ ràng bằng cách sử dụng export).

Phạm vi mô-đun làm cho mô-đun được đóng gói. Mọi biến private (không được xuất) vẫn là chi tiết nội bộ của mô-đun và phạm vi mô-đun bảo vệ các biến này không bị truy cập từ bên ngoài.

Nhìn từ một góc độ khác, phạm vi là một cơ chế đóng gói cho các khối mã, chức năng và mô-đun.

5. Phạm vi có thể được lồng vào nhau

Một đặc tính thú vị của phạm vi là chúng có thể được lồng vào nhau.

Trong ví dụ sau, hàm run()tạo một phạm vi và bên trong một ifkhối mã điều kiện sẽ tạo một phạm vi khác:

function run() {
  // "run" function scope
  const message = 'Run, Forrest, Run!';
  if (true) {
    // "if" code block scope
    const friend = 'Bubba';
    console.log(message); // 'Run, Forrest, Run!'
  }
  console.log(friend); // throws ReferenceError
}
run();

ifphạm vi khối mã được lồng bên trong run()phạm vi chức năng. Phạm vi của bất kỳ loại nào (khối mã, chức năng, mô-đun) có thể được lồng vào nhau.

Phạm vi được chứa trong một phạm vi khác được đặt tên là phạm vi bên trong . Trong ví dụ, ifphạm vi khối mã là một phạm vi bên trong của phạm vi run()chức năng.

Phạm vi bao bọc một phạm vi khác được đặt tên là phạm vi bên ngoài . Trong ví dụ, run()phạm vi chức năng là phạm vi bên ngoài để ifmã phạm vi khối.

JavaScript lồng nhau phạm vi

Điều gì về khả năng truy cập của biến? Đây là một quy tắc đơn giản cần nhớ:

Phạm vi bên trong có thể truy cập các biến của phạm vi bên ngoài của nó .

messagebiến, là một phần của run()phạm vi chức năng (phạm vi bên ngoài), có thể truy cập bên trong ifphạm vi khối mã (phạm vi bên trong).

6. Phạm vi toàn cầu

Phạm vi toàn cầu là phạm vi ngoài cùng. Nó có thể truy cập được từ bất kỳ phạm vi bên trong (hay còn gọi là cục bộ ) nào.

Trong môi trường trình duyệt, phạm vi trên cùng của tệp JavaScript được tải bằng <script>thẻ là phạm vi toàn cầu:

<script src="myScript.js"></script>
// myScript.js
// "global" scope
let counter = 1;

Một biến được khai báo bên trong phạm vi toàn cục được đặt tên là biến toàn cục. Các biến toàn cục có thể truy cập được từ bất kỳ phạm vi nào.

Trong đoạn mã trước, counterlà một biến toàn cục. Biến này có thể được truy cập từ bất kỳ vị trí nào trong JavaScript của trang web.

Phạm vi toàn cầu là một cơ chế cho phép máy chủ của JavaScript (trình duyệt, Node) cung cấp các ứng dụng với các chức năng dành riêng cho máy chủ lưu trữ dưới dạng các biến toàn cục.

windowdocument, ví dụ, là các biến toàn cục do trình duyệt cung cấp. Trong môi trường Node, bạn có thể truy cập processđối tượng như một biến toàn cục.

7. Phạm vi từ vựng

Hãy xác định 2 hàm, có hàm innerFunc()được lồng vào bên trong outerFunc().

function outerFunc() {
  // the outer scope
  let outerVar = 'I am from outside!';
  function innerFunc() {
    // the inner scope
    console.log(outerVar); // 'I am from outside!'
  }
  return innerFunc;
}
const inner = outerFunc();
inner();

Nhìn vào dòng cuối cùng của đoạn mã inner(): việc gọi innerFunc()xảy ra bên ngoài outerFunc()phạm vi. Tuy nhiên, làm thế nào JavaScript hiểu rằng outerVarbên trong innerFunc()tương ứng với biến outerVarcủa outerFunc()?

Câu trả lời là do phạm vi từ vựng.

JavaScript thực hiện một cơ chế xác định phạm vi có tên là phạm vi từ vựng (hoặc phạm vi tĩnh). Phạm vi Lexical có nghĩa là khả năng truy cập của các biến được xác định tĩnh bởi vị trí của các biến trong phạm vi hàm lồng nhau: phạm vi hàm bên trong có thể truy cập các biến từ phạm vi hàm bên ngoài .

Một định nghĩa chính thức về phạm vi từ vựng:

Phạm vi từ vựng bao gồm các phạm vi bên ngoài được xác định tĩnh.

Trong ví dụ, phạm vi từ vựng của innerFunc()bao gồm phạm vi của outerFunc().

Hơn nữa, innerFunc()là một bao đóng vì nó nắm bắt biến outerVartừ phạm vi từ vựng.

Nếu bạn muốn nắm vững khái niệm đóng, tôi thực sự khuyên bạn nên đọc bài đăng của tôi Giải thích đơn giản về JavaScript Closures .

8. Biến cách ly

Một thuộc tính ngay lập tức của phạm vi phát sinh: phạm vi cô lập các biến. Và những phạm vi khác nhau tốt có thể có các biến có cùng tên.

Bạn có thể sử dụng lại các tên biến phổ biến ( ,,, countv.v. ) trong các phạm vi khác nhau mà không có xung đột .indexcurrentvalue

foo()bar()phạm vi hàm có các biến riêng nhưng được đặt tên giống nhau count:

function foo() {
  // "foo" function scope
  let count = 0;
  console.log(count); // 0
}
function bar() {
  // "bar" function scope
  let count = 1;
  console.log(count); // 1
}
foo();
bar();

9. Kết luận

Phạm vi là một chính sách quản lý sự sẵn có của các biến. Một biến được xác định bên trong một phạm vi chỉ có thể truy cập trong phạm vi đó, nhưng không thể truy cập bên ngoài.

Trong JavaScript, phạm vi được tạo bởi các khối mã, hàm, mô-đun.

Trong khi constletcác biến được xác định phạm vi bởi các khối mã, chức năng hoặc mô-đun, varcác biến chỉ được xác định phạm vi bởi các hàm hoặc mô-đun.

Các phạm vi có thể được lồng vào nhau. Bên trong phạm vi bên trong, bạn có thể truy cập các biến của phạm vi bên ngoài.

Phạm vi từ vựng bao gồm các phạm vi chức năng bên ngoài được xác định tĩnh. Bất kỳ hàm nào, bất kể nơi được thực thi, đều có thể truy cập các biến trong phạm vi từ vựng của nó (đây là khái niệm về sự đóng ).

Hy vọng rằng, bài viết của tôi đã giúp bạn hiểu rõ hơn về phạm vi!

Nguồn: https://dmitripavlutin.com/javascript-scope/

#javascript 

What is GEEK

Buddha Community

Phạm Vi Hoạt Động Như Thế Nào Trong JavaScript
Hoang  Ha

Hoang Ha

1660561620

Phạm Vi Hoạt Động Như Thế Nào Trong JavaScript

Phạm vi là một khái niệm quan trọng quản lý sự sẵn có của các biến. Phạm vi nằm ở phần đóng cơ sở, xác định ý tưởng về các biến cục bộ và toàn cục.

Nếu bạn muốn viết mã bằng JavaScript, thì việc hiểu phạm vi của các biến là điều bắt buộc.

Trong bài đăng này, tôi sẽ giải thích từng bước, chuyên sâu, cách phạm vi hoạt động trong JavaScript.

1. Phạm vi

Trước khi đi sâu vào phạm vi là gì, chúng ta hãy thử một thí nghiệm chứng minh phạm vi biểu hiện như thế nào.

Giả sử bạn xác định một biến message:

const message = 'Hello';
console.log(message); // 'Hello'

Sau đó, bạn có thể dễ dàng ghi lại biến này trong dòng tiếp theo sau khai báo. Không có câu hỏi nào ở đây.

Bây giờ, hãy di chuyển phần khai báo messagebên trong một ifkhối mã:

if (true) {
  const message = 'Hello';
}
console.log(message); // ReferenceError: message is not defined

Lần này, khi cố gắng ghi lại biến, JavaScript ném ReferenceError: message is not defined.

Tại sao nó xảy ra?

Khối ifmã tạo một phạm vi cho messagebiến. Và messagebiến chỉ có thể được truy cập trong phạm vi này.

Phạm vi JavaScript

Ở cấp độ cao hơn, khả năng truy cập của các biến bị giới hạn bởi phạm vi nơi chúng được tạo. Bạn có thể tự do truy cập vào biến được xác định trong phạm vi của nó. Nhưng bên ngoài phạm vi của nó, biến không thể truy cập được.

Bây giờ, hãy đặt ra một định nghĩa chung về phạm vi:

Phạm vi là một chính sách quản lý khả năng truy cập của các biến.

2. Phạm vi khối

Một khối mã trong JavaScript xác định phạm vi cho các biến được khai báo bằng cách sử dụng letconst:

if (true) {
  // "if" block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

Đầu tiên console.log(message)ghi chính xác biến vì messageđược truy cập từ phạm vi nơi nó được xác định.

Nhưng lỗi thứ hai console.log(message)gây ra lỗi tham chiếu vì messagebiến được truy cập bên ngoài phạm vi của nó: biến không tồn tại ở đây.

Khối mã của câu lệnh if, forcũng whiletạo ra một phạm vi.

Trong ví dụ sau, forvòng lặp xác định phạm vi:

for (const color of ['green', 'red', 'blue']) {
  // "for" block scope
  const message = 'Hi';
  console.log(color);   // 'green', 'red', 'blue'
  console.log(message); // 'Hi', 'Hi', 'Hi'
}
console.log(color);   // throws ReferenceError
console.log(message); // throws ReferenceError

colormessagecác biến tồn tại trong phạm vi của whilekhối mã.

Tương tự như cách khối mã của whilecâu lệnh tạo phạm vi cho các biến của nó:

while (/* condition */) {
  // "while" block scope
  const message = 'Hi';
  console.log(message); // 'Hi'
}
console.log(message); // => throws ReferenceError

messageđược định nghĩa bên trong while()phần thân, do đó messagechỉ có thể truy cập được bên trong while()phần thân.

Trong JavaScript, bạn có thể xác định các khối mã độc lập. Các khối mã độc lập cũng phân định phạm vi:

{
  // block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

2.1 var không phải là phạm vi khối

Như đã thấy trong phần trước, khối mã tạo một phạm vi cho các biến được khai báo bằng cách sử dụng constlet. Tuy nhiên, đó không phải là trường hợp của các biến được khai báo bằng cách sử dụng var.

Đoạn mã dưới đây khai báo một biến countbằng varcâu lệnh:

if (true) {
  // "if" block scope
  var count = 0;
  console.log(count); // 0
}
console.log(count); // 0

countnhư mong đợi, có thể truy cập được trong phạm vi của ifkhối mã. Tuy nhiên, countbiến cũng có thể truy cập bên ngoài!

Một khối mã không tạo phạm vi cho varcác biến, nhưng một thân hàm thì có . Đọc lại câu trước và cố gắng ghi nhớ nó.

Hãy tiếp tục về phạm vi chức năng trong phần tiếp theo.

3. Phạm vi chức năng

Một hàm trong JavaScript xác định phạm vi cho các biến được khai báo bằng cách sử dụng var, letconst.

Hãy khai báo một varbiến trong thân hàm:

function run() {
  // "run" function scope
  var message = 'Run, Forrest, Run!';
  console.log(message); // 'Run, Forrest, Run!'
}
run();
console.log(message); // throws ReferenceError

run()cơ quan chức năng tạo ra một phạm vi. Biến messagecó thể truy cập bên trong phạm vi hàm, nhưng không thể truy cập bên ngoài.

Tương tự như vậy, một thân hàm tạo ra một phạm vi letconstthậm chí cả các khai báo hàm.

function run() {
  // "run" function scope
  const two = 2;
  let count = 0;
  function run2() {}
  console.log(two);   // 2
  console.log(count); // 0
  console.log(run2);  // function
}
run();
console.log(two);   // throws ReferenceError
console.log(count); // throws ReferenceError
console.log(run2);  // throws ReferenceError

4. Phạm vi mô-đun

Mô-đun ES2015 cũng tạo một phạm vi cho các biến, hàm, lớp.

Mô-đun circlexác định một hằng số pi(đối với một số cách sử dụng nội bộ):

// "circle" module scope
const pi = 3.14159;
console.log(pi); // 3.14159
// Usage of pi

pibiến được khai báo trong phạm vi của circlemô-đun. Ngoài ra, biến pikhông được xuất ra khỏi mô-đun.

Sau đó, circlemô-đun được nhập:

import './circle';
console.log(pi); // throws ReferenceError

Không thể truy cập biến pibên ngoài circlemô-đun (trừ khi được xuất rõ ràng bằng cách sử dụng export).

Phạm vi mô-đun làm cho mô-đun được đóng gói. Mọi biến private (không được xuất) vẫn là chi tiết nội bộ của mô-đun và phạm vi mô-đun bảo vệ các biến này không bị truy cập từ bên ngoài.

Nhìn từ một góc độ khác, phạm vi là một cơ chế đóng gói cho các khối mã, chức năng và mô-đun.

5. Phạm vi có thể được lồng vào nhau

Một đặc tính thú vị của phạm vi là chúng có thể được lồng vào nhau.

Trong ví dụ sau, hàm run()tạo một phạm vi và bên trong một ifkhối mã điều kiện sẽ tạo một phạm vi khác:

function run() {
  // "run" function scope
  const message = 'Run, Forrest, Run!';
  if (true) {
    // "if" code block scope
    const friend = 'Bubba';
    console.log(message); // 'Run, Forrest, Run!'
  }
  console.log(friend); // throws ReferenceError
}
run();

ifphạm vi khối mã được lồng bên trong run()phạm vi chức năng. Phạm vi của bất kỳ loại nào (khối mã, chức năng, mô-đun) có thể được lồng vào nhau.

Phạm vi được chứa trong một phạm vi khác được đặt tên là phạm vi bên trong . Trong ví dụ, ifphạm vi khối mã là một phạm vi bên trong của phạm vi run()chức năng.

Phạm vi bao bọc một phạm vi khác được đặt tên là phạm vi bên ngoài . Trong ví dụ, run()phạm vi chức năng là phạm vi bên ngoài để ifmã phạm vi khối.

JavaScript lồng nhau phạm vi

Điều gì về khả năng truy cập của biến? Đây là một quy tắc đơn giản cần nhớ:

Phạm vi bên trong có thể truy cập các biến của phạm vi bên ngoài của nó .

messagebiến, là một phần của run()phạm vi chức năng (phạm vi bên ngoài), có thể truy cập bên trong ifphạm vi khối mã (phạm vi bên trong).

6. Phạm vi toàn cầu

Phạm vi toàn cầu là phạm vi ngoài cùng. Nó có thể truy cập được từ bất kỳ phạm vi bên trong (hay còn gọi là cục bộ ) nào.

Trong môi trường trình duyệt, phạm vi trên cùng của tệp JavaScript được tải bằng <script>thẻ là phạm vi toàn cầu:

<script src="myScript.js"></script>
// myScript.js
// "global" scope
let counter = 1;

Một biến được khai báo bên trong phạm vi toàn cục được đặt tên là biến toàn cục. Các biến toàn cục có thể truy cập được từ bất kỳ phạm vi nào.

Trong đoạn mã trước, counterlà một biến toàn cục. Biến này có thể được truy cập từ bất kỳ vị trí nào trong JavaScript của trang web.

Phạm vi toàn cầu là một cơ chế cho phép máy chủ của JavaScript (trình duyệt, Node) cung cấp các ứng dụng với các chức năng dành riêng cho máy chủ lưu trữ dưới dạng các biến toàn cục.

windowdocument, ví dụ, là các biến toàn cục do trình duyệt cung cấp. Trong môi trường Node, bạn có thể truy cập processđối tượng như một biến toàn cục.

7. Phạm vi từ vựng

Hãy xác định 2 hàm, có hàm innerFunc()được lồng vào bên trong outerFunc().

function outerFunc() {
  // the outer scope
  let outerVar = 'I am from outside!';
  function innerFunc() {
    // the inner scope
    console.log(outerVar); // 'I am from outside!'
  }
  return innerFunc;
}
const inner = outerFunc();
inner();

Nhìn vào dòng cuối cùng của đoạn mã inner(): việc gọi innerFunc()xảy ra bên ngoài outerFunc()phạm vi. Tuy nhiên, làm thế nào JavaScript hiểu rằng outerVarbên trong innerFunc()tương ứng với biến outerVarcủa outerFunc()?

Câu trả lời là do phạm vi từ vựng.

JavaScript thực hiện một cơ chế xác định phạm vi có tên là phạm vi từ vựng (hoặc phạm vi tĩnh). Phạm vi Lexical có nghĩa là khả năng truy cập của các biến được xác định tĩnh bởi vị trí của các biến trong phạm vi hàm lồng nhau: phạm vi hàm bên trong có thể truy cập các biến từ phạm vi hàm bên ngoài .

Một định nghĩa chính thức về phạm vi từ vựng:

Phạm vi từ vựng bao gồm các phạm vi bên ngoài được xác định tĩnh.

Trong ví dụ, phạm vi từ vựng của innerFunc()bao gồm phạm vi của outerFunc().

Hơn nữa, innerFunc()là một bao đóng vì nó nắm bắt biến outerVartừ phạm vi từ vựng.

Nếu bạn muốn nắm vững khái niệm đóng, tôi thực sự khuyên bạn nên đọc bài đăng của tôi Giải thích đơn giản về JavaScript Closures .

8. Biến cách ly

Một thuộc tính ngay lập tức của phạm vi phát sinh: phạm vi cô lập các biến. Và những phạm vi khác nhau tốt có thể có các biến có cùng tên.

Bạn có thể sử dụng lại các tên biến phổ biến ( ,,, countv.v. ) trong các phạm vi khác nhau mà không có xung đột .indexcurrentvalue

foo()bar()phạm vi hàm có các biến riêng nhưng được đặt tên giống nhau count:

function foo() {
  // "foo" function scope
  let count = 0;
  console.log(count); // 0
}
function bar() {
  // "bar" function scope
  let count = 1;
  console.log(count); // 1
}
foo();
bar();

9. Kết luận

Phạm vi là một chính sách quản lý sự sẵn có của các biến. Một biến được xác định bên trong một phạm vi chỉ có thể truy cập trong phạm vi đó, nhưng không thể truy cập bên ngoài.

Trong JavaScript, phạm vi được tạo bởi các khối mã, hàm, mô-đun.

Trong khi constletcác biến được xác định phạm vi bởi các khối mã, chức năng hoặc mô-đun, varcác biến chỉ được xác định phạm vi bởi các hàm hoặc mô-đun.

Các phạm vi có thể được lồng vào nhau. Bên trong phạm vi bên trong, bạn có thể truy cập các biến của phạm vi bên ngoài.

Phạm vi từ vựng bao gồm các phạm vi chức năng bên ngoài được xác định tĩnh. Bất kỳ hàm nào, bất kể nơi được thực thi, đều có thể truy cập các biến trong phạm vi từ vựng của nó (đây là khái niệm về sự đóng ).

Hy vọng rằng, bài viết của tôi đã giúp bạn hiểu rõ hơn về phạm vi!

Nguồn: https://dmitripavlutin.com/javascript-scope/

#javascript 

Rahul Jangid

1622207074

What is JavaScript - Stackfindover - Blog

Who invented JavaScript, how it works, as we have given information about Programming language in our previous article ( What is PHP ), but today we will talk about what is JavaScript, why JavaScript is used The Answers to all such questions and much other information about JavaScript, you are going to get here today. Hope this information will work for you.

Who invented JavaScript?

JavaScript language was invented by Brendan Eich in 1995. JavaScript is inspired by Java Programming Language. The first name of JavaScript was Mocha which was named by Marc Andreessen, Marc Andreessen is the founder of Netscape and in the same year Mocha was renamed LiveScript, and later in December 1995, it was renamed JavaScript which is still in trend.

What is JavaScript?

JavaScript is a client-side scripting language used with HTML (Hypertext Markup Language). JavaScript is an Interpreted / Oriented language called JS in programming language JavaScript code can be run on any normal web browser. To run the code of JavaScript, we have to enable JavaScript of Web Browser. But some web browsers already have JavaScript enabled.

Today almost all websites are using it as web technology, mind is that there is maximum scope in JavaScript in the coming time, so if you want to become a programmer, then you can be very beneficial to learn JavaScript.

JavaScript Hello World Program

In JavaScript, ‘document.write‘ is used to represent a string on a browser.

<script type="text/javascript">
	document.write("Hello World!");
</script>

How to comment JavaScript code?

  • For single line comment in JavaScript we have to use // (double slashes)
  • For multiple line comments we have to use / * – – * /
<script type="text/javascript">

//single line comment

/* document.write("Hello"); */

</script>

Advantages and Disadvantages of JavaScript

#javascript #javascript code #javascript hello world #what is javascript #who invented javascript

Minh  Nguyet

Minh Nguyet

1660556640

Chuyển đổi Kiểu Hoạt động Như Thế Nào Trong Javascript & Sử Dụng nó

Javascript là một ngôn ngữ được đánh máy yếu, có nghĩa là các kiểu dữ liệu khác nhau cho các biến và đối tượng không được chỉ định rõ ràng, nhưng được thực hiện ngầm bởi công cụ Javascript tại thời điểm biên dịch. Việc để lại nhiệm vụ quan trọng này cho công cụ Javascript đôi khi gây ra sự cố trong chương trình của chúng tôi mà chúng tôi thậm chí không nhận thức được. Đây là lý do tại sao điều quan trọng là phải biết chuyển đổi kiểu hoạt động như thế nào trong Javascript, đây là nguồn chính của các lỗi logic.

Trong bài này chúng ta sẽ tìm hiểu:

  • Chuyển đổi kiểu là gì?
  • Cách chuyển đổi kiểu nguyên thủy và kiểu đối tượng
  • Sự khác biệt giữa chuyển đổi kiểu ẩn và rõ ràng
  • Tại sao bạn nên sử dụng ===thay vì==
  • Thuật toán mà Javascript sử dụng để chuyển đổi các đối tượng thành nguyên thủy
  • Phương thức chuyển đổi đối tượng thành nguyên thủy là gì và cách chúng hoạt động

Chuyển đổi kiểu là gì?

Chuyển đổi kiểu như tên của nó chỉ đơn giản là chuyển đổi một giá trị từ kiểu này sang kiểu khác trong Javascript. Cả giá trị và đối tượng nguyên thủy đều là những ứng cử viên sáng giá cho việc chuyển đổi kiểu trong Javascript. Ví dụ:

20 + "twenty" // => "20 twenty": Number 20 converts to a string

"10" * "10" // => 100: Both strings convert to numbers

let y = 2 - "x" // n == NaN; String 'x' can't convert to a number

y + "values" // => "NaN values": NaN converts to string "NaN"

Javascript sử dụng toán tử so sánh bình đẳng nghiêm ngặt ( ===) và bình đẳng lỏng ( ==) để kiểm tra sự bình đẳng giữa hai giá trị, nhưng chuyển đổi kiểu chỉ xảy ra khi toán tử bình đẳng lỏng được sử dụng. Khi kiểm tra sự bình đẳng nghiêm ngặt bằng cách sử dụng ===, cả kiểu và giá trị của các biến chúng ta đang so sánh phải giống nhau.

10 === 10     // true

NaN === NaN   // false

Trong mã trước, số 10được so sánh với số 10và như mong đợi, biểu thức trả về true. Cả hai đều là số và có cùng giá trị 10. Ngoại lệ duy nhất cho quy tắc này là NaNgiá trị, một giá trị của kiểu số duy nhất trong Javascript. Hai NaNgiá trị không bao giờ bằng nhau.

Mặt khác, toán tử bình đẳng lỏng lẻo ( ==) là khá khác nhau. Khi được sử dụng để so sánh hai giá trị, hai giá trị chỉ được so sánh sau khi chúng đã được chuyển đổi thành một kiểu chung.

'20' == 20    //true

false == 0    // true

Các giá trị trong mã trước đó được chuyển đổi (cưỡng chế) thành một kiểu chung trước khi chúng được so sánh. Bất cứ khi nào chuyển đổi kiểu xảy ra trong Javascript, giá trị (nguyên thủy hoặc đối tượng) liên quan chỉ có thể được chuyển đổi thành chuỗi, số hoặc boolean. Mặc dù logic chuyển đổi cho nguyên thủy và đối tượng khác nhau, cả hai đều được chuyển đổi thành chỉ ba loại này.

Chuyển đổi loại rõ ràng

Khi nhà phát triển lựa chọn một cách có ý thức để chuyển đổi từ kiểu này sang kiểu khác, điều này được gọi là chuyển đổi kiểu rõ ràng (kiểu ép kiểu). Cách đơn giản nhất để thực hiện chuyển đổi kiểu rõ ràng là sử dụng Boolean(), Number()String()các hàm:

Number("5")     // 5
String(true)    // "true" 
Boolean([])     // true

Tất cả các giá trị ngoại trừ nullundefinedcó một toString()phương thức và kết quả của phương thức này thường giống với kết quả được trả về bởi String()hàm.

Chuyển đổi kiểu ngầm định

Chuyển đổi kiểu ngầm định được thực hiện bởi một số toán tử nhất định và đôi khi được sử dụng một cách rõ ràng cho mục đích chuyển đổi kiểu. Tuy nhiên, một chuyển đổi ngầm cũng có thể được kích hoạt bởi bối cảnh xung quanh. Ví dụ bằng cách sử dụng if (value) {...}, ở đây valueđược chuyển đổi thành boolean.

x + ""      // String(x)

2 + ""      // "2", String(2)

+x          // Number(x)

x-0         // Number(x)

!x          // Boolean(x)

!2          // false, Boolean(!2)

Các ! toán tử chuyển đổi toán hạng của nó thành boolean và phủ định nó.

Cách chuyển đổi các kiểu nguyên thủy

Hãy nhớ rằng, có hai loại trong Javascript: Nguyên thủy và Loại đối tượng. Hãy hiểu cách chuyển đổi kiểu hoạt động trong các kiểu nguyên thủy.

Việc chuyển đổi một giá trị nguyên thủy rất đơn giản, như đã thấy trong bảng trước. Công cụ Javascript sử dụng Boolean()Number()các String()hàm để chuyển đổi các giá trị nguyên thủy.

Chuyển đổi chuỗi

String(1)           // '1'
String("0")         // '0'
String("one")       // 'one'
String(true)        // 'true'
String(false)       // 'false'
String(null)        // 'null'
String(undefined)   // 'undefined' 
String()            // ''
String('')          // ''
String(' ')         // ' '

Chuyển đổi số

Number(1)           // 1
Number("0")         // 0
Number("one")       // NaN
Number(true)        // 1
Number(false)       // 0
Number(null)        // 0
Number(undefined)   // NaN 
Number()            // 0
Number('')          // 0
Number(' ')         // 0

Chuyển đổi Boolean

Boolean(1)          // true
Boolean(0)          // false
Boolean("one")      // true
Boolean(true)       // true
String(false)       // false
Boolean(null)       // false
Boolean(undefined)  // false
Boolean()           // false
Boolean('')         // false
Boolean(' ')        // true

Bảng sau đây tóm tắt chuyển đổi kiểu trong các kiểu nguyên thủy:

Giá trịChuyển đổi chuỗiChuyển đổi sốChuyển đổi Boolean
1“1”1true
0“0”0false
“1”“1”1true
“0”"0"0true
"một""one"NaNtrue
true"thật"1true
false"sai"0false
null"vô giá trị"0false
undefined"chưa xác định"NaNfalse
0false
’ ’’ ’0true

Cách các loại đối tượng được chuyển đổi

Chuyển đổi đối tượng thành nguyên thủy xảy ra trong bối cảnh một đối tượng được sử dụng thay vì giá trị nguyên thủy. Ví dụ, khi các đối tượng được thêm vào obj_1 + obj_2, rút ​​ngắn obj_1 - obj_2hoặc in bằng cách sử dụng alert(obj). Javascript sử dụng Toprimitivethuật toán để chuyển đổi đối tượng thành nguyên thủy. Thuật toán này cho phép bạn chọn cách một đối tượng sẽ được chuyển đổi (sử dụng một trong các phương pháp chuyển đổi đối tượng thành nguyên thủy ) tùy thuộc vào ngữ cảnh mà đối tượng đang được sử dụng (được xác định thông qua gợi ý ).

Về mặt khái niệm, [ToPrimitive]thuật toán có thể nói là tổng hợp của hai phần tổng hợp: Gợi ý và Phương pháp chuyển đổi đối tượng thành nguyên thủy.

1

Gợi ý

Gợi ý là các tín hiệu mà [ToPrimitive]thuật toán sử dụng để xác định đối tượng nên được chuyển đổi thành gì trong một ngữ cảnh cụ thể. Có ba biến thể:

  • "string": Trong ngữ cảnh mà một hoạt động mong đợi một chuỗi, nếu có thể chuyển đổi thành chuỗi, chẳng hạn như alert()hoặc hàm tích hợp sẵn String().
// output
alert(obj);

//output
String(obj)

// using object as a property key
anotherObj[obj] = 1000;
  • "default": Xảy ra trong một số trường hợp hiếm hoi, trong bối cảnh mà một toán tử không bày tỏ sự ưu tiên về loại giá trị nguyên thủy được mong đợi; tức là, nó "không chắc chắn" về loại mà bạn mong đợi.

Ví dụ: +toán tử nhị phân hoạt động với cả chuỗi (nối chúng) và số (thêm chúng), vì vậy đối tượng có thể được chuyển đổi thành cả chuỗi hoặc số trong trường hợp này. Hoặc khi một đối tượng được so sánh với một chuỗi, số hoặc ký hiệu bằng cách sử dụng ==toán tử bình đẳng lỏng lẻo.

// binary plus
let sum = obj1 + obj2;

// obj == string/number/symbol
if (obj == 10 ) { ... };

Tất cả các đối tượng dựng sẵn (ngoại trừ Ngày) thực hiện "default"gợi ý là "number". Ngày thực hiện "default"gợi ý là "string".

N / B : Chỉ có ba gợi ý. Thật đơn giản. Không có gợi ý "boolean" (tất cả các đối tượng đều đúng trong ngữ cảnh boolean) hoặc bất kỳ thứ gì khác. Và nếu chúng ta xử lý "default""number"giống như hầu hết các đối tượng tích hợp sẵn, thì chỉ có hai chuyển đổi.

Phương thức chuyển đổi đối tượng thành nguyên thủy

Sau khi [ToPrimitive]thuật toán đã xác định được giá trị nguyên thủy mà một đối tượng sẽ chuyển đổi thành dựa trên gợi ý. Đối tượng sau đó được chuyển đổi thành giá trị nguyên thủy bằng cách sử dụng các phương pháp chuyển đổi Đối tượng thành nguyên thủy.

Có ba biến thể:

  • toString / valueOf : Và toString()được valueOf()kế thừa bởi tất cả các đối tượng trong Javascript. Chúng được sử dụng cho mục đích duy nhất là chuyển đổi đối tượng thành nguyên thủy. Thuật [ToPrimitive]toán thử toString()phương pháp đầu tiên. Nếu phương thức được định nghĩa, nó trả về một giá trị nguyên thủy, thì Javascript sử dụng giá trị nguyên thủy (ngay cả khi nó không phải là một chuỗi!). Nếu toString()trả về một đối tượng hoặc không tồn tại, thì Javascript sẽ thử valueOf()phương thức. Nếu phương thức đó tồn tại và trả về một giá trị nguyên thủy, Javascript sẽ sử dụng giá trị đó. Nếu không, chuyển đổi không thành công với a TypeError.
  • toString -> valueOfcho gợi ý "chuỗi".
  • valueOf -> toStringnếu không thì.
let Person = {
  name: "Mary",
  age: 22,

  // for hint="string"
  toString() {
    return `{name: "${this.name}"}`;
  },

  // for hint="number" or "default"
  valueOf() {
    return this.age;
  }
};

alert(Person); // toString -> {name: "Mary"}
alert(+Person); // valueOf -> 22
alert(Person + 10); // valueOf -> 32

Trong mã trước, Persontrở thành một chuỗi hoặc số tự mô tả tùy thuộc vào ngữ cảnh chuyển đổi. Phương toString()thức được sử dụng để chuyển đổi cho hint="string"valueOf()được sử dụng theo cách khác ( "number" or "default").

Tuy nhiên, bạn có thể muốn xử lý tất cả các chuyển đổi của mình ở một nơi duy nhất. Trong trường hợp đó, bạn chỉ có thể triển khai toString()phương thức như sau:

let Person = {
  name: "Mary",

  toString() {
    return this.name;
  }
};

alert(Person); // toString -> Mary
alert(Person + 1000); // toString -> Mary1000
  • Symbol.toPrimitive : Không giống như các phương thức toString()valueOf()đã tồn tại từ thời cổ đại, Symbol.toPrimitiveSymbol là một Symbol nổi tiếng trong ES6. Nó cho phép bạn ghi đè chuyển đổi đối tượng thành nguyên thủy mặc định (trong đó thuật toán toString()valueOfphương thức được sử dụng [ToPrimitive]) trong Javascript và xác định cách bạn muốn các đối tượng được chuyển đổi thành giá trị nguyên thủy. Để làm điều này, hãy xác định một phương thức có tên tượng trưng như thế này:
obj[Symbol.toPrimitive] = function(hint) {
  // return a primitive value
  // hint = one of "string", "number", "default"
}

Ví dụ, ở đây Personđối tượng làm tương tự như trên bằng cách sử dụngSymbol.toPrimitive

let Person = {
  name: "Mary",
  age: 22,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.age;
  }
};

// conversions demo:
alert(Person); // hint: string -> {name: "Mary"}
alert(+Person); // hint: number -> 22
alert(Person + 10); // hint: default -> 32

Trong đoạn mã trước, chúng ta có thể thấy rằng một phương pháp Person[Symbol.toPrimitive]xử lý tất cả các trường hợp chuyển đổi.

N / B : Trong trường hợp không có Symbol.toPrimitivevalueOf(), toString()sẽ xử lý tất cả các chuyển đổi nguyên thủy.

Chuyển đổi đối tượng thành boolean

Tất cả các đối tượng đều chuyển đổi thành true trong Javascript — bao gồm cả đối tượng wrapper new Boolean(false)và mảng trống. Thuật toán đối tượng thành nguyên thủy không bắt buộc để chuyển đổi đối tượng thành boolean.

Chuyển đổi đối tượng thành chuỗi

Khi một đối tượng cần được chuyển đổi thành một chuỗi, Javascript trước tiên sẽ chuyển đổi nó thành một thuật toán nguyên thủy bằng cách sử dụng [ToPrimitive]thuật toán (gợi ý "string"), sau đó chuyển đổi nguyên thủy có nguồn gốc thành một chuỗi.

Ví dụ: nếu bạn truyền một đối tượng vào một hàm dựng sẵn yêu cầu một đối số chuỗi giống như String()hoặc khi các đối tượng được nội suy trong các ký tự mẫu .

Chuyển đổi đối tượng thành số

Khi một đối tượng cần được chuyển đổi thành một số, Javascript đầu tiên sẽ chuyển đổi nó thành một [ToPrimitive]thuật toán nguyên thủy bằng cách sử dụng (gợi ý "number"), sau đó chuyển đổi nguyên thủy có nguồn gốc thành một số. Các hàm và phương thức Javascript tích hợp mong đợi các đối số số chuyển đổi các đối số đối tượng thành số theo cách này, ví dụ Math():

Chuyển đổi toán tử trường hợp đặc biệt

Chuyển đổi kiểu cũng xảy ra khi toán hạng của các toán tử Javascript nhất định là một đối tượng, Chúng ta hãy xem xét chi tiết các toán tử này.

Toán tử : +Toán tử này thực hiện phép cộng số và nối chuỗi. nếu một trong các toán hạng của nó là một đối tượng, chúng được chuyển đổi thành các giá trị nguyên thủy bằng cách sử dụng [ToPrimitive]thuật toán (gợi ý "default"). Các kiểu của chúng được kiểm tra, sau khi chúng được chuyển đổi thành các giá trị nguyên thủy. Nếu một trong hai đối số là một chuỗi, nó sẽ chuyển đối số kia thành một chuỗi và nối các chuỗi. Nếu không, nó sẽ chuyển đổi cả hai đối số thành số và thêm chúng.

==Toán tử và : Các !==toán tử này thực hiện kiểm tra bình đẳng và bất bình đẳng một cách lỏng lẻo cho phép chuyển đổi kiểu. Nếu một toán hạng là một đối tượng và toán hạng kia là giá trị nguyên thủy, các toán tử này chuyển đổi đối tượng thành nguyên thủy bằng cách sử dụng [ToPrimitive]thuật toán (gợi ý "default") và sau đó so sánh hai giá trị nguyên thủy.

<,<=,>Toán tử quan hệ và : Các >=toán tử quan hệ so sánh mối quan hệ giữa hai giá trị và có thể được sử dụng để so sánh cả số và chuỗi. Nếu một trong hai toán hạng là một đối tượng, nó được chuyển đổi thành giá trị nguyên thủy bằng cách sử dụng [ToPrimitive]thuật toán (gợi ý "number"). Tuy nhiên, không giống như chuyển đổi đối tượng thành số, các giá trị nguyên thủy được trả về không được chuyển đổi thành số (vì chúng được so sánh và không được sử dụng).

Sự kết luận

Bây giờ bạn đã biết cách chuyển đổi kiểu hoạt động trong Javascript và cách sử dụng nó. Bạn tự tin hơn và biết rõ nơi sử dụng nó và khi nào Javascript chuyển đổi ngầm giữa các kiểu khác nhau (đặc biệt là chuyển đổi đối tượng thành nguyên thủy).

#javascript

Hire Dedicated JavaScript Developers -Hire JavaScript Developers

It is said that a digital resource a business has must be interactive in nature, so the website or the business app should be interactive. How do you make the app interactive? With the use of JavaScript.

Does your business need an interactive website or app?

Hire Dedicated JavaScript Developer from WebClues Infotech as the developer we offer is highly skilled and expert in what they do. Our developers are collaborative in nature and work with complete transparency with the customers.

The technology used to develop the overall app by the developers from WebClues Infotech is at par with the latest available technology.

Get your business app with JavaScript

For more inquiry click here https://bit.ly/31eZyDZ

Book Free Interview: https://bit.ly/3dDShFg

#hire dedicated javascript developers #hire javascript developers #top javascript developers for hire #hire javascript developer #hire a freelancer for javascript developer #hire the best javascript developers

Hong  Nhung

Hong Nhung

1587381540

Event Loop trong JavaScript là gì và hoạt động như thế nào?

Đặt vấn đề

Vài tháng trước, mình có một buổi presentation về Javascript core nên cũng có tìm hiểu qua về một số khái niệm cơ bản và hay ho như nhân V8 (Google), Event-Driven, Non-blocking I/O, Event Loop… những khái niệm giúp JS tận dụng sức mạnh của phần cứng.

Một trong khái niệm mình cảm thấy khá trừu tượng trong JavascriptEvent loop. Thật sự cảm thấy “khoai mì” để có thể hiểu chính xác Event loop trong Javascript làm gì? Rốt cục tất cả những thứ trên là cái gì? Hoạt động thế nào? và Tại sao nó mang lại lợi ích?

Bài viết này sẽ xoay quanh các vấn đề về Event loop trong Javascript, hy vọng có thể làm sáng tỏ cũng như giúp các bạn cảm thấy nó không còn phức tạp nữa.

Một số khái niệm cơ bản

Tất cả các ngôn ngữ lập trình đều được sinh ra để làm thứ ngôn ngữ giao tiếp giữa người và máy. Dù là ngôn ngữ gì đi chăng nữa thì cuối cùng vẫn phải dịch ra mã máy, được load lên memory, chạy từng dòng lệnh, ghi các dữ liệu tạm thời ra bộ nhớ, ổ đĩa rồi giao tiếp các thiết bị ngoại vi… Thế nên để cho tiện thì trước tiên, chúng ta cùng tìm hiểu một số khái niệm cơ bản nhé 😛😛

Stack

Stack là một vùng nhớ đặc biệt trên con chip máy tính phục vụ cho quá trình thực thi các dòng lệnh.

Dòng lệnh cụ thể ở đây là các hàm. Hàm chẳng qua là một nhóm các lệnh. Một chương trình thì gồm một nhóm các hàm phối hợp với nhau. Mỗi khi một hàm được gọi thì nó sẽ được đẩy vào một hàng đợi đặc biệt có tên là stack. Stack là một hàng đợi kiểu LIFO (Last In First Out), nghĩa là vào đầu tiên thì ra sau cùng.

Một hàm chỉ được lấy ra khỏi stack khi nó hoàn thành và return.

Một ví dụ cụ thể, xét trong một hàm Foo() có gọi một hàm khác ( hàm Bar()) thì trạng thái hiện tại của hàm Foo() được cất giữ trong stack và hàm Bar() sẽ được chèn vào stack. Vì đây là hàng đợi LIFO nên Bar() sẽ được xử lý trước Foo(). Khi Bar() xong và return thì mới đến lượt Foo() được xử lý. Khi Foo được xử lý xong và return thì Stack rỗng và sẽ đợi các hàm tiếp theo được đẩy vào.

Heap

Heap là vùng nhớ được dùng để chứa kết quả tạm phục vụ cho việc thực thi các hàm trong stack.

Heap càng lớn thì khả năng tính toán càng cao. Heap có thể được cấp phát tĩnh hoặc cấp phát động.

Event loop

Overview

Trước giờ vẫn nghe nói NodeJs có thể xử lý cả hàng ngàn request cùng một lúc mặc dù nó là kiểu single-thread. Nếu như ở PHP hay Java thì với mỗi một request sẽ sinh ra một thread để xử lý request đó (multi-thread), các thread hoạt động độc lập, được cấp bộ nhớ, giao tiếp ngoại vi và trả về kết quả. Vậy làm thế nào để NodeJs có thể xử lý cả ngàn request một lúc với chỉ một thread duy nhất?

Trên web browser thì trong khi fetch data từ các url thì người dùng vẫn có thể thực hiện các thao tác khác như click button và gõ vào các ô textbox. Lý do chúng ta có thể chạy song song được là trình duyệt không đơn giản chỉ là Runtime. Javascript Runtime chỉ làm mỗi lúc một việc nhưng trình duyệt cho chúng ta nhiều thứ khác. Tất cả là nhờ có các Web APIs làm việc hiệu quả với các threads và cơ chế hoạt động của Event loop.

Event loop là cơ chế giúp Javascript có thể thực hiện nhiều thao tác cùng một lúc (concurrency)

Tuy Js Runtime chỉ có một thread duy nhất nhưng các Web APIs giúp nó giao tiếp với thế giới multi-thread bên ngoài, tận dụng các con chip đa nhân vốn rất phổ biến hiện nay.
Web APIs giúp đẩy các job ra bên ngoài và chỉ tạo ra các sự kiện kèm theo các handler gắn với các sự kiện. Kể cả đối với NodeJs khi không có Web APIs thì nó vẫn có các cơ chế tương đương khác giúp đẩy job ra bên ngoài và chỉ quản lý các đầu việc.

Cơ chế quản lý theo đầu việc là bí kíp giúp JS Runtime có thể xử lý hàng ngàn tác vụ cùng một lúc

Hãy thử tưởng tượng rằng bạn đang ở một trang web và bấm vào một nút trên trang web, sau đó trang web bị treo. Bạn sẽ thử bấm vào các nút khác nhưng không được, các nút khác không hoạt động. Nguyên nhân của việc này (giả sử không có lỗi) là do các nút bấm sau đó kích hoạt các đoạn Javascript nhưng đã bị block.

Vậy Javascript xử lý một dòng lệnh tại cùng một thời điểm như thế nào? Và điều gì tạo nên tính chất Non-blocking? Mình cùng tìm hiểu thêm nào 😛😛

Event loop hoạt động như thế nào?

Đúng như cái tên thôi, Event loop có một vòng lặp vô tận trong Javascript Runtime (V8 trong Google Chrome) dùng để lắng nghe các Event.

Nhiệm vụ của Event loop rất đơn giản đó là đọc StackEvent Queue. Nếu nhận thấy Stack rỗng nó sẽ nhặt Event đầu tiên trong Event QueueHandler (callback hoặc listener) gắn với event đó và đẩy vào Stack.

Đặc điểm của việc thực thi hàm trong JS là sẽ chỉ dừng lại khi hàm return hoặc throw exception.

Điều này có nghĩa là trong khi hàm đang chạy thì sẽ không có một hàm khác được chạy, dữ liệu tạm của hàm cũng sẽ không bị thay đổi bởi một hàm khác hay cũng không bị dừng lại cho đến khi hoàn thành (ngoại trừ yield trong ES6).

Ngoài stack ra, JS Runtime còn thao tác với một callback queue hay event queue. Event queue này khác với stack ở chỗ nó là queue kiểu FIFO (First In First Out).

Mỗi khi có một Event được tạo ra, ví dụ user click vào một Button thì một Event sẽ được đẩy vào Event queue cùng với một handler (event listener) gắn với nó. Nếu một Event không có listener thì nó sẽ bị mất và không được đẩy vào Event queue.

Để cho dễ hình dung cách thức hoạt động của Event Loop ta lấy một ví dụ như sau:

const fs = require('fs');

function someAsyncOperation(callback) {
  const startCallback = Date.now();
  // giả sử đọc file hết 98ms
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(function logInfo() => {
  const delay = Date.now() - timeoutScheduled;
  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// đọc file xong sẽ tiếp tục chờ thêm 10ms
someAsyncOperation(function readFileAsync() => {
  // chờ 10ms
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

Flow của đoạn script này sẽ như sau:

  • Đầu tiên phần khai báo biến và hàm sẽ được chạy nhưng không được đẩy vào stack (vì hàm chưa được gọi mà^^).

  • Tiếp đó, setTimeout() sẽ được đẩy vào stack và thực hiện.
    Hàm này không có trong Javascript Runtime V8 đâu nhé, nó được cung cấp bởi Browser, hỗ trợ Javascript Runtime.

    • Lúc này, nó sẽ khởi tạo một bộ đếm Timer() có trong web APIs (nghĩa là khi setTimeout() được gọi, bản thân nó đã chạy xong luôn rồi và sẽ được lấy ra khỏi stack).
      Bây giờ, tới Timer(), trong 100s tới, nó không thể “chọt chẹt” vào đoạn script, cũng ko thể “chọt chẹt” vào Stack. Kể cả setTimeout(cb, 0). Bởi vì nếu nó làm vậy thì stack sẽ loạn lên mất. Đó là lý do có Task queue (Callback queue).

    • Bất kì web APIs nào cũng sẽ đưa callback vào Task queue khi nó hoàn thành. Đó chính là Event loop đã định nghĩa ở trên.

Công việc của Event loop là theo dõi stack và ngó qua Task queue, nếu stack trống thì lấy callback trong task queue đẩy vào stack

  • Sau đúng 100ms thì nó sẽ đẩy logInfo() (là một callback hoặc có thể gọi là một event listener cũng được) vào Event Queue.

  • Kế đến sẽ chạy hàm someAsyncOperation() và đẩy vào stack, vì hàm này async và có callback readFileAsync() nên readFileAsync() được đẩy luôn vào Event Queue mà không phải chờ như setTimeout để hứng sự kiện đọc xong file (sau 98ms).

  • Để ý là Stack LIFO nên someAsyncOperation() sẽ nằm dưới cùng còn Event Queue FIFO nên readFileAsync sẽ nằm trên cùng.

  • Sau khi readFileAsync() được đẩy vào Event Queue thì someAsyncOperation() return và được lấy ra khỏi Stack. Lúc này Stack không có gì nên Event Queue sẽ được đọc, nên nhớ là Event Queue chỉ được đọc khi Stack trống rỗng. readFileAsync() sẽ được đẩy vào Event Queue trước vì nó chỉ mất có 98ms trong khi logInfo() thì phải chờ 100ms. readFileAsync() này sẽ được lấy khỏi Event Queue và đẩy vào stack để chạy.

  • readFileAsync() sẽ gặp vòng while và dừng ở đó 10ms. Vậy tổng cộng hàm đọc file sẽ mất 105ms để hoàn thành. Nhưng ở giây thứ 100 thì logInfo() được đẩy vào Event Queue (lúc này đã rỗng) trong khi readFileAsync thì còn phải mất thêm 8ms nữa mới hoàn thành. Vì cơ chế của Javascript là chạy đến khi hoàn thành mới thôi nên logInfo() không có cách nào để dừng readFileAsync() lại để chiếm quyền điều khiển, trừ khi trong readFileAsync() có lệnh yield. Sau 108ms thì readFileAsync() return và được lấy ra khỏi Stack.

  • Một lần nữa Stack lại trống và logInfo() được đẩy vào Stack. Như vậy logInfo() sẽ phải đợi tổng cộng 108ms để được chạy, chứ không phải 100ms như dự tính.

    Do đó, tham số thứ 2 của setTimeout là thời gian tối thiểu để một Event được đẩy vào Stack và chạy chứ không phải là thời gian chính xác nó sẽ được chạy.

Phía trên mình có đề cập tới yield():

Giả sử bạn có một đoạn code jQuery như sau :

$('#button_1').click(function yield() {
  console.log('Ouch!');
});

Event sẽ được đẩy vào Event Queue khi Bar()Foo() return và được lấy ra khỏi Stack thì yield sẽ được đẩy vào Stack với tham số là DOM Element xảy ra sự kiện click.

Cơ chế run-to-completion của Javascript có một điểm bất lợi đó là nếu một hàm chạy quá lâu hoặc bị vòng lặp vô tận thì sẽ không có hàm nào được chạy nữa, kết quả là Browser sẽ bị đơ, không phản ứng với các sự kiện như click chuột … Ví dụ :

function foo() {
   console.log('i am foo!');
   foo();
}

foo();

Hàm đệ quy không điểm dừng Foo() sẽ liên tục đẩy foo vào stack cho đến khi đầy, và bạn đoán xem lúc này chúng ta sẽ có cái vấn đề mà hàng ngày các đều được tìm kiếm Stack Overflow 😄😄

Để tránh tình trạng Browser bị treo vì lỗi lập trình thì các Browser sẽ throw exception trong trường hợp này :

MAXIMUM CALL STACK SIZE EXCEEDED.

Fun fact

Hầu hết các thao tác trong Javascript đều là bất đồng bộ nhưng có một số ngoại lệ thú vị như hàm alert (hàm này là của Browser API, không có trong NodeJs). Khi hàm này được chạy thì bạn không thể thực hiện một thao tác nào khác ngoài click OK.

Kết

Trên đây là những keynote mình cảm thấy tâm đắc khi tìm hiểu về Event loop . Mong rằng bài viết này có thể mang lại giá trị cho các bạn.

Từ giờ khi nghe đồng nghiệp hay nói những câu như “đừng chặn event loop”, “đảm bảo code phải chạy mượt 60fps nhé”, “dĩ nhiên là nó sẽ không chạy, là một hàm callback bất đồng bộ”… thì sẽ không phải hoang mang nữa nhé 😄😄😄

Cảm ơn các bạn đã đọc bài chia sẻ của mình.

Happy coding !

#javascript #web-development