DEV Community

Nguyen Thien Ly
Nguyen Thien Ly

Posted on • Edited on

What is closure? (Closure là gì?)

Mục lục

  1. Vấn đề
  2. Lexical scoping là gì ?
  3. Closure là gì?
  4. Giả lập class bằng closures
  5. Closure Scope Chain
  6. Các bài phỏng vấn thường thấy về closure
  7. Kết bài

0. Vấn đề

Heyzo what's up

Xin chào tất cả mọi người, mọi người có biết closures là gì không? Đối với một người mới đi làm như mình thì cũng mơ hồ

Mình rất cay vì mình luôn được phỏng vần về closure tưởng đã chuẩn bị chắc ăn nhưng cuối cùng toàn trả lời fail. Vì vậy hôm nay mình sẽ tổng hợp về closure để tạo ra một bí kíp riêng cho mình và chia sẻ cùng mọi người nha

Bài viết này mình tổng hợp từ nhiều nguồn, nếu mọi người có góp ý gì thì mình rất sẵn lòng tiếp thu.

Bắt đầu thôi @-@

1. Lexical scoping là gì?

Xem xét ví dụ dưới đây

function outerFuc() {
  var name = 'Nguyen Thien Ly'; 

  function innerFunc() { 
    console.log(name);  
  }

  innerFunc();
}

outerFuc(); // Kết quả ra là Nguyen Thien ly
Enter fullscreen mode Exit fullscreen mode

Bên trong hàm outerFunc gồm: Một biến cục bộ name, và function innerFunc. Bên trong function innerFunc không có biến cục bộ nào cả nhưng trong hàm này đang sài một biết name của cha nó function functionOuter. Vậy đây là một ví dụ về lexical scopping cách mà các function sử dụng một biến của một hàm nó lòng trong, hoặc nhiều hàm lòng vào nhau.

2. Closure là gì?

Closure là một hàm được viết lồng vào bên trong một hàm khác (hàm cha) nó có thể sử dụng 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ó (lexical scoping)

Tiếp tục đoạn code trên và thay đổi một chút

function outerFuc() {
  var name = 'Nguyen Thien Ly'; 

  function innerFunc() { 
    console.log(name);  
  }

  return innerFunc;
}

var refInnerFunc = outerFuc();

refInnerFunc(); // Kết quả ra là Nguyen Thien ly
Enter fullscreen mode Exit fullscreen mode

Nếu mà bạn không để ý thì bạn sẽ để ý được sự khác biệt mà javascript mang lại.

Cái chỗ thằng refInnerFunc được gán bằng kết quả thực hàm outerFuc() vậy thì giờ thằng refInnerFunc đang references đến kết quả chạy hàm outerFuc() (refInnerFunc trỏ đến hàm innerFunc nhưng chưa thực thi hàm này nhé)

Hàm outerFuc() giờ đã thực thi xong các biến cục bộ của nó sẽ được giải phóng

Giờ đến lượt thực thi function innerFunc() (Chạy dòng này nè refInnerFunc()). kết quả in ra Nguyen Thien Ly. Ủa đây là biến thằng cha mà, nó đã bị hủy rồi sao còn in ra được zậy, không phải undefined à. Vậy đã có điều gì xảy ra?

javascript khi một hàm nằm trong một hàm khác, thì cái hàm nằm trong chưa thực thi mà thằng cha nó lỡ thực thi trước, thì nó sẽ tạo ra một Lexical Enviroment để đặt tất cả các biến của nó đang có và "gắn" vào hàm con để đề phòng trường hợp thằng con có cần thì có mà xài :v

Ở trường hợp trên sau khi function outerFuc() đã chết rồi nó để lại di chúc cho thằng hàm con trong nó là một tài sản biến name, sẽ đi theo thằng hàm innerFunc() cho đến khi mà thằng này cũng chết thì nó mới giải phóng

Vậy trong ví dụ trên ai là Closure? Câu trả lời là refInnerFunc vì nó đang là một hàm nằm trong hàm khác (Vì nó đã được assign innerFunc) và nó đang có biến name của cha nó (Có thể sài dù cha nó có thể mất đi) và biến global (nếu có) và biến cục bộ của nó (nếu có). (Lexical enviroment). refInnerFunc sẽ reference đến cái Lexical enviroment (Trỏ vào đây). Vậy thằng refInnerFunc đang trỏ vào function innerFunc() và lexical enviroment của nó nhá.

Nhìn vào hình dưới đây sẽ thấy

Một ví dụ khác :v

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12
Enter fullscreen mode Exit fullscreen mode

Cả hai thằng add5add10 đều là closure và thằng mỗi thằng có mỗi lexical enviroment riêng.

add5 (Cha nó để lại di chúc biến x=5 và nó đang trỏ tới anonymous function và trong đó có biến cục bộ là y)

ddd10 (Cha nó để lại di chúc biến x=10 và nó đang trỏ tới anonymous function và trong đó có biến cục bộ là y)

Đậy là dạng closure dùng khác lexical enviroment (thằng x = 5 thằng x = 10) và chung xử lí (Chung hàm anonymous y)
(Dạng 1)

