Hướng dẫn đơn giản để giúp bạn hiểu các bao đóng trong JavaScript

Đóng trong JavaScript là một trong những khái niệm mà nhiều người đấu tranh để có được đầu óc của họ. Trong bài viết sau, tôi sẽ giải thích một cách rõ ràng về việc đóng cửa là gì và tôi sẽ lái xe về nhà bằng cách sử dụng các ví dụ mã đơn giản.

Đóng cửa là gì?

Một bao đóng là một tính năng trong JavaScript trong đó một hàm bên trong có quyền truy cập vào hàm ngoài (kèm theo) các biến Biến - một chuỗi phạm vi.

Việc đóng cửa có ba chuỗi phạm vi:

  • nó có quyền truy cập vào phạm vi của chính nó - các biến được xác định giữa các dấu ngoặc nhọn của nó
  • Nó có quyền truy cập vào các biến ngoài chức năng
  • nó có quyền truy cập vào các biến toàn cầu

Đối với người không quen biết, định nghĩa này có vẻ như chỉ là rất nhiều biệt ngữ!

Nhưng những gì thực sự là một đóng cửa?

Một đóng cửa đơn giản

Hãy xem một ví dụ đóng cửa đơn giản trong JavaScript:

hàm ngoài () {
   var b = 10;
   hàm bên trong () {
        
         var a = 20;
         console.log (a + b);
    }
   trở về bên trong;
}

Ở đây chúng tôi có hai chức năng:

  • một hàm ngoài có hàm b và trả về hàm bên trong
  • một hàm bên trong có biến được gọi là a và truy cập vào biến ngoài b, trong thân hàm của nó

Phạm vi của biến b được giới hạn ở hàm ngoài và phạm vi của biến a được giới hạn ở hàm bên trong.

Bây giờ chúng ta gọi hàm ngoài () và lưu trữ kết quả của hàm ngoài () trong một biến X. Sau đó chúng ta gọi hàm ngoài () lần thứ hai và lưu nó vào biến Y.

hàm ngoài () {
   var b = 10;
   hàm bên trong () {
        
         var a = 20;
         console.log (a + b);
    }
   trở về bên trong;
}
var X = ngoài (); // bên ngoài () được gọi lần đầu tiên
var Y = ngoài (); // bên ngoài () được gọi lần thứ hai

Hãy để Lặn xem từng bước những gì xảy ra khi hàm ngoài () được gọi đầu tiên:

  1. Biến b được tạo, phạm vi của nó được giới hạn ở hàm ngoài () và giá trị của nó được đặt thành 10.
  2. Dòng tiếp theo là một khai báo hàm, vì vậy không có gì để thực thi.
  3. Trên dòng cuối cùng, trả về vẻ bên trong cho một biến gọi là bên trong, thấy rằng biến bên trong này thực sự là một hàm và do đó trả về toàn bộ phần thân của hàm bên trong.
    [Lưu ý rằng câu lệnh return không thực thi hàm bên trong - một hàm chỉ được thực thi khi được theo sau bởi () -, mà là câu lệnh return trả về toàn bộ phần thân của hàm.]
  4. Các nội dung được trả về bởi câu lệnh return được lưu trữ trong X.
    Do đó, X sẽ lưu trữ như sau:
     hàm bên trong () {
     var a = 20;
    console.log (a + b);
    }
  5. Hàm bên ngoài () kết thúc thực hiện và tất cả các biến trong phạm vi của bên ngoài () bây giờ không còn tồn tại.

Phần cuối cùng này là quan trọng để hiểu. Khi một hàm hoàn thành việc thực thi của nó, bất kỳ biến nào được xác định bên trong phạm vi hàm sẽ ngừng tồn tại.

Tuổi thọ của một biến được xác định bên trong hàm là tuổi thọ của thực thi hàm.

Điều này có nghĩa là trong console.log (a + b), biến b chỉ tồn tại trong quá trình thực hiện hàm ngoài (). Khi hàm ngoài đã thực hiện xong, biến b không còn tồn tại.

Khi hàm được thực thi lần thứ hai, các biến của hàm được tạo lại và chỉ tồn tại cho đến khi hàm hoàn thành thực thi.

Do đó, khi bên ngoài () được gọi lần thứ hai:

  1. Một biến b mới được tạo, phạm vi của nó được giới hạn ở hàm ngoài () và giá trị của nó được đặt thành 10.
  2. Dòng tiếp theo là một khai báo hàm, vì vậy không có gì để thực thi.
  3. trả về bên trong trả về toàn bộ cơ thể của chức năng bên trong.
  4. Các nội dung được trả về bởi câu lệnh return được lưu trữ trong Y.
  5. Hàm bên ngoài () kết thúc thực hiện và tất cả các biến trong phạm vi của bên ngoài () bây giờ không còn tồn tại.

Điểm quan trọng ở đây là khi hàm ngoài () được gọi lần thứ hai, biến b được tạo lại. Ngoài ra, khi hàm ngoài () kết thúc thực hiện lần thứ hai, biến b mới này lại không còn tồn tại.

Đây là điểm quan trọng nhất để nhận ra. Các biến bên trong các hàm chỉ tồn tại khi hàm đang chạy và ngừng tồn tại sau khi các hàm hoàn thành thực thi.

Bây giờ, chúng ta hãy quay lại ví dụ mã của chúng ta và xem xét X và Y. Vì hàm ngoài () khi thực thi trả về một hàm, các biến X và Y là các hàm.

Điều này có thể được xác minh dễ dàng bằng cách thêm đoạn mã sau vào mã JavaScript:

console.log (typeof (X)); // X là hàm loại
console.log (typeof (Y)); // Y là hàm loại

Vì các biến X và Y là các hàm, chúng ta có thể thực hiện chúng. Trong JavaScript, một hàm có thể được thực thi bằng cách thêm () sau tên hàm, chẳng hạn như X () và Y ().

hàm ngoài () {
var b = 10;
   hàm bên trong () {
        
         var a = 20;
         console.log (a + b);
    }
   trở về bên trong;
}
var X = ngoài ();
var Y = ngoài ();
// kết thúc thực hiện hàm ngoài ()
X (); // X () được gọi lần đầu tiên
X (); // X () đã gọi lần thứ hai
X (); // X () đã gọi lần thứ ba
Y (); // Y () được gọi lần đầu tiên

Khi chúng ta thực thi X () và Y (), về cơ bản chúng ta đang thực thi hàm bên trong.

Hãy để chúng tôi kiểm tra từng bước những gì xảy ra khi X () được thực thi lần đầu tiên:

  1. Biến a được tạo và giá trị của nó được đặt thành 20.
  2. JavaScript bây giờ cố gắng thực thi a + b. Đây là nơi mọi thứ trở nên thú vị. JavaScript biết rằng tồn tại vì nó vừa tạo ra nó. Tuy nhiên, biến b không còn tồn tại. Vì b là một phần của hàm ngoài, b sẽ chỉ tồn tại trong khi hàm ngoài () đang thực thi. Vì hàm ngoài () đã kết thúc thực hiện từ lâu trước khi chúng ta gọi X (), nên bất kỳ biến nào trong phạm vi của hàm ngoài sẽ không còn tồn tại và do đó biến b không còn tồn tại.

JavaScript xử lý việc này như thế nào?

Đóng cửa

Hàm bên trong có thể truy cập các biến của hàm kèm theo do đóng trong JavaScript. Nói cách khác, hàm bên trong bảo tồn chuỗi phạm vi của hàm kèm theo tại thời điểm hàm kèm theo được thực thi và do đó có thể truy cập vào các biến đóng gói của hàm.

Trong ví dụ của chúng ta, hàm bên trong đã bảo toàn giá trị b = 10 khi hàm ngoài () được thực thi và tiếp tục bảo toàn (đóng) nó.

Bây giờ nó đề cập đến chuỗi phạm vi của nó và thông báo rằng nó có giá trị của biến b trong chuỗi phạm vi của nó, vì nó đã bao quanh giá trị của b trong một bao đóng tại điểm khi hàm ngoài đã thực thi.

Do đó, JavaScript biết a = 20 và b = 10 và có thể tính a + b.

Bạn có thể xác minh điều này bằng cách thêm dòng mã sau vào ví dụ trên:

hàm ngoài () {
var b = 10;
   hàm bên trong () {
        
         var a = 20;
         console.log (a + b);
    }
   trở về bên trong;
}
var X = ngoài ();
console.dir (X); // sử dụng console.dir () thay vì console.log ()

Mở phần tử Kiểm tra trong Google Chrome và đi tới Bảng điều khiển. Bạn có thể mở rộng phần tử để thực sự thấy phần tử Đóng (hiển thị trong dòng thứ ba đến dòng cuối cùng bên dưới). Lưu ý rằng giá trị của b = 10 được bảo toàn trong Đóng ngay cả sau khi hàm ngoài () hoàn thành việc thực hiện.

Biến b = 10 được bảo toàn trong Đóng, Đóng trong Javascript

Bây giờ chúng ta hãy xem lại định nghĩa về sự đóng cửa mà chúng ta đã thấy lúc đầu và xem liệu bây giờ nó có ý nghĩa hơn không.

Vì vậy, chức năng bên trong có ba chuỗi phạm vi:

  • truy cập vào phạm vi riêng của nó - biến a
  • truy cập vào các chức năng bên ngoài Các biến số - biến b, được bao quanh
  • truy cập vào bất kỳ biến toàn cầu có thể được xác định

Đóng cửa trong hành động

Để lái xe về nhà điểm đóng cửa, hãy để Gia tăng ví dụ bằng cách thêm ba dòng mã:

hàm ngoài () {
var b = 10;
var c = 100;
   hàm bên trong () {
        
         var a = 20;
         console.log ("a =" + a + "b =" + b);
         a ++;
         b ++;
    }
   trở về bên trong;
}
var X = ngoài (); // bên ngoài () được gọi lần đầu tiên
var Y = ngoài (); // bên ngoài () được gọi lần thứ hai
// kết thúc thực hiện hàm ngoài ()
X (); // X () được gọi lần đầu tiên
X (); // X () đã gọi lần thứ hai
X (); // X () đã gọi lần thứ ba
Y (); // Y () được gọi lần đầu tiên

Khi bạn chạy mã này, bạn sẽ thấy đầu ra sau trong console.log:

a = 20 b = 10
a = 20 b = 11
a = 20 b = 12
a = 20 b = 10

Hãy từng bước kiểm tra mã này để xem chính xác những gì đang xảy ra và để xem các đóng cửa trong Hành động!

var X = ngoài (); // bên ngoài () được gọi lần đầu tiên

Hàm ngoài () được gọi lần đầu tiên. Các bước sau đây diễn ra:

  1. Biến b được tạo và được đặt thành 10
    Biến c được tạo và được đặt thành 100
    Hãy để Lừa gọi đây là b (first_time) và c (first_time) để chúng ta tham khảo.
  2. Hàm bên trong được trả về và gán cho X
    Tại thời điểm này, biến b được đặt trong chuỗi phạm vi hàm bên trong dưới dạng đóng với b = 10, vì bên trong sử dụng biến b.
  3. Hàm ngoài hoàn thành việc thực thi và tất cả các biến của nó không còn tồn tại. Biến c không còn tồn tại, mặc dù biến b tồn tại dưới dạng đóng trong bên trong.
var Y = ngoài (); // bên ngoài () được gọi lần thứ hai
  1. Biến b được tạo lại và được đặt thành 10
    Biến c được tạo lại.
    Lưu ý rằng mặc dù lớp ngoài () đã được thực thi một lần trước khi các biến b và c không còn tồn tại, một khi hàm hoàn thành thực thi chúng được tạo như các biến hoàn toàn mới.
    Hãy để chúng tôi gọi những b (second_time) và c (second_time) để tham khảo của chúng tôi.
  2. Hàm bên trong được trả về và gán cho Y
    Tại thời điểm này, biến b được đặt trong chuỗi phạm vi hàm bên trong dưới dạng đóng với b (second_time) = 10, vì bên trong sử dụng biến b.
  3. Hàm ngoài hoàn thành việc thực thi và tất cả các biến của nó không còn tồn tại.
    Biến c (second_time) không còn tồn tại, mặc dù biến b (second_time) tồn tại dưới dạng đóng bên trong.

