034.2 Bài 1
Chứng chỉ: |
Web Development Essentials |
---|---|
Phiên bản: |
1.0 |
Chủ đề: |
034 Lập trình JavaScript |
Mục tiêu: |
034.2 Cấu trúc Dữ liệu trong JavaScript |
Bài học: |
1 trên 1 |
Giới thiệu
Giống như ngôn ngữ tự nhiên, ngôn ngữ lập trình đại diện cho hiện thực thông qua các ký hiệu được kết hợp thành các câu lệnh có ý nghĩa. Hiện thực được biểu thị bằng ngôn ngữ lập trình là tài nguyên của máy, chẳng hạn như hoạt động của bộ xử lý, thiết bị và bộ nhớ.
Trong số vô số các ngôn ngữ lập trình, mỗi ngôn ngữ đều áp dụng một mô hình để biểu diễn thông tin. JavaScript áp dụng các quy ước điển hình của ngôn ngữ cấp cao (trong đó hầu hết các chi tiết như việc phân bổ bộ nhớ đều được ngầm xác định) cho phép lập trình viên tập trung vào mục đích của tệp lệnh trong ngữ cảnh của ứng dụng.
Ngôn ngữ Cấp cao
Các ngôn ngữ cấp cao cung cấp các quy tắc trừu tượng giúp lập trình viên có thể viết ít mã hơn để diễn đạt một ý tưởng. JavaScript cung cấp các cách thuận tiện để sử dụng bộ nhớ máy tính thông qua các khái niệm lập trình giúp đơn giản hóa việc viết các mã lặp lại và thường là đủ cho mục đích của nhà phát triển web.
Note
|
Mặc dù có thể sử dụng các cơ chế chuyên dụng để truy cập bộ nhớ một cách chi tiết nhưng các loại dữ liệu đơn giản hơn mà chúng ta sẽ xem xét sau đây lại được sử dụng phổ biến hơn. |
Các hoạt động điển hình trong ứng dụng web bao gồm việc yêu cầu dữ liệu thông qua một số lệnh JavaScript và lưu trữ chúng để xử lý và trình bày cho người dùng. Việc lưu trữ này khá linh hoạt trong JavaScript với các định dạng lưu trữ phù hợp cho từng mục đích sử dụng.
Khai báo Hằng và Biến
Việc khai báo các hằng số và biến để chứa dữ liệu là nền tảng của tất cả các ngôn ngữ lập trình. JavaScript áp dụng quy ước của hầu hết các ngôn ngữ lập trình là gán giá trị cho hằng số hoặc biến bằng cú pháp name = value
. Hằng số hoặc biến ở bên trái sẽ nhận giá trị ở bên phải. Tên hằng số hoặc tên biến phải bắt đầu bằng một chữ cái hoặc dấu gạch dưới.
Loại dữ liệu được lưu trữ trong biến không cần phải được chỉ định vì JavaScript là một ngôn ngữ gõ động. Loại của biến sẽ được suy ra từ giá trị được gán cho nó. Tuy nhiên, sẽ thuận tiện hơn khi ta chỉ định các thuộc tính cụ thể trong phần khai báo để đảm bảo được một kết quả như mong đợi.
Note
|
TypeScript là một ngôn ngữ lấy cảm hứng từ JavaScript. Nó giống như ngôn ngữ cấp thấp và cho phép ta khai báo các biến cho các loại dữ liệu cụ thể. |
Hằng số
Hằng số (constant) là một ký hiệu được gán ngay khi chương trình bắt đầu và sẽ không bao giờ thay đổi. Các hằng số sẽ rất hữu ích trong việc chỉ định các giá trị cố định, chẳng hạn như xác định hằng số PI
là 3.14159265 hoặc COMPANY_NAME
để giữ tên công ty của bạn.
Ví dụ như trong một ứng dụng web, có một máy khách nhận thông tin thời tiết từ một máy chủ từ xa. Lập trình viên có thể quyết định rằng địa chỉ máy chủ phải là hằng số bởi nó sẽ không thay đổi trong quá trình thực thi ứng dụng. Tuy nhiên, thông tin nhiệt độ có thể thay đổi với mỗi dữ liệu mới đến từ máy chủ.
Khoảng thời gian giữa (interval) các truy vấn được thực hiện cho máy chủ cũng có thể được xác định bởi một hằng số và có thể được truy vấn từ bất kỳ phần nào của chương trình:
const update_interval = 10;
function setup_app(){
console.log("Update every " + update_interval + "minutes");
}
Khi được gọi, hàm setup_app()
sẽ hiển thị thông báo Update every 10 minutes
(Cập nhật 10 phút một lần) trên bảng điều khiển. Thuật ngữ const
được đặt trước tên update_interval
đảm bảo rằng giá trị của nó sẽ được giữ nguyên trong toàn bộ quá trình thực thi tệp lệnh. Nếu ta muốn đặt lại giá trị của một hằng số, hệ thống sẽ xuất lỗi TypeError: Assignment to constant variable
(Gán cho biến không đổi).
Biến
Nếu không có thuật ngữ const
, JavaScript sẽ tự động giả định rằng update_interval
là một biến (variable) và giá trị của nó sẽ có thể được sửa đổi. Điều này tương đương với việc khai báo biến rõ ràng với var
:
var update_interval;
update_interval = 10;
function setup_app(){
console.log("Update every " + update_interval + "minutes");
}
Hãy lưu ý rằng mặc dù biến update_interval
được xác định bên ngoài hàm nhưng nó lại được truy cập từ bên trong hàm. Bất kỳ hằng số hoặc biến nào được khai báo bên ngoài các hàm hoặc khối mã được xác định bởi dấu ngoặc nhọn ({}
) đều sẽ có phạm vi toàn cục - nghĩa là nó có thể được truy cập từ bất kỳ phần nào của mã. Điều ngược lại thì lại không đúng: một hằng số hoặc biến được khai báo bên trong một hàm có phạm vi cục bộ thì nó chỉ có thể được truy cập từ bên trong chính hàm đó. Các khối mã được phân định bằng dấu ngoặc nhọn (chẳng hạn như các khối được đặt trong cấu trúc quyết định if
hoặc các vòng lặp for
) sẽ phân định phạm vi của các hằng số chứ không phải các biến được khai báo là var
. Ví dụ như mã sau đây sẽ được coi là hợp lệ:
var success = true;
if ( success == true )
{
var message = "Transaction succeeded";
var retry = 0;
}
else
{
var message = "Transaction failed";
var retry = 1;
}
console.log(message);
Câu lệnh console.log(message)
có thể truy cập vào biến message
dù nó đã được khai báo trong khối mã của câu lệnh`if`. Điều tương tự sẽ không xảy ra nếu message
là hằng số như trong ví dụ sau:
var success = true;
if ( success == true )
{
const message = "Transaction succeeded";
var retry = 0;
}
else
{
const message = "Transaction failed";
var retry = 1;
}
console.log(message);
Trong trường hợp này, một thông báo lỗi loại ReferenceError: message is not defined
(Lỗi tham chiếu: thông báo không được xác định) sẽ được đưa ra và tệp lệnh sẽ bị dừng. Mặc dù trông có vẻ giống một hạn chế, việc giới hạn phạm vi của các biến và hằng số có thể giúp ta tránh nhầm lẫn giữa thông tin được xử lý trong phần nội dung của tệp lệnh và trong các khối mã khác nhau của nó. Vì lý do này, các biến được khai báo bằng let
thay vì var
cũng sẽ bị giới hạn phạm vi bởi các khối được phân định bằng dấu ngoặc nhọn. Có những khác biệt nhỏ khác giữa việc khai báo một biến với var
và với let
, nhưng điều quan trọng nhất là liên quan đến phạm vi của biến (như chúng ta đang thảo luận ở đây).
Các loại Giá trị
Trong hầu hết mọi trường hợp, lập trình viên sẽ không cần phải lo lắng về loại dữ liệu được lưu trữ trong một biến vì JavaScript sẽ tự động xác định nó là một trong các loại nguyên thủy trong quá trình gán giá trị cho biến lần đầu. Tuy nhiên, một số thao tác có thể sẽ chỉ dành riêng cho một loại dữ liệu này hay một loại dữ liệu khác và có thể dẫn đến lỗi khi bị sử dụng một cách tùy ý. Ngoài ra, JavaScript cũng cung cấp các loại có cấu trúc cho phép ta kết hợp nhiều loại nguyên thủy thành một.
Các loại Nguyên thủy
Các loại nguyên thủy tương ứng với các biến truyền thống chỉ lưu trữ một giá trị. Các loại sẽ được xác định hoàn toàn; vì vậy, toán tử typeof
có thể được sử dụng để xác định loại giá trị nào sẽ được lưu trữ trong một biến:
console.log("Undefined variables are of type", typeof variable);
{
let variable = true;
console.log("Value `true` is of type " + typeof variable);
}
{
let variable = 3.14159265;
console.log("Value `3.14159265` is of type " + typeof variable);
}
{
let variable = "Text content";
console.log("Value `Text content` is of type " + typeof variable);
}
{
let variable = Symbol();
console.log("A symbol is of type " + typeof variable);
}
Tệp lệnh này sẽ hiển thị trên bảng điều khiển loại biến nào được sử dụng trong từng trường hợp:
undefined variables are of type undefined Value `true` is of type boolean Value `3.114159265` is of type number Value `Text content` is of type string A symbol is of type symbol
Hãy lưu ý rằng dòng đầu tiên đã cố gắng tìm một loại biến không được khai báo. Điều này đã khiến biến đã cho được coi là không xác định (undefined
). Loại ký hiệu (symbol
) là loại nguyên thủy ít trực quan nhất. Mục đích của nó là cung cấp một tên thuộc tính duy nhất trong một đối tượng khi không cần xác định một tên thuộc tính cụ thể. Một đối tượng là một trong những cấu trúc dữ liệu mà chúng ta sẽ xem xét tiếp theo.
Loại có Cấu trúc
Trong khi các loại nguyên thủy là đủ để viết các tiến trình đơn giản thì việc chỉ sử dụng chúng trong các ứng dụng phức tạp lại có nhiều hạn chế. Ví dụ như một ứng dụng thương mại điện tử sẽ khó viết hơn nhiều bởi lập trình viên sẽ cần tìm cách lưu trữ danh sách các mục và giá trị tương ứng chỉ sử dụng các biến loại nguyên thủy.
Loại có cấu trúc sẽ đơn giản hóa việc nhóm các thông tin có cùng bản chất thành một biến duy nhất. Ví dụ như danh sách các mặt hàng trong giỏ hàng có thể được lưu trữ trong một biến duy nhất kiểu mảng:
let cart = ['Milk', 'Bread', 'Eggs'];
Như được minh họa trong ví dụ, một mảng các mục sẽ được chỉ định bằng dấu ngoặc vuông. Ví dụ đã điền vào mảng ba giá trị chuỗi ký tự, do đó mà nó sử dụng dấu nháy đơn. Các biến cũng có thể được sử dụng như các mục trong một mảng; nhưng trong trường hợp đó, chúng phải được chỉ định mà không có dấu ngoặc kép. Số lượng phần tử trong một mảng có thể được truy vấn bằng thuộc tính độ dài (length
):
let cart = ['Milk', 'Bread', 'Eggs'];
console.log(cart.length);
Số 3
sẽ được hiển thị trong đầu ra của bảng điều khiển. Các mục mới có thể được thêm vào mảng bằng phương thức push()
:
cart.push('Candy');
console.log(cart.length);
Lần này, số được hiển thị là 4
. Mỗi mục trong danh sách có thể được truy cập thông qua chỉ số số học của nó, bắt đầu bằng 0
:
console.log(cart[0]);
console.log(cart[3]);
Đầu ra hiển thị trên bảng điều khiển sẽ là:
Milk Candy
Giống như việc có thể sử dụng push()
để thêm một phần tử, ta cũng có thể sử dụng pop()
để xóa phần tử cuối cùng khỏi một mảng.
Các giá trị được lưu trữ trong một mảng không nhất thiết phải cùng một kiểu (ví dụ như ta có thể lưu trữ số lượng của từng mặt hàng ngay bên cạnh nó). Một danh sách mua sắm giống như danh sách trong ví dụ trên có thể được xây dựng như sau:
let cart = ['Milk', 1, 'Bread', 4, 'Eggs', 12, 'Candy', 2];
// Item indexes are even
let item = 2;
// Quantities indexes are odd
let quantity = 3;
console.log("Item: " + cart[item]);
console.log("Quantity: " + cart[quantity]);
Đầu ra sẽ hiển thị trên bảng điều khiển sau khi chạy mã này là:
Item: Bread Quantity: 4
Như có thể đã nhận thấy, việc kết hợp tên của các mục với số lượng tương ứng của chúng trong một mảng có thể không phải là một ý tưởng hay vì mối quan hệ giữa chúng không rõ ràng trong cấu trúc dữ liệu và sẽ rất dễ bị lỗi (do con người). Ngay cả khi dùng một mảng cho tên và một mảng khác cho số lượng, việc duy trì tính toàn vẹn của danh sách cũng sẽ cần phải rất cẩn thận và sẽ không được hiệu quả. Trong những tình huống này, giải pháp thay thế tốt nhất là sử dụng cấu trúc dữ liệu phù hợp hơn: một đối tượng.
Trong JavaScript, cấu trúc dữ liệu kiểu đối tượng sẽ cho phép ta liên kết các đặc tính với một biến. Ngoài ra, không giống như một mảng, các phần tử tạo nên một đối tượng sẽ không có thứ tự cố định. Ví dụ: một mặt hàng trong danh sách mua sắm có thể là một đối tượng có các đặc tính name
và quantity
(số lượng):
let item = { name: 'Milk', quantity: 1 };
console.log("Item: " + item.name);
console.log("Quantity: " + item.quantity);
Ví dụ này cho thấy rằng một đối tượng có thể được xác định bằng dấu ngoặc nhọn ({}
), trong đó mỗi cặp đặc tính/giá trị sẽ được phân tách bằng dấu hai chấm, và các đặc tính sẽ được phân tách bằng dấu phẩy. Ta có thể truy cập đăc tính ở định dạng biến.đặc tính như trong item.name
để đọc và gán giá trị mới. Đầu ra hiển thị trên bảng điều khiển sau khi chạy mã này sẽ là:
Item: Milk Quantity: 1
Cuối cùng, mỗi đối tượng đại diện cho một mặt hàng có thể được đưa vào mảng danh sách mua sắm. Điều này có thể được thực hiện trực tiếp khi tạo danh sách:
let cart = [{ name: 'Milk', quantity: 1 }, { name: 'Bread', quantity: 4 }];
Như trước đây, một đối tượng mới đại diện cho một mục có thể được thêm vào mảng sau:
cart.push({ name: 'Eggs', quantity: 12 });
Các mục trong danh sách hiện sẽ được truy cập theo chỉ mục và tên đặc tính của chúng:
console.log("Third item: " + cart[2].name);
console.log(cart[2].name + " quantity: " + cart[2].quantity);
Đầu ra hiển thị trên bảng điều khiển sau khi chạy mã này sẽ là:
third item: eggs Eggs quantity: 12
Cấu trúc dữ liệu sẽ cho phép lập trình viên giữ cho mã của họ có tổ chức và dễ bảo trì hơn, cho dù là bởi tác giả ban đầu hay bởi các lập trình viên khác trong nhóm. Ngoài ra, nhiều kết quả đầu ra từ các hàm JavaScript sẽ ở dưới dạng có cấu trúc và cần được lập trình viên xử lý đúng cách.
Toán tử
Cho đến nay, chúng ta mới chỉ thấy cách gán giá trị cho các biến mới được tạo. Đơn giản như vậy, bất kỳ chương trình nào cũng sẽ thực hiện một số thao tác khác trên các giá trị của biến. JavaScript cung cấp một số loại toán tử có thể tác động trực tiếp lên giá trị của một biến hoặc lưu trữ kết quả của phép toán trong một biến mới.
Hầu hết các toán tử đều hướng tới các phép toán số học. Ví dụ: để tăng số lượng của một mặt hàng trong danh sách mua sắm, chỉ cần sử dụng toán tử cộng +
:
item.quantity = item.quantity + 1;
Đoạn mã sau sẽ in giá trị của item.quantity
trước và sau khi thực hiện phép cộng. Đừng nhầm lẫn về vai trò của các dấu cộng trong đoạn mã. Các câu lệnh console.log
sử dụng dấu cộng để kết hợp hai chuỗi.
let item = { name: 'Milk', quantity: 1 };
console.log("Item: " + item.name);
console.log("Quantity: " + item.quantity);
item.quantity = item.quantity + 1;
console.log("New quantity: " + item.quantity);
Đầu ra hiển thị trên bảng điều khiển sau khi chạy mã này sẽ là:
Item: Milk Quantity: 1 New quantity: 2
Hãy lưu ý rằng giá trị được lưu trữ trước đó trong item.quantity
được sử dụng làm toán hạng của phép cộng: item.quantity = item.quantity + 1
. Chỉ sau khi thao tác hoàn tất, giá trị trong item.quantity
mới được cập nhật cùng với kết quả của phép tính. Loại phép toán số học liên quan đến giá trị hiện tại của biến mục tiêu này là khá phổ biến; do đó, ta có các toán tử tốc ký cho phép viết cùng một phép toán ở định dạng rút gọn:
item.quantity += 1;
Các phép toán cơ bản khác cũng có các toán tử tốc ký tương đương:
-
a = a - b
tương đương vớia -= b
. -
a = a * b
tương đương vớia *= b
. -
a = a / b
tương đương vớia /= b
.
Đối với phép cộng và phép trừ, ta có sẵn định dạng thứ ba khi toán hạng thứ hai chỉ có một đơn vị:
-
a = a + 1
tương đương vớia++
. -
a = a - 1
tương đương vớia--
.
Ta có thể kết hợp nhiều toán tử trong cùng một thao tác và kết quả có thể sẽ được lưu trữ trong một biến mới. Ví dụ: câu lệnh sau tính tổng giá của một mặt hàng cộng với chi phí vận chuyển:
let total = item.quantity * 9.99 + 3.15;
Thứ tự thực hiện các phép toán sẽ tuân theo thứ tự ưu tiên truyền thống: đầu tiên thực hiện các phép toán nhân và chia, sau đó mới thực hiện các phép toán cộng và trừ. Các toán tử có cùng mức độ ưu tiên được thực thi theo thứ tự xuất hiện trong biểu thức và từ trái sang phải. Để ghi đè thứ tự ưu tiên mặc định, chúng ta sẽ sử dụng dấu ngoặc đơn như trong a * (b + c)
.
Trong một số trường hợp, kết quả của một phép toán thậm chí không cần được lưu trữ trong một biến. Đây là trường hợp khi ta muốn đánh giá kết quả của một biểu thức trong câu lệnh if
:
if ( item.quantiy % 2 == 0 )
{
console.log("Quantity for the item is even");
}
else
{
console.log("Quantity for the item is odd");
}
Toán tử %
(modulo) sẽ trả về số dư của phép chia toán hạng thứ nhất cho toán hạng thứ hai. Trong ví dụ này, câu lệnh if
sẽ kiểm tra xem số dư của phép chia item.quantity
cho 2
có bằng 0 hay không, nghĩa là item.quantity
có phải là bội số của 2 hay không.
Khi một trong các toán hạng của toán tử +
là một chuỗi thì các toán tử khác sẽ bị cưỡng chế thành chuỗi và kết quả sẽ cho ra phép nối các chuỗi. Trong các ví dụ trước, loại phép tính này được sử dụng để nối các chuỗi và biến thành đối số của câu lệnh console.log
.
Việc chuyển đổi tự động này có thể không phải là hành vi được mong đợi. Ví dụ: một giá trị do người dùng cung cấp trong trường biểu mẫu có thể được xác định là một chuỗi, nhưng thực sự thì nó lại là một giá trị số. Trong những trường hợp như thế này, trước tiên, biến phải được chuyển đổi thành một số bằng hàm Number()
:
sum = Number(value1) + value2;
Hơn nữa, điều quan trọng ở đây là phải xác minh rằng người dùng đã cung cấp một giá trị hợp lệ trước khi tiếp tục thao tác. Trong JavaScript, một biến không có giá trị được gán sẽ chứa giá trị null
. Điều này cho phép lập trình viên sử dụng câu lệnh quyết định, chẳng hạn như if ( value1 == null )
, để kiểm tra xem một biến có được gán giá trị hay không, bất kể loại giá trị được gán cho biến đó là gì.
Bài tập Hướng dẫn
-
Mảng là một cấu trúc dữ liệu có trong một số ngôn ngữ lập trình, một số trong đó chỉ cho phép các mảng có các mục cùng một loại. Trong trường hợp của JavaScript, có thể xác định được một mảng với các mục thuộc các loại khác nhau hay không?
-
Dựa trên ví dụ
let item = { name: 'Milk', quantity: 1 }
đối với một đối tượng trong danh sách mua sắm, làm cách nào để khai báo đối tượng này để bao gồm cả giá của mặt hàng? -
Chỉ với một dòng mã, có những cách nào để cập nhật giá trị của một biến thành một nửa giá trị hiện tại của nó?
Bài tập Mở rộng
-
Trong đoạn mã sau, giá trị nào sẽ được hiển thị trong đầu ra của bảng điều khiển?
var value = "Global"; { value = "Location"; } console.log(value);
-
Điều gì sẽ xảy ra khi một hoặc nhiều toán hạng tham gia vào phép toán nhân là một chuỗi?
-
Làm cách nào để có thể xóa mục
Eggs
khỏi mảngcart
được khai báo vớilet cart = ['Milk', 'Bread', 'Eggs']
?
Tóm tắt
Bài học này đã nói về cách sử dụng cơ bản của hằng số và biến trong JavaScript. JavaScript là một ngôn ngữ gõ động; vì vậy, lập trình viên không cần chỉ định loại biến trước khi gán giá trị cho nó. Tuy nhiên, điều quan trọng là phải biết về các loại nguyên thủy của ngôn ngữ để đảm bảo kết quả chính xác cho các phép toán cơ bản. Hơn nữa, các cấu trúc dữ liệu như mảng và đối tượng sẽ kết hợp các loại nguyên thủy và cho phép lập trình viên xây dựng các biến tổng hợp và phức tạp hơn. Bài học này đã đi qua các khái niệm và quy trình sau:
-
Hằng và biến
-
Phạm vi của biến
-
Khai báo biến với
var
vàlet
-
Các loại nguyên thủy
-
Toán tử số học
-
Mảng và đối tượng
-
Cưỡng chế và chuyển đổi kiểu
Đáp án Bài tập Hướng dẫn
-
Mảng là một cấu trúc dữ liệu có trong một số ngôn ngữ lập trình, một số trong đó chỉ cho phép các mảng có các mục cùng một loại. Trong trường hợp của JavaScript, có thể xác định được một mảng với các mục thuộc các loại khác nhau hay không?
Có. Trong JavaScript, ta có thể xác định mảng với các mục thuộc các loại nguyên thủy khác nhau, chẳng hạn như chuỗi và số.
-
Dựa trên ví dụ
let item = { name: 'Milk', quantity: 1 }
đối với một đối tượng trong danh sách mua sắm, làm cách nào để khai báo đối tượng này để bao gồm cả giá của mặt hàng?let item = { name: 'Milk', quantity: 1, price: 4.99 };
-
Chỉ với một dòng mã, có những cách nào để cập nhật giá trị của một biến thành một nửa giá trị hiện tại của nó?
Người ta có thể sử dụng chính biến đó làm toán hạng,
value = value / 2
hoặc toán tử tốc ký/=
:value /= 2
.
Đáp án Bài tập Mở rộng
-
Trong đoạn mã sau, giá trị nào sẽ được hiển thị trong đầu ra của bảng điều khiển?
var value = "Global"; { value = "Location"; } console.log(value);
Location
-
Điều gì sẽ xảy ra khi một hoặc nhiều toán hạng tham gia vào phép toán nhân là một chuỗi?
JavaScript sẽ gán giá trị
NaN
(Not a Number - Không phải là Số) cho kết quả để cho biết rằng phép toán không hợp lệ. -
Làm cách nào để có thể xóa mục
Eggs
khỏi mảngcart
được khai báo vớilet cart = ['Milk', 'Bread', 'Eggs']
?Mảng trong Javascript có phương thức
pop()
để loại bỏ mục cuối cùng trong danh sách:cart.pop()
.