034.3 Bài 2
Chứng chỉ: |
Web Development Essentials |
---|---|
Phiên bản: |
1.0 |
Chủ đề: |
034 Lập trình JavaScript |
Mục tiêu: |
034.3 Cấu trúc và Hàm Điều khiển trong JavaScript |
Bài học: |
2 trên 2 |
Giới thiệu
Ngoài những hàm tích hợp sẵn tiêu chuẩn do ngôn ngữ JavaScript cung cấp, nhà phát triển có thể viết thêm các hàm tùy chỉnh của riêng họ để ánh xạ đầu vào thành đầu ra phù hợp với nhu cầu của ứng dụng. Các hàm tùy chỉnh về cơ bản là một tập hợp các câu lệnh được đóng gói để sử dụng ở những nơi khác như một phần của biểu thức.
Việc sử dụng các hàm là một cách tốt để tránh việc viết mã trùng lặp, bởi chúng có thể được gọi từ các vị trí khác nhau trong suốt chương trình. Ngoài ra, việc nhóm các câu lệnh trong các hàm sẽ tạo điều kiện thuận lợi cho việc liên kết các hành động tùy chỉnh với các sự kiện - đây là khía cạnh trọng tâm trong lập trình JavaScript.
Xác định một Hàm
Khi một chương trình phát triển, việc tổ chức các chức năng của nó sẽ trở nên khó khăn hơn nếu không sử dụng hàm. Mỗi hàm có một phạm vi biến riêng; vì vậy, các biến được xác định bên trong một hàm sẽ chỉ khả dụng bên trong hàm đó. Do đó, chúng sẽ không bị trộn lẫn với các biến từ các hàm khác. Các biến toàn cục vẫn có thể truy cập được từ bên trong các hàm, nhưng cách tốt hơn để gửi các giá trị đầu vào tới một hàm là thông qua tham số hàm. Ví dụ: chúng ta sẽ xây dựng trình xác thực số nguyên tố từ bài học trước:
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
Nếu sau đó ta cần kiểm tra xem một số có phải là số nguyên tố hay không, ta sẽ cần phải lặp lại mã đã được viết. Phương pháp này không được khuyến khích vì bất kỳ sửa đổi hoặc cải tiến nào đối với mã gốc sẽ cần được sao chép thủ công ở mọi nơi mà mã đã được sao chép vào. Ngoài ra, mã lặp lại sẽ tạo ra gánh nặng cho trình duyệt và mạng và có thể sẽ làm chậm quá trình hiển thị trang web. Thay vì làm điều này, ta nên chuyển các câu lệnh thích hợp sang một hàm:
// A naive prime number tester function
function test_prime(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
Khai báo hàm sẽ bắt đầu bằng câu lệnh function
, theo sau là tên của hàm và các tham số của hàm. Tên của hàm cũng phải tuân theo các quy tắc giống như tên của biến. Các tham số của hàm - hay còn được gọi là đối số hàm (arguments) - được phân tách bằng dấu phẩy và được đặt trong dấu ngoặc đơn.
Tip
|
Việc liệt kê các đối số trong phần khai báo hàm là không bắt buộc. Các đối số được truyền cho một hàm có thể được truy xuất từ một đối tượng |
Trong ví dụ này, hàm test_prime
chỉ có một đối số: đối số candidate
- ứng viên số nguyên tố sẽ được kiểm tra. Các đối số của hàm hoạt động giống như các biến, nhưng giá trị của chúng sẽ được gán bởi câu lệnh gọi hàm. Ví dụ: câu lệnh test_prime(231)
sẽ gọi hàm test_prime
và gán giá trị 231 cho đối số candidate
; sau đó, giá trị này sẽ có sẵn bên trong phần thân của hàm giống như một biến thông thường.
Nếu câu lệnh gọi sử dụng các biến đơn giản cho các tham số của hàm, giá trị của chúng sẽ được sao chép vào các đối số của hàm. Quy trình này (việc sao chép các giá trị của tham số được sử dụng trong câu lệnh gọi sang tham số được sử dụng bên trong hàm) được gọi là truyền đối số theo giá trị. Bất kỳ sửa đổi nào được thực hiện đối với đối số của hàm sẽ không ảnh hưởng đến biến ban đầu được sử dụng trong câu lệnh gọi. Tuy nhiên, nếu câu lệnh gọi sử dụng các đối tượng phức tạp làm đối số (nghĩa là một đối tượng có các thuộc tính và phương thức gắn liền với nó) cho các tham số của hàm, chúng sẽ được truyền dưới dạng tham chiếu và hàm sẽ có thể sửa đổi đối tượng ban đầu được sử dụng trong câu lệnh gọi.
Các đối số được truyền theo giá trị cũng như các biến được khai báo bên trong hàm sẽ không hiển thị với bên ngoài, nghĩa là phạm vi của chúng sẽ bị giới hạn trong phần thân của hàm nơi chúng được khai báo. Tuy nhiên, các hàm thường được sử dụng để tạo ra một số đầu ra có thể hiển thị bên ngoài hàm. Để chia sẻ một giá trị với hàm gọi của nó, một hàm sẽ xác định câu lệnh return
.
Chẳng hạn, hàm test_prime
trong ví dụ trước đã trả về giá trị của biến is_prime
. Do đó, hàm có thể thay thế biến ở bất kỳ đâu mà nó sẽ được sử dụng trong ví dụ ban đầu:
// The number we want to evaluate
let candidate = 231;
// Display the result in the console window
if ( test_prime(candidate) )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
Câu lệnh return
, đúng như tên gọi của nó, sẽ trả về quyền điều khiển cho hàm gọi. Do đó, dù câu lệnh return
được đặt ở đâu trong hàm thì bất cứ thứ gì theo sau nó cũng sẽ không được thực thi. Một hàm có thể chứa nhiều câu lệnh return
. Phương pháp này có thể sẽ hữu ích nếu một số lệnh nằm trong các khối câu lệnh có điều kiện để hàm có thể hoặc không thể thực thi một câu lệnh return
cụ thể trong mỗi lần chạy.
Một số hàm có thể sẽ không trả về giá trị; vì vậy, câu lệnh return
không phải là bắt buộc. Ví dụ như các câu lệnh bên trong của hàm sẽ được thực thi bất kể nó có ở đó hay không. Vì vậy, các hàm cũng có thể được sử dụng để thay đổi giá trị của các biến toàn cục hoặc nội dung của các đối tượng được truyền bởi tham chiếu. Mặc dù vậy, nếu hàm không có câu lệnh return
thì giá trị trả về mặc định của nó sẽ được đặt thành undefined
- một biến được đặt trước, không có giá trị và không thể ghi được.
Biểu thức Hàm
Trong JavaScript, các hàm cũng chỉ là một loại đối tượng. Do đó, hàm cũng có thể được sử dụng trong tệp lệnh giống như biến. Đặc điểm này trở nên rõ ràng hơn khi hàm được khai báo bằng cú pháp thay thế được gọi là biểu thức hàm:
let test_prime = function(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
Sự khác biệt duy nhất giữa ví dụ này và việc khai báo hàm trong ví dụ trước là ở dòng đầu tiên: let test_prime = function(candidate)
thay vì function test_prime(candidate)
. Trong một biểu thức hàm, tên test_prime
sẽ được sử dụng cho đối tượng chứa hàm chứ không phải để đặt tên cho chính hàm đó. Các hàm được định nghĩa trong các biểu thức hàm sẽ được gọi giống như các hàm được định nghĩa bằng cú pháp khai báo. Tuy nhiên, trong khi các hàm đã khai báo có thể được gọi trước hoặc sau khi khai báo, các biểu thức hàm chỉ có thể được gọi sau khi khởi tạo. Giống như với biến, việc gọi một hàm được xác định trong một biểu thức trước khi khởi tạo nó sẽ gây ra lỗi tham chiếu.
Đệ quy Hàm
Ngoài việc thực thi các câu lệnh và gọi các hàm được tích hợp sẵn, các hàm tùy chỉnh cũng có thể gọi các hàm tùy chỉnh khác, kể cả chính bản thân nó. Việc gọi một hàm từ chính nó được gọi là đệ quy hàm. Tùy thuộc vào loại vấn đề mà bạn đang muốn giải quyết, việc sử dụng các hàm đệ quy có thể sẽ đơn giản hơn việc sử dụng các vòng lặp lồng nhau để thực hiện các tác vụ lặp đi lặp lại.
Cho đến nay, chúng ta đã biết cách sử dụng một hàm để kiểm tra xem một số đã cho có phải là số nguyên tố hay không. Bây giờ, giả sử ta muốn tìm số nguyên tố tiếp theo sau một số đã cho. Ta có thể sử dụng vòng lặp while
để tăng số lượng "ứng viên" và viết một vòng lặp lồng nhau để tìm các thừa số nguyên cho "ứng viên" đó:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "true" keeps the loop going until a prime is found
while ( true )
{
// Auxiliary control variable
let is_prime = true;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Test the next candidate
is_prime = false;
break;
}
}
// End loop and return candidate if it is prime
if ( is_prime )
{
return candidate;
}
// If prime not found yet, try the next odd number
candidate = candidate + 2;
}
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Hãy lưu ý rằng chúng ta cần phải sử dụng một điều kiện không đổi cho vòng lặp while
(biểu thức true
bên trong dấu ngoặc đơn) và biến phụ trợ is_prime
để hệ thống biết khi nào nên dừng vòng lặp. Mặc dù giải pháp này là đúng nhưng việc sử dụng các vòng lặp lồng nhau không được tinh tế bằng việc sử dụng đệ quy để thực hiện cùng một tác vụ:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Call the next_prime function recursively, this time
// using the failed candidate as the argument.
return next_prime(candidate);
}
}
// "candidate" is not divisible by any integer factor other
// than 1 and itself, therefore it is a prime number.
return candidate;
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Cả hai phiên bản của next_prime
đều sẽ trả về số nguyên tố tiếp theo sau số được cung cấp làm đối số duy nhất của nó (from
). Phiên bản đệ quy cũng giống như phiên bản kia, tức là nó sẽ bắt đầu bằng cách kiểm tra các trường hợp đặc biệt (nghĩa là các số nhỏ hơn hoặc bằng hai). Sau đó, nó sẽ tăng ứng viên và bắt đầu tìm kiếm bất kỳ thừa số nguyên nào bằng vòng lặp for
(hãy lưu ý rằng vòng lặp while
không còn ở đó nữa). Tại thời điểm đó, số nguyên tố chẵn duy nhất đã được kiểm tra; do đó, ứng viên và các thừa số có thể có của nó đã được tăng thêm hai (một số lẻ cộng với hai là số lẻ tiếp theo).
Chỉ có hai cách thoát khỏi vòng lặp for
trong ví dụ này. Nếu tất cả các thừa số có thể được kiểm tra và không thừa số nào có số dư bằng 0 khi chia ứng viên, vòng lặp for
sẽ hoàn tất và hàm sẽ trả về ứng viên là số nguyên tố tiếp theo sau from
. Mặt khác, nếu factor
là một thừa số nguyên của candidate
(candidate % factor == 0
), giá trị được trả về sẽ đến từ hàm next_prime
được gọi theo cách đệ quy với candidate
tăng lên như tham số from
của nó. Các phiên gọi next_prime
sẽ được xếp chồng lên nhau cho đến khi một ứng viên cuối cùng xuất hiện không có thừa số nguyên nào. Sau đó, phiên bản next_prime
cuối cùng chứa số nguyên tố sẽ trả nó về phiên bản next_prime
trước đó và lần lượt xuống phiên bản next_prime
đầu tiên. Mặc dù mỗi lần gọi hàm sẽ sử dụng cùng một tên cho các biến, các phiên gọi vẫn sẽ hoàn toàn độc lập với nhau để các biến của chúng được giữ tách biệt trong bộ nhớ.
Bài tập Hướng dẫn
-
Các nhà phát triển có thể giảm thiểu loại tổn hao nào bằng việc sử dụng các hàm?
-
Sự khác biệt giữa các đối số hàm được truyền theo giá trị và các đối số hàm được truyền theo tham chiếu là gì?
-
Giá trị nào sẽ được sử dụng làm đầu ra của một hàm tùy chỉnh nếu nó không có câu lệnh trả về?
Bài tập Mở rộng
-
Nguyên nhân có thể xảy ra của Lỗi Tham chiếu chưa được xử lý (Uncaught Reference Error) được phát sinh khi gọi một hàm được khai báo bằng cú pháp biểu thức là gì?
-
Hãy viết một hàm có tên
multiples_of
nhận ba đối số:factor
,from
vàto
. Bên trong hàm, hãy sử dụng lệnhconsole.log()
để in tất cả bội số củafactor
nằm giữafrom
vàto
.
Tóm tắt
Bài học này đã trình bày về cách viết các hàm tùy chỉnh trong mã JavaScript. Các hàm tùy chỉnh cho phép nhà phát triển chia ứng dụng thành “các khối” mã có thể tái sử dụng, giúp việc viết và duy trì các chương trình lớn trở nên dễ dàng hơn. Bài học đã đi qua các khái niệm và quy trình sau:
-
Cách xác định hàm tùy chỉnh: khai báo hàm và biểu thức hàm.
-
Sử dụng tham số làm đầu vào hàm.
-
Sử dụng câu lệnh
return
để đặt đầu ra của hàm. -
Hàm đệ quy.
Đáp án Bài tập Hướng dẫn
-
Các nhà phát triển có thể giảm thiểu loại tổn hao nào bằng việc sử dụng các hàm?
Các hàm cho phép chúng ta sử dụng lại mã và tạo điều kiện bảo trì mã. Tệp lệnh nhỏ hơn cũng giúp tiết kiệm bộ nhớ và thời gian tải xuống.
-
Sự khác biệt giữa các đối số hàm được truyền theo giá trị và các đối số hàm được truyền theo tham chiếu là gì?
Khi được truyền theo giá trị, đối số sẽ được sao chép vào hàm và hàm sẽ không thể sửa đổi biến ban đầu trong câu lệnh gọi. Khi được truyền theo tham chiếu, hàm có thể thao tác với biến ban đầu được sử dụng trong câu lệnh gọi.
-
Giá trị nào sẽ được sử dụng làm đầu ra của một hàm tùy chỉnh nếu nó không có câu lệnh trả về?
Giá trị được trả về sẽ được đặt thành
undefined
.
Đáp án Bài tập Mở rộng
-
Nguyên nhân có thể xảy ra của Lỗi Tham chiếu chưa được xử lý (Uncaught Reference Error) được phát sinh khi gọi một hàm được khai báo bằng cú pháp biểu thức là gì?
Hàm đã được gọi từ trước khi khai báo trong tệp tệp lệnh.
-
Hãy viết một hàm có tên
multiples_of
nhận ba đối số:factor
,from
vàto
. Bên trong hàm, hãy sử dụng lệnhconsole.log()
để in tất cả bội số củafactor
nằm giữafrom
vàto
.function multiples_of(factor, from, to) { for ( let number = from; number <= to; number++ ) { if ( number % factor == 0 ) { console.log(factor, "*", number / factor, "=", number); } } }