Bây giờ hãy để Lừa xem điều gì xảy ra khi các dòng mã sau được thực thi:

X (); // X () được gọi lần đầu tiên
X (); // X () đã gọi lần thứ hai
X (); // X () đã gọi lần thứ ba
Y (); // Y () được gọi lần đầu tiên

Khi X () được gọi lần đầu tiên,

  1. biến a được tạo và đặt thành 20
  2. giá trị của a = 20, giá trị của b là từ giá trị đóng. b (lần đầu tiên), vì vậy b = 10
  3. biến a và b được tăng thêm 1
  4. X () hoàn thành thực thi và tất cả các biến bên trong của nó - biến a - không còn tồn tại.
    Tuy nhiên, b (first_time) được bảo tồn dưới dạng đóng, vì vậy b (first_time) tiếp tục tồn tại.

Khi X () được gọi lần thứ hai,

  1. biến a được tạo lại và đặt thành 20
     Mọi giá trị trước đó của biến a không còn tồn tại, vì nó đã không còn tồn tại khi X () hoàn thành thực hiện lần đầu tiên.
  2. giá trị của a = 20
    giá trị của b được lấy từ giá trị đóng - b (first_time)
    Cũng lưu ý rằng chúng tôi đã tăng giá trị của b lên 1 từ lần thực hiện trước, vì vậy b = 11
  3. biến a và b được tăng thêm 1 lần nữa
  4. X () hoàn thành thực thi và tất cả các biến bên trong của nó - biến a - ngừng tồn tại
    Tuy nhiên, b (first_time) được giữ nguyên khi đóng tiếp tục tồn tại.

Khi X () được gọi lần thứ ba,

  1. biến a được tạo lại và đặt thành 20
    Mọi giá trị trước đó của biến a không còn tồn tại, vì nó đã không còn tồn tại khi X () hoàn thành thực hiện lần đầu tiên.
  2. giá trị của a = 20, giá trị của b là từ giá trị đóng - b (first_time)
    Cũng lưu ý rằng chúng tôi đã tăng giá trị của b lên 1 trong lần thực hiện trước, vì vậy b = 12
  3. biến a và b được tăng thêm 1 lần nữa
  4. X () hoàn thành thực thi và tất cả các biến bên trong của nó - biến a - không còn tồn tại
    Tuy nhiên, b (first_time) được giữ nguyên khi đóng tiếp tục tồn tại

Khi Y () được gọi lần đầu tiên,

  1. biến a được tạo lại và đặt thành 20
  2. giá trị của a = 20, giá trị của b là từ giá trị đóng - b (second_time), vì vậy b = 10
  3. biến a và b được tăng thêm 1
  4. Y () hoàn thành thực thi và tất cả các biến bên trong của nó - biến a - không còn tồn tại
    Tuy nhiên, b (second_time) được bảo tồn dưới dạng đóng, vì vậy b (second_time) tiếp tục tồn tại.

Kết luận

Đóng cửa là một trong những khái niệm tinh tế trong JavaScript rất khó nắm bắt lúc đầu. Nhưng một khi bạn hiểu chúng, bạn nhận ra rằng mọi thứ không thể là cách nào khác.

Hy vọng rằng những giải thích từng bước này đã giúp bạn thực sự hiểu khái niệm về sự đóng cửa trong JavaScript!

Các bài viết khác:

  • Hướng dẫn nhanh về cách tự gọi các chức năng của Viking trong Javascript
  • Hiểu phạm vi chức năng so với phạm vi khối trong Javascript
  • Cách sử dụng Lời hứa trong JavaScript
  • Cách xây dựng hoạt hình Sprite đơn giản trong JavaScript