Javascript closure là gì? Vì sao người phỏng vấn thích hỏi bạn câu hỏi này?

Nội dung bài viết

Video học lập trình mỗi ngày

Closures là một trong 10 khái niệm quan trọng nhất bên cạnh những khái niệm về curryinghoisting  khi bạn muốn tăng lương hay muốn thành công trong một buổi phỏng vấn, và cũng bị hiểu lầm nhiều nhất trong programming patterns. Và còn một lý do khác đó là khái niệm này là một điểm cộng so với những người cùng ngang cấp với bạn. Chính vì vậy, hãy dành cho mình một 1 phút để hiểu về Closures là gì? Và Closures quan trọng như thế nào trong lập trình


Bài trước chúng ta đang nói về "10 khái niệm mỗi developer javascript cần phải biết" (Những ai chưa đọc nên đọc qua để biết mình thiếu sót điểm nào) trong đó có một khái niệm là Closures trong javascript. Sau khi bài viết được xuất bản thì câu hỏi phản hồi khá nhiều về "Closures" cho fan page. Chính vì lẽ đó, hôm nay trong bài viết này, chúng ta cần làm rõ khái niệm này. 

Để hiểu thì chúng ta đi từ vấn đề đơn giản thông qua các ví dụ dưới đây sẽ cho các devjs biết nó hoạt động như thế nào? 

Vòng đời của một function (The lifecycle of a function)

Đầu tiên đi vào một function đơn giản mà ai ai cũng biết

function getValue(){
  var a = 1
  var b = 2
  return a + b
}

var val = getValue()
console.log(val)
// Output: 3

Giải thích một tý cho nó oai chứ ai mà chả biết : var a và b chỉ active trong body getValue() mà thôi, không ai có thể truy cập được chúng ngoài function đó. Điều này có nghĩa là chỉ có getValue() mới trả về được var a và b. Vì sao chúng ta đang nói tới Closure  mà lại liên quan đến ví dụ trên, vì nó liên quan đến khái niệm scope trong javascript. 

Closure là gì?

Closure là một chức năng có quyền truy cập vào phạm vi cha, ngay cả sau khi scope đã đóng.

Đầu tiên bạn phải hiểu về scope như đã nói ở trên? Scope chính là tuổi thọ của một biến trong javascript. Bạn có thể thấy trong đó một biến được định nghĩa đóng một vai trò lớn trong khoảng thời gian của biến đó và các hàm trong chương trình của bạn có quyền truy cập vào đó. 

Ở đây nếu bạn chỉ cần đọc ví dụ này mà hiểu thì có lẽ bạn nên dừng lại và không nên đọc tiếp vì từng đó quá đủ để bạn hiểu về closure trong javascript.

function f1()
{
    var N = 0; // N luon duoc khoi tao khoi ham f1 dduowcj thuc thi
    console.log(N);
    function f2() // Ham f2
    {
        N += 1; // cong don cho bien N
        console.log('-->>',N);
    }

    return f2;
}

var result = f1();

result(); // Chay lan 1
result(); // Chay lan 2
result(); // Chay lan 3

Cụ thể ở đây là đối với các hàm Closure, khi hàm ngoài trả về, hàm bên trong vẫn có thể truy cập các biến của hàm ngoài. Và trên đó là một ví dụ thực tế và ngắn gọn nhất mà tôi có thể giúp bạn hiểu nhanh và gọn. Giải thích đoạn code trên. Trong ví dụ trên, hàm ngoài F1 chỉ được thực hiện một lần, và khi f1() được thực thi thì biến N được set lại N = 0 và hàm bên trong f2() được gán cho kết quả biến . Vì hàm ngoài f1() đã được thực thi, nên biến N bên trong của nó sẽ bị xóa trong bộ nhớ, nhưng đây không phải là trường hợp của Closure mỗi lần chúng ta gọi kết quả , mà là chúng ta thấy rằng biến N đã thay đổi theo mỗi lần gọi f1() tại sao? Đây là sự kỳ diệu của việc Closure!


Bây giờ chúng ta đi tiếp ví dụ nếu bạn cảm thấy thực sự chưa hiểu và nên nhớ đọc từ từ nhé : Những biến nào được tạo bên trong function được gọi là local scope. Một biến local chỉ có thể được truy cập trong function (scope) mà nó được định nghĩa. Ví dụ dưới đây cho chúng ta thấy một lỗi khi bạn cố truy cập vào một biến trong phạm vi local.

function speak(){
  var words = 'hi'; 
  console.log(words);
}
speak(); // 'hi'
console.log(words); // Uncaught ReferenceError: words is not defined

biến words là local, nếu cố gắng truy cập ở ngoài function thì chúng ta sẽ nhận được một error. Ok, tương phản với local scope thì đó chính là global scope.

Ví dụ

var words = 'hi';
function speak(){ 
  console.log(words);
}
speak(); // 'hi' 
console.log(words); // 'hi'

