Closure trong JavaScript

Ngày đăng: 13/10/2020   -    Cập nhật: 17/10/2020


Closures trong JavaScript là một khái niệm cực kỳ quan trọng.

Closuremột hàm được viết lồng bên trong một hàm khác, nó có thể sử dụng biến cục bộ, biến toàn cục của hàm cha và biến cục bộ của chính nó (lexical scoping)



Closure trong JavaScript

1. Lexical Scoping là gì?


Để hiểu Lexical Scoping là gì, hãy xem qua ví dụ sau đây:


function chaoKhachHang() {
    var tenKhachHang = 'Ngô Minh Trung';

    function loiChao() {
        console.log('Xin chào! ' + tenKhachHang); // Xin chào! Ngô Minh Trung
    }
    loiChao();
}
chaoKhachHang();
 


Hàm chaoKhachHang tạo một biến cục bộ là tenKhachHang và một hàm là loiChao().

Hàm loiChao() là một hàm được định nghĩa bên trong hàm chaoKhachHang() và chỉ có hiệu lực bên trong hàm chaoKhachHang() mà thôi.

Hãy nhớ là hàm loiChao() không có tạo ra một biến mới nào, nhưng mà có thể truy cập đến biến tenKhachHang của hàm cha của nó.

Như vậy khi ta thực thi đoạn code trên sẽ có kết quả như sau:


Ví dụ Lexical Scoping trong JavaScript

 
Hàm loiChao() sẽ hiển thị ra câu: Xin chào! Ngô Minh Trung, vậy có nghĩa là nó đã truy cập được giá trị của biến tenKhachHang đã định nghĩa trong hàm cha của nó.

Đây là một ví dụ về lexical scoping, mô tả cách JavaScript engine phân tích code và giải quyết cách sử dụng biến khi các hàm được lồng vào nhau.

Các hàm lồng vào nhau có quyền truy cập vào các biến dược khai báo trong phạm vi bên ngoài của chúng.

Đó chính là Lexical scoping!

 

2. Hiểu vê closure trong JavaScript


Hãy cùng xem ví dụ:


function chaoKhachHang() {
    var tenKhachHang = 'Ngô Minh Trung';

    function loiChao() {
        console.log('Xin chào! ' + tenKhachHang); // Xin chào! Ngô Minh Trung
    }
    return loiChao;
}
var chaoKhach = chaoKhachHang();
chaoKhach();
 

Khi chạy code lên kết quả sẽ cho ra tương tự như ví dụ phía trên.

Điểm khác và chúng ta cần quan tâm là hàm bên trong loiChao() được trả về từ hàm bên ngoài trước khi thực thi.

Thoạt nhìn thì có vẻ code này sẽ hoạt động, nhưng trong một số ngôn ngữ lập trình khác, các biến cục bộ trong hàm chỉ tồn tại trong khoảng thời gian mà hàm đó được thực thi.


"Các biến toàn cục tồn tại cho đến khi trang bị hủy, như khi bạn điều hướng đến một trang khác hoặc đóng cửa sổ.
 
Các biến cục bộ có tuổi thọ ngắn hơn. Chúng được tạo khi hàm được gọi và bị xóa khi hàm kết thúc"

Ở dòng lệnh này,


var chaoKhach = chaoKhachHang();
 

Sau khi hàm chaoKhachHang() thực thi xong, bạn có thể cho rằng biến tenKhachHang sẽ không truy cập được nữa.

Nhưng không!

Code vẫn hoạt động, vi diệu chưa?

Điều này chứng tỏ là JavaScript có cách hoạt động khác so với phần còn lại.

Vì thế, chúng ta có khái niệm Closures!

Khái niệm Closures là tập hợp bao gồm một hàm và môi trường nơi mà hàm đó được khai báo.

Môi trường ở đây là bao gồm tất cả những biến cục bộ trong phạm vi hàm đó.

Hàm Closures có thể truy cập biến ở 3 phạm vi:


  • Biến toàn cục (global)
  • Biến được khai báo ở hàm chứa hàm closures (outer function)
  • Biến ở bên trong hàm closures

Ví dụ:


// Tạo đối tượng Date
var d = new Date();
// Biến toàn cục
var ngayThang = d.toDateString();

function chaoKhachHang() {
    // Biến cục bộ
    var tenKhachHang = 'Ngô Minh Trung';
    // Hàm closure
    function loiChao() {
        var xinChao = 'Xin chào! ' + tenKhachHang + ", nay là " + ngayThang;
        console.log(xinChao);
    }
    return loiChao;
}
var chaoKhach = chaoKhachHang();
chaoKhach();
 

Đoạn code trên hàm closures là hàm loiChao(). Hàm closures này sử dụng biến cục bộ của hàm bên ngoài (outer function) là tenKhachHang, biến toàn cục là ngayThang và biến của chính nó là xinChao.

Dưới đây là một ví dụ thực tế hơn mà người ta thường sử dụng:



function loiChao(thongDiep) {
    return function(ten) {
        return thongDiep + ' ' + ten;
    }
}
var xinChao = loiChao('Xin chào');
var chaoMung = loiChao('Chào mừng');

