1660561620
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.
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 message
bên trong một if
khố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 if
mã tạo một phạm vi cho message
biến. Và message
biến chỉ có thể được truy cập trong phạm vi này.
Ở 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.
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 let
và const
:
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ì message
biế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
, for
cũng while
tạo ra một phạm vi.
Trong ví dụ sau, for
vò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
color
và message
các biến tồn tại trong phạm vi của while
khối mã.
Tương tự như cách khối mã của while
câ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 đó message
chỉ 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
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 const
và let
. 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 count
bằng var
câu lệnh:
if (true) {
// "if" block scope
var count = 0;
console.log(count); // 0
}
console.log(count); // 0
count
như mong đợi, có thể truy cập được trong phạm vi của if
khối mã. Tuy nhiên, count
biế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 var
cá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.
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
, let
và const
.
Hãy khai báo một var
biế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 message
có 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 let
và const
thậ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
Mô-đun ES2015 cũng tạo một phạm vi cho các biến, hàm, lớp.
Mô-đun circle
xá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
pi
biến được khai báo trong phạm vi của circle
mô-đun. Ngoài ra, biến pi
không được xuất ra khỏi mô-đun.
Sau đó, circle
mô-đun được nhập:
import './circle';
console.log(pi); // throws ReferenceError
Không thể truy cập biến pi
bên ngoài circle
mô-đ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.
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 if
khố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();
if
phạ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ụ, if
phạ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 để if
mã phạm vi khối.
Đ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ó .
message
biế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 if
phạm vi khối mã (phạm vi bên trong).
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, counter
là 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.
window
và document
, 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.
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 outerVar
bên trong innerFunc()
tương ứng với biến outerVar
củ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 outerVar
từ 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 .
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 ( ,,, count
v.v. ) trong các phạm vi khác nhau mà không có xung đột .indexcurrentvalue
foo()
và 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();
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 const
và let
cá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, var
cá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!
1660561620
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.
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 message
bên trong một if
khố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 if
mã tạo một phạm vi cho message
biến. Và message
biến chỉ có thể được truy cập trong phạm vi này.
Ở 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.
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 let
và const
:
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ì message
biế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
, for
cũng while
tạo ra một phạm vi.
Trong ví dụ sau, for
vò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
color
và message
các biến tồn tại trong phạm vi của while
khối mã.
Tương tự như cách khối mã của while
câ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 đó message
chỉ 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
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 const
và let
. 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 count
bằng var
câu lệnh:
if (true) {
// "if" block scope
var count = 0;
console.log(count); // 0
}
console.log(count); // 0
count
như mong đợi, có thể truy cập được trong phạm vi của if
khối mã. Tuy nhiên, count
biế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 var
cá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.
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
, let
và const
.
Hãy khai báo một var
biế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 message
có 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 let
và const
thậ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
Mô-đun ES2015 cũng tạo một phạm vi cho các biến, hàm, lớp.
Mô-đun circle
xá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
pi
biến được khai báo trong phạm vi của circle
mô-đun. Ngoài ra, biến pi
không được xuất ra khỏi mô-đun.
Sau đó, circle
mô-đun được nhập:
import './circle';
console.log(pi); // throws ReferenceError
Không thể truy cập biến pi
bên ngoài circle
mô-đ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.
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 if
khố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();
if
phạ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ụ, if
phạ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 để if
mã phạm vi khối.
Đ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ó .
message
biế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 if
phạm vi khối mã (phạm vi bên trong).
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, counter
là 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.
window
và document
, 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.
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 outerVar
bên trong innerFunc()
tương ứng với biến outerVar
củ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 outerVar
từ 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 .
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 ( ,,, count
v.v. ) trong các phạm vi khác nhau mà không có xung đột .indexcurrentvalue
foo()
và 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();
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 const
và let
cá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, var
cá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!
1622207074
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.
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.
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.
In JavaScript, ‘document.write‘ is used to represent a string on a browser.
<script type="text/javascript">
document.write("Hello World!");
</script>
<script type="text/javascript">
//single line comment
/* document.write("Hello"); */
</script>
#javascript #javascript code #javascript hello world #what is javascript #who invented javascript
1660556640
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:
===
thay vì==
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ố 10
và 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à NaN
giá trị, một giá trị của kiểu số duy nhất trong Javascript. Hai NaN
giá 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.
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()
và String()
các hàm:
Number("5") // 5
String(true) // "true"
Boolean([]) // true
Tất cả các giá trị ngoại trừ null
và undefined
có 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 đượ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ó.
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()
và Number()
các String()
hàm để chuyển đổi các giá trị nguyên thủy.
String(1) // '1'
String("0") // '0'
String("one") // 'one'
String(true) // 'true'
String(false) // 'false'
String(null) // 'null'
String(undefined) // 'undefined'
String() // ''
String('') // ''
String(' ') // ' '
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
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ỗi | Chuyển đổi số | Chuyển đổi Boolean |
---|---|---|---|
1 | “1” | 1 | true |
0 | “0” | 0 | false |
“1” | “1” | 1 | true |
“0” | "0" | 0 | true |
"một" | "one" | NaN | true |
true | "thật" | 1 | true |
false | "sai" | 0 | false |
null | "vô giá trị" | 0 | false |
undefined | "chưa xác định" | NaN | false |
” | ” | 0 | false |
’ ’ | ’ ’ | 0 | true |
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_2
hoặc in bằng cách sử dụng alert(obj)
. Javascript sử dụng Toprimitive
thuậ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.
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"
và "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.
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()
đượ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 -> valueOf
cho gợi ý "chuỗi".valueOf -> toString
nế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, Person
trở 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"
và 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
toString()
và valueOf()
đã tồn tại từ thời cổ đại, Symbol.toPrimitive
Symbol 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()
và valueOf
phươ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.toPrimitive
và valueOf()
, toString()
sẽ xử lý tất cả các chuyển đổi nguyên thủy.
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.
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 .
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 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).
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
1616670795
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
1587381540
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 Javascript
là Event 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.
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
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 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.
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 😛😛
Đú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 Stack
và Event Queue
. Nếu nhận thấy Stack
rỗng nó sẽ nhặt Event đầu tiên trong Event Queue
và Handler
(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ặcthrow 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õistack
và ngó quaTask queue
, nếustack
trống thì lấycallback
trongtask queue
đẩy vàostack
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()
và 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.
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.
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