Giờ đây các bạn có thể truy cập biến words bất cứ ở đây, thoải mái đê... Xong, đó là những gì mà bạn cần nắm để trước khi đi vào Closure Funciton Bây giờ chúng tâ đi vào ví dụ closure, nếu bạn nào đang dùng Google Chrome thì mở console ra chuẩn bị test nào 

Để mở console cho cool thì dùng lệnh sau nếu [WINDOWS]: Ctrl + Shift + J hoặc [MAC]: Cmd + Opt + J Copy rồi paste ví dụ này vào console

function speak() {
  return function logIt() {
    var words = 'hi';
    console.log(words);
  }
}

Chúng ta vừa tạo xong function speak() và nhiệm vụ của nó return về một function khác có tên là logIt(). Và kết thúc là logIt() sẽ print ra words. Khi bạn đã sao chép nó vào console, chúng tôi sẽ tạo một biến và gán nó cho chức năng speak như sau :

var sayHello = speak();

Bây giờ chúng ta có thể thấy giá trị của sayHello là gì bằng cách gọi biến nhưng không gọi hàm bên trong:

sayHello;
//  function logIt() {
//    var words = 'hi';
//    console.log(words);
//  }

Tại sao nó ko print ra 'hi'. Từ từ đừng nôn nóng nha. Nhưng nếu chúng ta có thể gọi

sayHello();
ouput: 
'hi'

Oà nó đã làm việc, nhưng điều đó là đương nhiên và không có gì đặc biệt. Bây giờ chúng ta move biến words ra ngoài function logIt(), thì nó sẽ như thế nào nha

function speak() {
  var words = 'hi';
  return function logIt() {
    console.log(words);
  }
}
var sayHello = speak();

tiếp tục gọi

sayHello

//  function logIt() {
//    console.log(words);
//  }

Ồ, không có biến words rồi :), giả sử gọi

sayHello()
output:
'hi'

Oạch, vô lý vậy? sao nó vần print ra 'hi', Cho dù biến words không được định nghĩa??? Tại sao vậy? haha bạn vừa trải qua những tác động của một closure! 

Hãy nhớ rằng Closure một chức năng có quyền truy cập vào phạm vi cha, ngay cả sau khi phạm vi đã đóng.

Giờ đây việc closure chạy như thế nào thì các bạn cũng đã hiểu hơn chưa? Nếu chưa thì hãy kiên nhẫn, chung ta ssẽ đi thêm một ví dụ nữa. ví dụ phức tạp hơn một chút

function name(n) {
  return function(a) {
    return `${n} likes ${a}`;
  };
}

Gọi lần lượt xem nó thế nào nha

var j = name('John');
var c = name('Cindy');

rồi outout ra xem nó return về gì?

j;

//  function (a) {
//    return `${n} likes ${a}`;
//  }

Chung ta hình dung ra rồi vì closure được giới thiệu ở ví dụ trước rồi đúng không? Function vẫn có thể truy cập biến n từ phạm vi cha. Tất cả những gì chúng ta cần làm là vượt qua giá trị của a khi gọi hàm. Thử tiếp xem :

j('dogs');  // 'John likes dogs'
c('cats');  // 'Cindy likes cats'

wow nó vẫn làm việc :D Closure cho phép bạn thực hiện các biến provate trong javascript. Chúng cũng giúp chức năng của javascript hay hơn, vì nếu không có chúng, sẽ không thể trả về các hàm cần truy cập vào các biến được xác định tại thời điểm hàm được khai báo.

Sử dụng closure để xác định các biến private

Nói chung, các nhà phát triển JavaScript sử dụng dấu gạch dưới (_) làm tiền tố cho các biến private. Nhưng trên thực tế, các biến này vẫn có thể được truy cập và sửa đổi, không phải là biến private. Tại thời điểm này, việc sử dụng closure có thể xác định các biến thực sự private, ở đây có một ví dụ có lẽ bạn sẽ cười mỉm và nói rằng, à thì ra là vậy?

function Product() {

    var name;

    this.setName = function(value) {
        name = value;
    };

    this.getName = function() {
        return name;
    };
}

var p = new Product();
p.setName("anonystick.com");

console.log(p.name); // undefined
console.log(p.getName()); // anonystick.com

Trong mã, thuộc tính name của đối tượng p là thuộc tính private và không thể truy cập trực tiếp bằng p.name. Mà có thể lấy giá trị chỉ thông qua getName()

Kết Luận


Hy vọng rằng bây giờ bạn có thể hiểu những điều cơ bản về Closure trong JavaScript và cách chúng hoạt động! Đây chỉ là phần nổi của tảng băng trôi. Nhưng đừng quên rằng bạn cũng phải bần biết về curryinghoisting. Bây giờ bạn có kiến thức để tìm hiểu về các ví dụ phức tạp và thực tế hơn về Closure. Cảm ơn các bạn đã đọc, nếu các bạn cảm thấy hiểu hơn xin hãy share ra cho mọi người cùng thảo luận. Mọi ý kiến và thắc mắc bạn của gửi về fan page. 

Có thể bạn đã bị missing