console.log(xinChao('Ngô Minh Trung')); // Xin chào Ngô Minh Trung
console.log(chaoMung('Ngô Minh Trung')); // Chào mừng Ngô Minh Trung
 

Hàm loiChao() sẽ nhận một tham số là thongDiep và trả về một hàm chỉ có một tham số là ten.

Hàm này trả về một thông báo là sự kết hợp của hai tham số thongDiepten.

Hàm loiChao() hoạt động như là một nguyên mẫu. Chúng ta khai báo hàm xinChao()chaoMung() với mỗi thông báo tương ứng Xin chàoChào mừng.

xinChao()chaoMung() là các hàm closures, chúng có cùng chức năng với nhau nhưng lưu trữ giá trị khác nhau.

Trong khi hàm closures xinChao() lưu lời chào là Xin chào thì hàm closures chaoMung() lưu lời chào là Chào mừng.


3. Closures trong vòng lặp


Cùng xem ví dụ vòng lặp for sau với mình:


for (var i = 1i <= 3i++) {
    setTimeout(function() {
        console.log('Sau ' + i + ' giây: ' + i);
    }, i * 1000);
}
 

Kết quả:

Ví dụ closure trong vòng lặp

Đoạn code sẽ cho ra kết quả tương tự nhau.

Nhưng điều mà mình muốn làm trong vòng lặp là sao chép giá trị của i trong mỗi lần lặp tại thời điểm lặp để hiển thị thông báo sau 1, 2, 3 giây.

Lý do bạn thấy cùng một thông báo Sau 4 giây(s):4  là do lệnh gọi là được chuyển đến setTimeout() là một hàm closure.

Nó sẽ nhớ giá trị của i từ lần lặp cuối cùng của vòng lặp, là 4.

Ngoài ra, cả ba closures được tạo bởi vòng lặp for đều chia sẻ cùng một phạm vi toàn cục truy cập cùng một giá trị của i.

Để giải quyết vấn đề này, bạn cần tạo một closure mới trong mỗi lần lặp của vòng lặp.

Có hai giải pháp là: IIFE và từ khóa let.


3.1. Sử dụng IIFE


Trong giải pháp này, bạn sử dụng một biểu thức hàm được gọi ngay lập tức (còn gọi là IIFE) vì IIFE tạo một scope mới bằng cách định nghĩa một hàm và thực thi nó ngay lập tức.


for (var i = 1i <= 3i++) {
    (function(i) {
        setTimeout(function() {
            console.log('Sau ' + i + ' giây(s):' + i);
        }, i * 1000);
    })(i);
}
 

Kết quả ta được:

Closure trong vòng lặp: Giải pháp IIFE

3.2. Sử dụng từ khóa let


Nếu bạn sử dụng từ khóa let trong vòng lặp for, nó sẽ tạo ra một phạm vi sử dụng mới trong mỗi lần lặp.

Nói cách khác là bạn sẽ có một biến i mới trong mỗi lần lặp.

Ngoài ra, phạm vi sử dụng mới được liên kết với phạm vi sử dụng trước đó để giá trị trước đó của i được sao chép lại.



for (let i = 1i <= 3i++) {
    setTimeout(function() {
        console.log('Sau ' + i + ' giây: ' + i);
    }, i * 1000);
}
 

Kết quả:

Closure trong vòng lặp: Giải pháp từ khóa let

4. Ứng dụng closures trong thực tế


Closures rất hữu ích vì nó cho phép chúng ta gắn một vài dữ liệu (bên trong lexical environment) với một function sẽ tương tác với dữ liệu.

Tương tự như trong lập trình hướng đối tượng, các đối tượng cho phép chúng ta gắn một vài dữ liệu với một hoặc nhiều phương thức bên trong.

 

Trong lập trình web, hầu hết code được viết bằng JavaScript là event-based — chúng ta định nghĩa một xử lý, sau đó gắn nó vào event sẽ được gọi bởi user (ví dụ như hành vi click hay nhấn phím).

Đoạn code của chúng ta sẽ là callback: 1 function chạy khi có một sự kiện xảy ra.

> Tìm hiểu thêm về Callback trong JavaScript

 

Ví dụ:
 

  • Giả sử chúng ta muốn thêm một cái nút để thay đổi kích thước chữ. Một trong những cách làm là thiết lập font-size cho thẻ body bằng giá trị px, sau đó thiết lập kích thước của những phần từ khác (như header) sử dụng đơn vị em


body {
    font-familyArialsans-serif;
    font-size12px;
}

h1 {
    font-size1.5em;
}

h2 {
    font-size1.2em;
}
 

Khi thay đổi font-size của thẻ body, kích thước font của các thẻ h1, h2 sẽ tự động được điều chỉnh (bởi vì đơn vị em dựa theo kích thước của font của phần tử cha).

Bây giờ, chúng ta sẽ thiết lập chức năng thay đổi kích thước font-size cho thẻ body

 

Trong JavaScript, ta tạo ra:
 


function chinhFontSize(fontSizeMoi) {
    return function() {
        document.body.style.fontSize = fontSizeMoi + 'px';
    };
}

var size12 = chinhFontSize(12);
var size14 = chinhFontSize(14);
var size16 = chinhFontSize(16);
 


size12size14, và size16 bây giờ là những hàm sẽ thay đổi kích thước font chữ của body sang 12px, 14px hay 16px.

Tạo ra các nút / liên kết với có thể click với HTML:

 


<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
 


Sử dụng JS DOM để gán hàm tương ứng cho id tương ứng:
 


// Gán hàm thay đổi kích thước font-size
// cho id tương ứng
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
 

 

Tổng kết


Như vậy mình vừa giới thiệu với bạn cơ bản về Closures trong JavaScript. Các bạn tự đọc / tìm hiểu thêm về closure trên Google vì sau này nó rất hữu ích nếu bạn làm chuyên sâu về lập trình web / lập trình Front end.

Bài này cũng đã khép lại series Nhập môn JavaScript Đạo.

Mình tin là với những kiến thức cốt lõi nhất về JavaScript này sẽ giúp bạn bước tiếp sang Lập trình Web Back End ổn.


> Tham khảo ngay các khóa học lập trình chuyên sâu tại NIIT - ICT Hà Nội:
 

JavaScript không chỉ dừng lại ở đây. Còn rất nhiều thứ bạn có thể làm với nó.

Bởi vì hiện tại JavaScript không chỉ là một ngôn ngữ, mà nó đã trở thành "Đạo" rồi.

Còn rất nhiều thứ đang chờ đợi bạn ở phía trước. Hãy cứ khám phá nhé!

Chúc bạn thành công!!


Bình luận Facebook
Đăng ký tư vấn
Nhân viên gọi điện tư vấn miễn phí sau khi đăng ký
Được cập nhật các ưu đãi sớm nhất
Hotline: 0383180086
Tên không được để trống
Số điện thoại không được để trống
Email không được để trống
Hãy đăng ký để nhận những thông tin mới nhất về học bổng mới nhất tại NIIT - ICT Hà Nội
top
Đóng lại Đăng ký học tại NIIT - ICT Hà Nội
6260+ học viên đã theo học tại NIIT - ICT Hà Nội và có việc làm tốt trong ngành lập trình. Nắm lấy cơ hội ngay hôm nay!
Chọn khóa học
  • KHÓA HỌC LẬP TRÌNH FRONT END VỚI REACT.JS
  • KHÓA HỌC LẬP TRÌNH PHP WEB
  • Khóa học PHP Full stack [2023] cho người mới bắt đầu
  • Khóa học BIG DATA với Hadoop và Spark
  • Khóa học Lập trình Android tại Hà Nội
  • [Tuyển sinh 2023] Lập trình viên Quốc tế DigiNxt
  • Khóa học Tiền lương & Phúc lợi (C&B Excel) tại Hà Nội
  • LẬP TRÌNH GAME
    • Khóa học Lập trình Game Unity
  • LẬP TRÌNH WEB FRONT END
    • KHÓA HỌC PYTHON HƯỚNG ĐỐI TƯỢNG
    • KHÓA HỌC ANGULAR & TYPESCRIPT (FRONT END)
  • LẬP TRÌNH WEB BACK END
    • LẬP TRÌNH JAVA WEB VỚI FRAME WORK
    • Lập trình Web với Django
    • Lập trình PHP với Laravel Framework
  • CHƯƠNG TRÌNH ĐÀO TẠO ỨNG DỤNG CÔNG NGHỆ
    • Khóa học Tiền lương & Phúc lợi (C&B Excel) tại TP HCM
  • LẬP TRÌNH WEB FULL STACK
    • Khóa học Java Full stack (IJFD)
  • LẬP TRÌNH MOBILE
    • FRONT-END VỚI REACTJS VÀ REACT NATIVE
    • Lập trình Android Nâng cao
  • ĐÀO TẠO CHO DOANH NGHIỆP
    • KHÓA HỌC BUSINESS ANALYSIC TỪ CƠ BẢN ĐẾN NÂNG CAO 2023
    • Khóa học Magento: Làm chủ CMS TMĐT lớn nhất
    • Khóa học IOT: Xây dựng Sản phẩm IOT với Raspberry Pi
    • Khóa học Automation Testing Chuyên nghiệp
  • KHÓA HỌC DỰ ÁN
    • Học sử dụng bộ Office: Word, Excel, Power Point, Mail chuyên nghiệp
  • KHÓA HỌC KHÁC
    • VBA Excel Toàn Tập (Cơ Bản - Nâng Cao)
    • VBA Excel Nâng cao
    • Khóa học JMeter: Performance Testing
    • Khóa học Tester đạt chuẩn Quốc tế ISTQB Foundation Level
    • Khoá Học Tester đạt chuẩn quốc tế ISTQB Advanced Level
Bạn chưa chọn khóa học cần đăng ký
Tên không được để trống
Số điện thoại không được để trống
Email không được để trống
Đăng ký học thành công!
Cảm ơn bạn đã đăng ký học tại NIIT - ICT HÀ NỘI!