NỘI DUNG BÀI VIẾT
Nói đến JavaScript, một phần quan trọng nhưng lại khá khó để nắm bắt, có thể nói nó là “thần thoại”, là một phần của ngôn ngữ: Closure. Closure là một khái niệm không phải ai cũng biết và thực sự hiểu về nó, đây có thể coi là một cách định nghĩa hàm nâng cao giúp code nhìn trong sáng, cách sử dụng linh hoạt hơn. Vậy Closure là gì thì chúng ta cùng tìm hiểu về nó nhé.
Closure function là gì?
Closure – bao đóng là một hàm được tạo ra từ bên trong một hàm khác (hàm cha), nó có thể sử dụng các biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó.
Nghe khái niệm không thì khá là trừu tượng. Ví dụ một cách đơn giản, đó là chúng ta có một cái lồng, chúng ta bỏ con chuột hunter và xe trượt của nó vào trong và đóng cửa lại. Ta có thể thấy cái lồng chính là bao đóng con chuột và xe trượt kia. Và trong lúc đó con chuột vẫn có thể chạy trên cái xe trượt xoay kia vì tất cả đều nằm trong lồng.
Chúng ta sẽ đi tìm hiểu một ví dụ đơn giản sau sau:
Ví dụ: Xây dựng hàm hiển thị thông báo, với câu thông báo được nhận từ một tham số, có dạng: “Hello world! My name is John”
// Định nghĩa function function helloWorld(myName) { alert('Hello world! My name is ' + myName); }; // Thực thi function helloWorld('John');
Cách viết này nhìn rất đơn giản và dễ hiểu. Nhưng giờ mình sẽ viết một cách khác sử dụng Closure function, như sau:
// Tạo closure function function helloWorld(myName) { return function() { alert('Hello world! My name is ' + myName); }; } // Khởi tạo closure function var message = helloWorld('John'); // Thực thi closure function message();
Rõ ràng nhìn cách này rất rối, mình sẽ giải thích từng bước. Trong fuction helloWorld mình đã return về một fuction, vì vậy lúc khởi tạo và gán nó vào biến message , thì biến message sẽ là một function chưa được khởi tạo, vì vậy câu thông báo kia vẫn chưa được xuất hiện. Vậy đoạn code Thực thi closure function message(); sẽ khởi tạo hàm trả về đó và câu thông báo sẽ xuất hiện.
Các ví dụ về Closure function
Cách viết Closure function rất linh động, nó phụ thuộc vào bài toán cụ thể mà đưa ra giải pháp khác nhau. Và sau đây mình sẽ đưa ra những ví dụ về một số cách viết thông thường.
Closure function có tham số truyền vào
Ví dụ:
// Tạo closure function function helloworld(myName) { return function(count) { for(var i = 1; i <= count; i++) { alert('Hello world! My name is ' + myName + ' - '); } }; } // Khởi tạo closure function var message = helloWorld('John'); // Thực thi closure function message(5);
Trong ví dụ này Closure function trả về có một tham số là count, lúc này khi thực thi function ta phải truyền cho nó một tham số message(5); thì chương trình mới chạy. Và lúc này sẽ hiển thị ra thông báo là 5 câu Hello world! My name is John – Hello world! My name is John – … –
Closure function thay đổi giá trị biến toàn cục và biến cục bộ
Các Closure function có thể sử dụng biến ở 3 phạm vi, thứ nhất là biến toàn cục (global), thứ hai là biến cục bộ của hàm cha (parent function’s local) và thứ ba là biến cục bộ của chính nó. Không chỉ sử dụng được mà còn có khả năng thay đổi giá trị của các biến đó.
// Tạo closure function function People() { var name = ''; var age = ''; return { setPeople : function(in_name, in_age) { name = in_name; age = in_age; }, getName : function() { return name; }, getAge : function() { return age; } }; } // Khởi tạo closure function var people = People(); // Chạy closure function people.setPeople('Christian John', '30'); alert(people.getName()); // "Christian John" alert(people.getAge()); // "30"
Return nhiều Closure function
Nếu bạn muốn return nhiều Closure function thì phải sử dụng một Object, trong đó mỗi phần tử sẽ là một Closure function.
function multipleClosure() { return { func1 : function() { console.log('Closure1'); }, func2 : function() { console.log('Closure2'); } }; } // Cách sử dụng var object = multipleClosure(); object.func1(); // Closure1 object.func2(); // Closure2
Độ ưu tiên các biến
Như ở trên đã giới thiệu, Closure function có thể sử dụng biến tại 3 vị trí. Giả sử, có 1 trường hợp: tên các biến ở ba vị trí đó bị trùng nhau thì sẽ có chuyện gì xảy ra? Với Closure, nó sẽ ưu tiên từ trong ra ngoài, nghĩa là nó sẽ thực hiện các bước như sau:
- Bước 1: Xem biến cục bộ trong hàm closure có trùng không? Nếu không trùng thì nó sẽ qua bước 2, còn nếu có trùng thì nó sẽ coi biến đó là biến cục bộ của chính nó.
- Bước 2: Xem biến cục bộ của hàm cha có trùng không? Nếu không trùng thì qua bước 3, nếu trùng thì nó sẽ coi biến đó là biến cục bộ của hàm cha.
- Bước 3: Xem biến toàn cục có trùng không? Nếu không trùng thì nó sẽ khởi tạo mới và đó sẽ là biến cục bộ của hàm closure, nếu trùng thì nó sẽ coi biến đó là biến toàn cục.
Ví dụ:
// Tạo closure function var message = 'Biến toàn cục'; function showMessage() { var message = 'Biến cục bộ của hàm cha'; return function() { alert(message); }; } // Khởi tạo closure function var messageFunc = showMessage(); // Chạy hàm closure messageFunc(); // Kết quả: Trong ví dụ này biến message trong hàm closure sẽ lấy giá trị của biến cục bộ hàm cha. => Biến cục bộ của hàm cha
Việc viết hàm theo kiểu closure trong một số trường hợp sẽ giúp code nhìn sáng và dễ quản lý hơn, linh hoạt hơn trong việc xử lý các chức năng. Trong bài viết, mình đã đề cập đến Khái niệm Closure trong Javascript và lấy một số ví dụ thông thường để các bạn hiểu rõ hơn. Tùy vào các bài toán cụ thể mà áp dụng linh động cách viết Closure function phù hợp và hiệu quả.
Kết luận
Để đơn giản, hãy tưởng tượng closure tương tự như cái ba-lô. Nó chứa tất cả các biên trong scope từ lúc function được khai báo. Ngoài ra, closures còn giúp cho JavaScipt có thể define được private variables.
Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.
Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.