3. Giả lập class bằng closures

javascript là ngôn ngữ hướng hàm functional programming nhưng ai cũng biết được rằng ngôn ngữ hướng đổi tưởng object oriented programming có những thứ rất hay ho ví dụ: Tính chất bao bọc và bảo vệ dữ liệu, vậy trong ES6 có class thì thực chất là khai báo và dùng tính chất closure này để mô phỏng tương đối


//Ví dụ 2
var counter1 = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
     changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

console.log(counter1.value()); // logs 0
counter1.increment();
counter1.increment();
console.log(counter1.value()); // logs 2
counter1.decrement();
console.log(counter1.value()); // logs 1

//Ví dụ 2
var counter2 = (function() {
  var privateCounter = 0;
  // function changeBy(val) {
  //   privateCounter += val;
  // }
  return {
    increment: function() {
      privateCounter ++;
    },
    decrement: function() {
      privateCounter --;
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

console.log(counter2.value()); // logs 0
counter2.increment();
counter2.increment();
console.log(counter2.value()); // logs 2
counter2.decrement();
console.log(counter2.value()); // logs 1
Enter fullscreen mode Exit fullscreen mode

Ở đây ta có một closure:

  • counter

Dùng chung một lexical enviroment gồm:

  • Biến privateCounter
  • Function changeBy

Và khác phần xử lí

  • increment
  • decrement
  • value

(Dạng 2) coi lại dạng 1 phần 2

Vì dùng chung nên cả ba thằng này đều có thể dùng function changeBy() để điểu khiến biến privateCounter như ví dụ 1 hoặc có thể tự điều khiển privateCounter bên trong mỗi hàm như ví dụ 2

Vậy ta không thể thấy hay điều khiển được các biến trong lexical enviroment này mà chỉ được tác động đến privateCounter thông qua những function mà nó cung cấp (increment, decrement, value) . Khá giống với OOP phải không nào ^^

4. Closure Scope Chain

Ví dụ dưới đây

// global scope
var e = 10;
function sum(a){
  return function sum2(b){
    return function sum3(c){
      // outer functions scope
      return function sum4(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

var s = sum(1);
var s1 = s(2);
var s2 = s1(3);
var s3 = s2(4);
console.log(s3) //log 20
Enter fullscreen mode Exit fullscreen mode

Trong ví đụ dưới đây ta thấy có các closure như sau

  • s (Có lexical enviroment gồm biến a và e trỏ vào function sum2 )

  • s1 (Có lexical enviroment gồm biến a, b và e trỏ vào function sum3 )

  • s2 (Có lexical enviroment gồm biến a, b, c và e trỏ vào function sum4 )

  • s3 (Có lexical enviroment gồm biến a, b, c, d và e và thực thi và giải phong a, b, c, d)

Vậy nhận ra rằng

  • Tất cả có chung biến global là e
  • Thằng đằng trong sẽ lấy tất cả các dữ liệu thằng ngoài có

5. Bài phỏng vấn thường thấy về closure

Ví dụ dưới đây

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000)
}

// logs 3, 3, 3
Enter fullscreen mode Exit fullscreen mode

Chúng ta thấy rằng function anonymous để console biến i của cha nó sẽ chạy sau khi cha nó chạy xong (Vì thằng này delay ít nhất 1s), vì vậy javascript sẽ tạo ra closure để cung cấp cho thằng hàm con trong có thể sài, vì vòng lặp là 3 nên sẽ tạo ra 3 closure

Giờ xem xét cái lexical enviroment nào, t biết rằng var i tuân thủ nguyên tắc function scope nó sẽ được tạo mới trong mỗi function còn ngoài function thì var i nhiều lần cũng chỉ là biến i đó thôi. Vậy chúng nó sài chung lexical enviroment (Giống bài mô phỏng class phần 3).

Mà để hàm thực thi function log i ra cần tới 1s sau, vậy theo như ví dụ trên, vòng lặp for sẽ được chạy xong trước khi thực thi function log i sớm nhất được thực thi vậy biến i lúc này sẽ luôn là giá trị sau khi chạy xong vòng for, i = 3.

Vậy sẽ ra kết quả là 3, 3, 3

Để sửa lỗi này t có nhiều cách nhưng mình sẽ đề xuất một cách là dùng let thay cho var vì let theo nguyên tắc block scope biến sẽ được tạo mới trong dấu { } nên sẽ có 3 closure dùng 3 lexical enviroment khác nhau nhé

for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000)
}

// logs 0, 1, 2
Enter fullscreen mode Exit fullscreen mode

6. Kết bài

Đến đây cũng đã hết bài viết rồi, cảm ơn mọi người đã theo dõi. Nếu có góp ý hay thắc mắc gì thì cứ để lại comment bên dưới mình sẽ trả lời. Xin chào mọi người.

Link tham khảo:

Top comments (1)

Collapse
 
buigiaanfb1 profile image
Bùi Gia An

hay quá cảm ơn bạn nhiều