Câu hỏi phỏng vấn khét tiếng

Những ngày này, tôi đang tích cực nộp hồ sơ và phỏng vấn xin việc. Có lẽ bạn cũng vậy, bạn đọc thân mến.

Vài ngày trước, tôi đã có một cuộc phỏng vấn trong đó đội kỹ thuật yêu cầu tôi giải thích closures là gì. Đó chắc chắn không phải là lần đầu tiên tôi được hỏi về thuật ngữ, nhưng tôi sẽ không nói dối, tôi đã hoảng sợ một chút.

Closures đã phát triển thành một cái gì đó có uy tín nổi tiếng vì là một câu hỏi sàng lọc khó lường.

Sau cuộc phỏng vấn, tôi thất vọng rằng chủ đề này vẫn còn khó khăn với tôi. Tôi quết tâm thắt chặt tư tưởng và đánh bại closures một lần và mãi mãi. Bài viết trên blog này sẽ đưa bạn qua hành trình của tôi.

Tìm thiểu thêm về khóa học JavaScript tại Techmaster.

Hàm và LIFE ẩn danh không bị closures

Một trường hợp sử dụng thường dùng để closures trong thời kỳ pre - es6 là các hàm/ LIFE ẩn danh được dùng để mô phỏng các phương thức private, nó không phải là bản gốc của javascript.

Các hạn chế của phạm vi var dẫn đến các trường hợp sử dụng tương tự và các trường hợp sử dụng tương tự khác được đề cập đến bởi let và const trong es6, cùng với các mô - đun. Điều đó nói rằng, LIFE đã bị closures, nhưng LIFE không bị closures.

Các hàm ẩn danh cũng không được closures.

anonymousFunc !== closure && IIFE !== closure // true

Nó vẫn có giá trị để học về những trường hợp sử dụng này. Nếu bạn có thể hiểu closures được sử dụng như thế nào trong quá khứ, bạn sẽ hiểu được nó được sử dụng như thế nào trong hiện tại.

Chưa kể là có rất nhiều code di sản es5 ở ngoài kia. Tuy nhiên, đó không phải là điều tôi sẽ đề cập ở đây hôm nay. Bây giờ chúng ta đã làm rõ, cùng bắt đầu nào.

Closures như một khái niệm

Trong khoa học máy tính, closure là một hàm có môi trường riêng và có ít nhất một biến trong môi trường đó. MDN kết thúc:

" Trong javascript, closures được tạo ra mỗi khi một hàm được tạo ra, vào thời gian tạo hàm. ”

Vì vậy, functions and closures phải đi với nhau. Mỗi lần bạn tạo ra một hàm, là bạn đang xây dựng một closure, có nghĩa là bạn đã tạo ra chúng liên tục ngay cả khi bạn không biết! MDN tiếp tục nói:

" Closure là sự kết hợp của một hàm được nhóm với nhau (kèm theo) với các tham chiếu đến trạng thái xung quanh.”

Điều này đưa chúng ta đến mục tiêu.

Mục tiêu của nó là gì

Hãy đào sâu hơn vào trạng thái thuật ngữ xung quanh thuật ngữ từ trích dẫn trước.

Khi bạn tạo một tập tin js, môi trường đó là phạm vi toàn bộ chương trình. Và khi bạn tạo ra một hàm, nó đi kèm với phạm vi của nó.

Bạn có thể nghĩ về phạm vi toàn cầu như một quốc gia. Trong một quốc gia, có nhiều thành phố, mỗi thành phố nằm trong biên giới riêng của mình. Tương tự, trong các phần cụ thể của chương trình của bạn, chúng tôi sẽ tìm thấy các đối tượng kèm theo phạm vi cục bộ.

Có hai phạm vi cục bộ trong Javascript: phạm vi hàm và phạm vi khối.

function encourage() {
 const positivity = 'You got this!';
}
// positivity has function scope
{
 const negativity = 'I don't got this.';
}
// negativity has block scope

Một hàm tồn tại và có quyền truy cập vào phạm vi toàn bộ. Bất cứ điều gì được tuyên bố trong hàm đó cũng có quyền truy cập vào phạm vi toàn bộ, mặc dù nó tồn tại trong phạm vi hàm của chính nó.

Tương tự, nếu bạn kèm theo một biến trong ngoặc xoắn ở bất cứ đâu trong mã của bạn, biến đó cũng sẽ bị cắt, nó là phạm vi khối.

Closure và Scope (phạm vi)

Nó có thể hữu ích khi nghĩ đến việc closure như một cánh cửa có tri thức hay tự nhận thức. Ví dụ: khi tạo một hàm mới, closure của hàm đó trông xung quanh và ghi chú xung quanh, phạm vi của nó.

function highestBoxOffice() {
  const context = “The highest grossing movie of all time is “;
     return context + “Avengers: Endgame”;
}

Mặc dù hàm này không có hàm con, nó vẫn có Closure. Closure không tồn tại chỉ trong các hàm lồng nhau. Hàm này đóng lại xung quanh và thấy các biến tồn tại bên trong nó, trong trường hợp này, biến context.

Closure trong hàm lồng nhau

Nếu chúng ta tạo một hàm lồng nhau, thì việc closure hàm đó sẽ thấy các bức tường của hàm mẹ mà nó tồn tại bên trong. Phạm vi của hàm cha là phạm vi bên ngoài của hàm lồng nhau . Điều này bao gồm các biến bên trong hàm cha của nó.

function highestBoxOffice(movies) {
   return function genreTopGross(genre) {
      return Math.Max(…movies.genre.boxOffice)
   }
}

Đây là nơi closures thực sự có ích. Hàm của chúng genreTopGross() có một closures. Phần closures của nó nhìn vào bên trong và nhìn thấy phạm vi bên trong của chính nó mà nó bao quanh

return Math.Max(…movies.genre.boxOffice)

Nó cũng nhìn ra ngoài và nhìn thấy phạm vi bên ngoài của nó, nó nằm trong một hàm, highestboxoffice() . Nó cũng thấy và có quyền truy cập bất kỳ đối số nào được truyền vào hàm cha của nó. Hãy đưa đối số vào ngay bây giờ.

function highestBoxOffice(movies) {
   return function genreTopGross(genre) {
      return Math.Max(…movies[genre].boxOffice)
   }
}
const topGrossing = highestBoxOffice(domesticMoviesObj)

Như các bạn thấy, chúng tôi đã tuyên bố một biến mới, topGrossing() và chúng tôi đã gán giá trị highestBoxOffice(domesticMoviesObj). Hiện tại, topGrossing sẽ không được định nghĩa, nhưng bây giờ chúng ta tiếp tục bước tiếp theo:

function highestBoxOffice(movies) {
   return function genreTopGross(genre) {
      return Math.Max(…movies[genre].boxOffice)
   }
}
const topGrossing = highestBoxOffice(domesticMoviesObj)
topGrossing("Romantic Comedy") // "Pretty Woman"

Chúng tôi gọi topGrossing() và đi qua “Romantic Comedy” là một đối số. Bây giờ chúng ta thấy closure hành động!

Phạm vi bên trong của hàm genretopgross() đòi hỏi một tham số movies, tham số này nằm trong phạm vi bên ngoài của nó trong đối số domesticMoviesObj. Mà nó sẽ truy cập qua closure.

Điều này cho phép mã thực hiện thành công và trả về giá trị chúng tôi đang tìm kiếm.

Closures và chuỗi phạm vi

Trong javascript, mỗi biến thuộc về một phạm vi từ vựng cụ thể khi nó được tạo đầu tiên.

Trong một chương trình văn bản, phạm vi của mỗi biến được liên kết thông qua những gì được gọi là chuỗi phạm vi, với phạm vi toàn bộ luôn ngồi ở đầu chuỗi.

Trình biên dịch javascript đi qua chuỗi này. Tuy nhiên trình biên dịch giống như một chiếc xe chỉ chạy ngược lại, không bao giờ chuyển tiếp.

Khi một biến được dùng, trình biên dịch di chuyển trở lại chuỗi phạm vi cho đến khi nó tìm được một mục nhập cho biến đó.

Vậy, khi chức năng genreTopGross() Sử dụng biến movies, Javascript không tìm thấy movies Trong phạm vi của genreTopGross(). Vì vậy, javascript di chuyển lên chuỗi phạm vi cho đến khi nó tìm thấy movies được thông qua highestBoxOffice().

Chuyện này có liên quan gì đến closure?

Closure chỉ cung cấp quyền truy cập từ bên trong, không phải từ bên ngoài.

Vì vậy, nếu bạn khai báo và định nghĩa một biến sâu bên trong một số hàm lồng, nhưng dùng nó trong phạm vi ngoài của một hàm cha, trình biên dịch sẽ trả về một lỗi không xác định. Nhớ là chiếc xe chỉ chạy ngược lại.

Kết luận

Như bạn có thể thấy, hiểu được closures đòi hỏi phải hiểu rõ về các hàm, phạm vi và chuỗi phạm vi, chính xác là những gì các nhà tuyển dụng đang tìm kiếm khi họ hỏi câu hỏi này.

Blog này bao gồm closures là gì, nhưng không có nhiều trường hợp sử dụng. Nếu bạn hiểu điều này, bạn có thể hiểu sâu hơn vào những trường hợp sử dụng mà không cảm thấy hoàn toàn lạc lõng.

Nếu không có gì khác, tôi hy vọng điều này sẽ tạo ra một nền tảng đơn giản hoặc tóm tắt để giữ cho bạn khỏi hoảng loạn về closures.

Như mọi khi, cảm ơn vì đã đọc.

Bài viết gốc tại đây