Thứ Ba, 5 tháng 1, 2016

Hàm (function) trong JavaScript


GIỚI THIỆU
Function (hàm) là 1 trong những thành phần cơ sở trong JavaScript (JS). Nói 1 cách đơn giản, hàm là 1 thủ tục - tức là 1 tập các câu lệnh dùng để thực hiện 1 tác vụ, hoặc để tính toán 1 giá trị nào đó. Để sử dụng hàm, ta cần phải định nghĩa (khai báo) nó trong cùng 1 phạm vi với lời gọi.


CÁCH ĐỊNH NGHĨA (KHAI BÁO) HÀM
Để định nghĩa hàm theo cú pháp chuẩn trong JS, ta dùng từ khóa function, tiếp theo là các thành phần sau:

  • Tên của hàm đang định nghĩa.
  • Danh sách các tham số của hàm.
  • Thân hàm - là tất cả các câu lệnh để thực hiện 1 tác vụ nào đó, nếu đây là hàm dùng để tính toán 1 giá trị thì ta dùng từ khóa return để trả về giá trị này.

Để minh họa cho điều này ta sẽ định nghĩa 1 hàm (đơn giản thui) dùng để tính bình phương của 1 số:
Mã:
function binhPhuong(so){
return so*so;
} //tinh binh phuong cua 3 alert(binhPhuong(3)); //-->9
:p Khi đọc tới đây các bạn thấy rằng hàm trong JavaScript cũng bình thường giống như hàm của rất nhiều các ngôn ngữ khác. Nhưng nếu các bạn tìm hiểu kỹ hơn thì, theo mình, hàm chính là thứ quan trọng nhất tạo nên sức mạnh của JavaScript, vì nó rất đặc biệt.
[*] Tham trị vs Tham biến:
Khi gởi tham số cho hàm, ta nên lưu ý rằng tham số này sẽ là tham trị hay tham biến hoàn toàn tùy thuộc vào kiểu của tham số và cách xử lý bên trong thân hàm. Vì trong JS không hề có từ khóa nào để hàm có thể phân biệt đâu là tham trị, đâu là tham biến (VD: trong C/C++ ta dùng dấu & trước tham biến, ..). Sau đây ta sẽ xét vài trường hợp:
  • Các tham số mà thuộc kiểu nguyên thủy (chẳng hạn kiểu number - số) luôn được gởi tới hàm bằng giá trị (tham trị). Tức là chỉ có giá trị mà tham số đang chứa được gởi tới cho hàm, dù trong hàm ta có làm gì thì cũng không ảnh đến nó ở ngoài hàm. Đây là khái niệm cơ bản mà các bạn đã biết trong các ngôn ngữ khác, nhưng mình sẽ minh họa bằng JS như sau:

Đoạn mã sau khai báo biến a và khởi tạo giá trị a=3, định nghĩa hàm binhPhuong dùng đểin ra bình phương của 1 số.
Mã:
var a=3;
binhPhuong(a);
//kiểm tra giá trị của biến a
alert('Biến a vẫn là ' + a);
function binhPhuong(so){
so*=so; //cố tình thay đổi giá trị của tham số :-) alert(so);
}
  • Nếu bạn gởi 1 đối tượng (VD: mảng, hoặc đối tượng do bạn định nghĩa) làm tham số cho 1 hàm, sau đó hàm này làm thay đổi các thuộc tính của nó thì ở bên ngoài hàm những thay đổi đó cũng có tác dụng với đối tượng (tham biến), như trong ví dụ sau:

Giả sử ta khai báo 1 đối tượng moto và 1 hàm doiMau (dùng để đổi màu của moto :cool như sau:
Mã:
function doiMau(doiTuong){
doiTuong.mau='Đỏ'; //thay đổi thuộc tính của tham số
} var moto={hangsx:'Yamaha', ten:'Exciter', mau:'Xanh'}; var mauCu, mauMoi; mauCu=moto.mau; //--> Xanh doiMau(moto); mauMoi=moto.mau; //-->đã đổi thành màu Đỏ
  • Trong trường hợp tham số vẫn là 1 đối tượng, nhưng trong thân hàm, nếu ta gán cho tham số này 1 đối tượng mới thì hành động này hoàn toàn không ảnh hưởng đến đối tượng bên ngoài hàm. Minh họa như sau:

Cũng với đối tượng moto như trên, nhưng lần này ta gởi nó làm tham số cho hàm doiMoi(đổi xe mới luôn hehe):
Mã:
function doiMoi(doiTuong){
doiTuong={hangsx:'Yamaha', ten:'Exciter', mau:'Đỏ'}; //gán đối tượng mới cho tham số
} var moto={hangsx:'Yamaha', ten:'Exciter', mau:'Xanh'}; var mauCu, mauMoi; mauCu=moto.mau; //--> Xanh doiMoi(moto); mauMoi=moto.mau; //-->vẫn là màu Xanh, oops!

BIỂU THỨC HÀM (Function Expression)
Trong các ví dụ mà mình đã dùng ở trên, các hàm đều được định nghĩa theo cú pháp chuẩn (syntactically). Ngoài ra các bạn có thể áp dụng biểu thức hàm để định nghĩa hàm trong JS (cái này không có trong các ngôn ngữ khác đâu nha) --> khai báo hàm với từ khóavar (giống như biến dzậy) sau đó khởi tạo nó với vế phải là một hàm vô danh (anonymous function) như sau:
Mã:
//định nghĩa lại hàm tính bình phương bằng Biểu thức hàm
var binhPhuong= function(so){return so*so;}
:p Từ ví dụ này ta có thể rút ra 1 điều, đó là: 1 hàm trong JS có thể được xem như là 1 biến (kiểu đối tượng) và tất nhiên cũng thể dùng để làm tham số cho các hàm khác. Điều này rất quan trọng với JS vì nó là tiền đề của rất nhiều tính năng cao cấp trong JS như: Callback - hàm gọi ngược, Object Oriented - hướng đối tượng, Closure - bao đóng,... Mình sẽ cố gắng chia sẻ tất cả :p
Các bạn lưu ý là vế phải của khai báo trong ví dụ trên chính là một Function-Expression, và ta cũng có thể đặt tên cho nó trong trường hợp các bạn muốn nó-gọi-lại-chính-nó (khái niệm đệ quy).
Đoạn mã sau định nghĩa hàm tính giai thừa (VD kinh điển về đệ quy):
Mã:
//định nghĩa lại hàm tính giai thừa bằng Biểu thức hàm
var giaiThua= function gt(so){return (so < 2) ? 1 : so * gt(so - 1);}

HÀM GỌI NGƯỢC (CALLBACK FUNCTION)
Callback-function thực chất là hàm được dùng để làm tham số cho một hàm khác. Đây cũng chính là 1 trong các công dụng của Biểu thức hàm.
Mã:
function phepTinhMang(mang, callback){
   var kqua= []; //khai báo 1 mảng mới
   for(var i= 0; i

HÀM CON - INNER FUNCTION
Trong JavaScript, 1 hàm có thể được định nghĩa ngay trong thân của một hàm khác. Giả sử hàm B được định nghĩa trong thân của hàm A thì ta gọi hàm B là hàm con (inner-function) của hàm A, và hàm A là hàm cha (outer-function) của hàm B.
Thế mạnh của inner-function là có thể sử dụng các thành phần (biến, hàm) của outer-function, hay xa hơn nữa, bất kỳ thành phần nào mà outer-function có thể truy cập được thì inner-function cũng có thể truy cập. Ngược lại, outer-function không thể sử dụng các thành phần thuộc về inner-function. Và đương nhiên, ở môi trường bên ngoài của outer-function sẽ không có sự tồn tại của inner-function. Ta có thể lấy 1 VD đơn giản để minh họa những điều này:
Mã:
function hamCha(){ //outer-function
   var bCha='Biến của cha';
   function hamCon(){ //inner-function
      var bCon='Ta có thể sử dụng '+ bCha; //truy xuất biến của hamCha (OK)
      alert(bCon); //-->'Ta có thể sử dụng Biến của cha'
   }
   //gọi hàm con
   hamCon();
   //truy xuất đến biến của hàm con
   alert(bCon); //Oops! Có lỗi: không thể truy xuất đến biến của hàm con
}
//gọi hàm cha
hamCha();
//bên ngoài hàm hamCha....
hamCon(); //Oops! Có lỗi: hàm hamCon chưa được định nghĩa
Các bạn có thể nhận xét rằng, qua ví dụ này, inner-function chẳng có ý nghĩa gì nhiều trong thực tế. Tuy nhiên, trong mô hình Hướng đối tượng của JS, inner-function được kết hợp với function-expression để hiện thực 1 khái niệm quan trọng, đó chính là phương thức(method).

FUNCTION VS CLASSTrong JS không có từ khóa class dùng để định nghĩa Lớp, tuy nhiên, như ta đã biết, 1 hàm cũng chính là một đối tượng, và đặc biệt hơn, nó chính là đối tượng đầu tiên của lớp mà ta muốn định nghĩa. Từ đó ta có thể tạo các đối tượng với từ khóa new.
Đoạn mã sau định nghĩa lớp SinhVien:
Mã:
//Khai báo lớp SinhVien
function SinhVien(ten, namSinh, lop){
   this.ten=ten;
   this.namSinh=namSinh;
   this.lop=lop;
   this.diemDanh=function(){
      alert('Em tên là ' + this.ten + ', học lớp ' + this.lop);
   };
}
Đoạn mã tiếp theo sẽ khai báo 2 sinh viên đang học lớp 11TLT.PY 
Mã:
var vonpro=new SinhVien('Bùi Quốc Tín', '1987', '11TLT.PY'),
     lybon=new SinhVien('Phạm Thành Nghị', '1989', '11TLT.PY');

//..nếu có điểm danh thì..
vonpro.diemDanh(); //-->'Em tên là Bùi Quốc Tín, học lớp 11TLT.PY'
lybon.diemDanh(); //-->'Em tên là Phạm Thành Nghị, học lớp 11TLT.PY'
Đến đây ta chỉ có thể nói rằng hàm trong JS quá 'mạnh', và hầu như nó là tất cả của JS (vừa là hàm, vừa là lớp, là phương thức, là đối tượng ..)

CLOSURES
Closures là một tính năng cao cấp của JS, và cũng là một khái niệm nâng cao. Tuy nhiên, nếu các bạn đã nắm được những khái niệm mà mình giới thiệu ở trên thì việc hiểu được closures sẽ rất dễ dàng.
Mình sẽ đi thẳng vào các ví dụ và chúng ta sẽ bàn luận về closures thông các ví dụ này.
[Ví dụ 1]
function hamCha(){
var bCha='Biến của cha';
//Khai báo inner-fuction
function hamCon(){
alert(bCha); //-->'Biến của cha'
}
//gọi hàm con
hamCon();
}

//gọi hàm cha
hamCha();
Các bạn hãy chú ý đến phạm vi của biến bCha trong ví dụ 1. Sau khi thực thi xong lời gọihamCha() thì biến bCha cũng kết thúc vòng đời của mình, và theo cách nói thông thường thì bCha đã 'ngoài tầm phủ sóng' :rolleyes:. Ngoài cách thông qua hamCha thì không còn cách nào khác để truy cập đến biến bCha. Tuy nhiên hãy xét tiếp VD sau đây.
[Ví dụ 2]
function hamCha(){
var bCha='Biến của cha';
//Khai báo inner-fuction
function hamCon(){
alert(bCha); //-->'Biến của cha'
}
//trả về hàm con (chứ không gọi hàm con)
return hamCon;
}

//ham sẽ tham chiếu đến hamCon (inner-function)
var ham = hamCha();

ham();
Mấy dòng màu đỏ chính là sự khác nhau giữa 2 VD. Bây giờ ta cũng chú ý đến phạm vi của biến bCha trong ví dụ 2. Một khi hamCha() đã thực thi xong, ta sẽ nghĩ rằng biến bChasẽ không còn tồn tại. Tuy nhiên lời gọi hàm ham() vẫn sẽ thực thi 1 cách 1 bình thường (hiển thị hộp thông báo với nội dung là giá trị của biến bCha) mà không tạo ra lỗi - điều này có vẻ hơi 'kỳ quặc'. 
Chỉ có 1 cách giải thích hợp lý nhất - đó là vì hàm ham đã trở thành 1 closure. Giờ ta có thể nói closure là 1 đối tượng đặc biệt bao gồm 2 yếu tố sau: 1 hàm, và 1 môi trường mà hàm được tạo ra từ đó :confused:. Ta hãy xét tiếp VD3 sau đây:
[Ví dụ 3]
function CongThem(so1){
return function(so2){
return so1+so2;
}
}

//tạo ra 2 closure
var congThem5= CongThem(5);
var congThem10= CongThem(10);
var so=100;
alert(congThem5(so)); //-->105
alert(congThem10(so)); //-->110
Đoạn mã trên tạo ra 2 closure (congThem5 và congThem10) là 2 hàm chung 1 định nghĩa, tuy nhiên chúng lưu trữ 2 môi trường khác nhau, đó là giá trị của biến so1 (=5 khi tạo ra congThem5, nhưng =10 khi tạo ra congThem10). Nghĩa là, trong quá trình thực thi, hàm congThem5 sẽ 'hiểu' biến so1 là 5 và tất nhiên nó sẽ trả về tổng của (5 + so2), còn hàm congThem10 thì lại 'hiểu' biến so1 là 10 và giá trị mà nó trả về là tổng của (10 + so2).
--- Closure là 1 đề tài khá lớn nhưng mình xin dừng lại tại đây, vì đây là topic về FUNCTION.
--- Chúng ta sẽ bàn luận thêm về Closure trong 1 topic riêng.

0 nhận xét: