Sử dụng thực tế cho việc đóng trong JavaScript là gì?


279

Tôi đang cố gắng hết sức để quấn đầu quanh các lần đóng JavaScript.

Tôi nhận được điều đó bằng cách trả về một hàm bên trong, nó sẽ có quyền truy cập vào bất kỳ biến nào được xác định trong cha mẹ trực tiếp của nó.

Điều này sẽ hữu ích cho tôi ở đâu? Có lẽ tôi chưa hoàn toàn hiểu ý mình. Hầu hết các ví dụ tôi đã thấy trực tuyến không cung cấp bất kỳ mã thế giới thực nào, chỉ là các ví dụ mơ hồ.

Ai đó có thể chỉ cho tôi một thế giới thực sử dụng của một đóng cửa?

Đây có phải là một ví dụ?

var warnUser = function (msg) {
    var calledCount = 0;
    return function() {
       calledCount++;
       alert(msg + '\nYou have been warned ' + calledCount + ' times.');
    };
};

var warnForTamper = warnUser('You can not tamper with our HTML.');
warnForTamper();
warnForTamper();

17
+1 để cố gắng hết sức :-) Đóng cửa có vẻ thực sự khó khăn khi bắt đầu, tôi biết chúng là dành cho tôi. Khi bạn hiểu rõ về chúng, bạn sẽ ngay lập tức trở thành một lập trình viên giỏi hơn nhiều.
Andy E

7
Tôi vừa mới viết một bài đăng trên blog về các lần đóng trong JavaScript mà bạn có thể tìm thấy helfpul.
Skilldrick

@Skilldrick. liên kết đã chết ... và tôi cũng thấy ví dụ thực tế này rất hữu ích. youtube.com/watch?v=w1s9PgtEoJs .
Abhi

Câu trả lời:


239

Tôi đã sử dụng các bao đóng để làm những việc như:

a = (function () {
    var privatefunction = function () {
        alert('hello');
    }

    return {
        publicfunction : function () {
            privatefunction();
        }
    }
})();

Như bạn có thể thấy ở đó, abây giờ là một đối tượng, với một phương thức publicfunction( a.publicfunction()) gọi privatefunction, chỉ tồn tại bên trong bao đóng. Bạn KHÔNG thể gọi privatefunctiontrực tiếp (tức là a.privatefunction()), chỉ publicfunction().

Đó là một ví dụ tối thiểu nhưng có lẽ bạn có thể thấy sử dụng nó? Chúng tôi đã sử dụng điều này để thực thi các phương pháp công cộng / riêng tư.


26
Ah, nếu đây là một đóng cửa, thì tôi đã sử dụng các bao đóng mà không biết nó! Tôi thường đặt các hàm bên trong một cái khác như thế, và sau đó phơi bày bất kỳ thứ gì tôi cần công khai bằng cách trả về một đối tượng theo nghĩa đen như trong ví dụ của bạn.
alex

1
Có, như bạn thấy, bạn giữ bối cảnh của hàm vì đối tượng bạn trả về các biến tham chiếu (và hàm) bên trong nó. Vì vậy, bạn đã sử dụng chúng, bạn không biết điều đó.
Francisco Soto

9
Về mặt kỹ thuật, mọi chức năng bạn thực hiện trong Javascript trên trình duyệt đều bị đóng vì đối tượng cửa sổ bị ràng buộc với nó.
Adam Gent

9
Tôi biết đây là một câu hỏi cũ, nhưng với tôi điều này vẫn không cung cấp một câu trả lời thỏa đáng. Tại sao không chỉ gọi hàm trực tiếp? Tại sao bạn cần một chức năng riêng tư?
qodeninja

5
Bởi vì mặc dù ví dụ chỉ có một hàm, nó cũng có thể có các biến không thể truy cập được từ bên ngoài. Nói: var obj = (function () {var value = 0; return {get: function () {return value;}, set: function (val) {value = val;}}}) (); obj.set (20); obj.get (); => 20, v.v.
Francisco Soto

209

Giả sử, bạn muốn đếm số lần người dùng nhấp vào nút trên trang web.
Đối với điều này, bạn đang kích hoạt một chức năng trên onclicksự kiện của nút để cập nhật số lượng của biến

<button onclick="updateClickCount()">click me</button>  

Bây giờ có thể có nhiều cách tiếp cận như:

1) Bạn có thể sử dụng biến toàn cục và hàm để tăng bộ đếm :

var counter = 0;

function updateClickCount() {
    ++counter;
    // do something with counter
}

Nhưng, điều đáng tiếc là bất kỳ tập lệnh nào trên trang đều có thể thay đổi bộ đếm mà không cần gọiupdateClickCount() .


2) Bây giờ, bạn có thể nghĩ đến việc khai báo biến bên trong hàm:

function updateClickCount() {
    var counter = 0;
    ++counter;
    // do something with counter
}

Nhưng, này! Mỗi khi updateClickCount()hàm được gọi, bộ đếm được đặt thành 1 lần nữa.


3) Suy nghĩ về các chức năng lồng nhau ?

Các hàm lồng nhau có quyền truy cập vào phạm vi "ở trên" chúng.
Trong ví dụ này, hàm bên trong updateClickCount()có quyền truy cập vào biến đếm trong hàm chacountWrapper()

function countWrapper() {
    var counter = 0;
    function updateClickCount() {
    ++counter;
    // do something with counter
    }
    updateClickCount();    
    return counter; 
}

Điều này có thể đã giải quyết được vấn đề nan giải, nếu bạn có thể tiếp cận updateClickCount()chức năng từ bên ngoài và bạn cũng cần tìm cách thực hiện counter = 0chỉ một lần chứ không phải mọi lúc.


4) Đóng cửa để giải cứu! (chức năng tự gọi) :

 var updateClickCount=(function(){
    var counter=0;

    return function(){
     ++counter;
     // do something with counter
    }
})();

Chức năng tự gọi chỉ chạy một lần. Nó đặt giá countertrị bằng 0 (0) và trả về một biểu thức hàm.

Cách này updateClickCounttrở thành một chức năng. Phần "tuyệt vời" là nó có thể truy cập vào bộ đếm trong phạm vi cha.

Điều này được gọi là đóng JavaScript . Nó làm cho hàm có thể có các biến " riêng tư ".

Các counterđược bảo vệ bởi phạm vi của hàm nặc danh, và chỉ có thể được thay đổi bằng cách sử dụng chức năng add!

Ví dụ sinh động hơn về Đóng cửa:

<script>
        var updateClickCount=(function(){
    	var counter=0;
    
    	return function(){
    	++counter;
    	 document.getElementById("spnCount").innerHTML=counter;
    	}
      })();
    </script>

    <html>
	 <button onclick="updateClickCount()">click me</button>
	  <div> you've clicked 
		<span id="spnCount"> 0 </span> times!
	 </div>
    </html>


Tham khảo: https://www.w3schools.com/js/js_feft_closures.asp


49
Đây là câu trả lời đầu tiên khiến tôi phải thốt lên "Ồ, đó là lý do tại sao tôi sẽ sử dụng các lần đóng cửa!"
Quỷ dữ ủng hộ

6
u làm cho ngày của tôi :)
JerryGidel

15
Tôi chỉ đọc trang w3schools về việc đóng cửa và sau đó đến đây để biết thêm thông tin. Điều này giống như trang w3schools
tyelford

1
@JerryGidel bạn có thể làm cho nó hoạt động với 2 nút riêng biệt không? Tôi không thể tìm ra làm thế nào mà không dùng đến 2 biến (bản sao của hàm), dường như loại bỏ một số lợi ích / sự thuận tiện chính.
Tyler Collier

2
Câu trả lời tốt. Lưu ý rằng đóng cửa không cần phải là một chức năng tự gọi, nhưng nó thể. Khi đóng cửa tự cách gọi (tức là ngay lập tức gọi bằng cách thêm () sau khi chức năng), các phương tiện này giá trị trả về ngay lập tức tính toán, chứ không phải là chức năng được trả lại và giá trị trả được tính toán sau khi hàm được gọi. Một bao đóng thực sự có thể là bất kỳ hàm nào trong một hàm khác và đặc điểm chính của nó là nó có quyền truy cập vào phạm vi của hàm cha bao gồm các biến và phương thức của nó.
Chris Halcrow

69

Ví dụ bạn đưa ra là một ví dụ tuyệt vời. Đóng cửa là một cơ chế trừu tượng cho phép bạn tách biệt các mối quan tâm rất sạch sẽ. Ví dụ của bạn là một trường hợp tách dụng cụ (đếm cuộc gọi) khỏi ngữ nghĩa (API báo cáo lỗi). Các mục đích sử dụng khác bao gồm:

  1. Truyền hành vi được tham số hóa vào một thuật toán (lập trình bậc cao cổ điển):

    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
  2. Mô phỏng lập trình hướng đối tượng:

    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
    
  3. Thực hiện kiểm soát luồng kỳ lạ, như xử lý sự kiện và API AJAX của jQuery.


3
( int?) Lần cuối tôi kiểm tra, JavaScript là ngôn ngữ gõ vịt. Có lẽ bạn đã nghĩ về Java?
Hello71

1
@ Hello71: Tôi đã nghĩ về JavaScript, nhưng thói quen cũ chết cứng. Nắm bắt tốt.
Marcelo Cantos

2
@MarceloCantos Có vẻ như bạn đã quên dấu phẩy trong quá trình thực hiện. Tôi đã chỉnh sửa bài viết của bạn để sửa nó. Hy vọng là ổn :)
Natan Streppel

2
@Streppel: Bắt tốt! Tôi rất vui cho bạn để làm cho mã của tôi tốt hơn. :-)
Marcelo Cantos

cố gắng hiểu # 1 ... Bạn sẽ gọi lân cận như thế nào?
Dave2081

26

Tôi biết tôi siêu muộn khi trả lời câu hỏi này nhưng nó có thể giúp bất cứ ai vẫn đang tìm kiếm câu trả lời trong năm 2018.

Javascript đóng cửa có thể được sử dụng để thực hiện các gadebounce chức năng trong ứng dụng của bạn.

Điều tiết :

Throttling đặt một giới hạn về số lần tối đa mà một chức năng có thể được gọi theo thời gian. Như trong "thực hiện chức năng này nhiều nhất cứ sau 100 mili giây."

Mã số:

const throttle = (func, limit) => {
  let isThrottling
  return function() {
    const args = arguments
    const context = this
    if (!isThrottling) {
      func.apply(context, args)
      isThrottling = true
      setTimeout(() => isThrottling = false, limit)
    }
  }
}

Tranh luận :

Việc ghi nợ đặt một giới hạn cho một chức năng không được gọi lại cho đến khi một khoảng thời gian nhất định trôi qua mà không được gọi. Như trong "chỉ thực hiện chức năng này nếu 100 mili giây đã trôi qua mà không được gọi."

Mã số:

const debounce = (func, delay) => {
  let debouncing
  return function() {
    const context = this
    const args = arguments
    clearTimeout(debouncing)
    debouncing = setTimeout(() => func.apply(context, args), delay)
  }
}

Như bạn có thể thấy việc đóng cửa đã giúp triển khai hai tính năng đẹp mà mọi ứng dụng web cần phải cung cấp chức năng trải nghiệm UI mượt mà.

Tôi hy vọng nó sẽ giúp được ai đó.


18

Vâng, đó là một ví dụ tốt về việc đóng cửa hữu ích. Lệnh gọi warnUser tạo calledCountbiến trong phạm vi của nó và trả về một hàm ẩn danh được lưu trong warnForTamperbiến. Bởi vì vẫn còn một bao đóng sử dụng biến được gọi là Count, nên nó không bị xóa khi thoát khỏi hàm, vì vậy mỗi lệnh gọi đến warnForTamper()sẽ làm tăng biến trong phạm vi và cảnh báo giá trị.

Vấn đề phổ biến nhất tôi thấy trên StackOverflow là việc ai đó muốn "trì hoãn" việc sử dụng một biến được tăng lên trên mỗi vòng lặp, nhưng vì biến đó nằm trong phạm vi nên mỗi tham chiếu đến biến sẽ xảy ra sau khi vòng lặp kết thúc, dẫn đến trạng thái kết thúc của biến:

for (var i = 0; i < someVar.length; i++)
    window.setTimeout(function () { 
        alert("Value of i was "+i+" when this timer was set" )
    }, 10000);

Điều này sẽ dẫn đến mọi cảnh báo hiển thị cùng một giá trị i, giá trị được tăng lên khi vòng lặp kết thúc. Giải pháp là tạo ra một bao đóng mới, một phạm vi riêng cho biến. Điều này có thể được thực hiện bằng cách sử dụng hàm ẩn danh được thực thi ngay lập tức, nhận biến và lưu trữ trạng thái của nó dưới dạng đối số:

for (var i = 0; i < someVar.length; i++)
    (function (i) {
        window.setTimeout(function () { 
            alert("Value of i was "+i+" when this timer was set" )
        }, 10000);
    })(i); 

Thú vị -1, tôi đoán đây không phải là "cách sử dụng thực tế cho việc đóng trong javascript"?
Andy E

1
Tôi đã tìm thấy một số sử dụng trong việc đọc nó vì vậy tôi đã thưởng +1 trước khi downvote.
alex

1
@alex: cảm ơn, tôi đã thông báo upvote. Tôi gần như đã quen với các downvote ẩn danh ở đây tại SO. Điều đó chỉ làm tôi bực mình vì tôi thực sự muốn biết liệu tôi đã nói điều gì đó không chính xác hay sai và họ có xu hướng khiến bạn nghĩ rằng bạn vừa bị một số người trả lời khác muốn xem rõ hơn cho câu trả lời của họ. May mắn thay, tôi không phải kiểu báo thù ;-)
Andy E

1
Tôi nghĩ rằng đây là một công việc xung quanh cho phạm vi khối bị hỏng của JavaScripts. Bạn chỉ có thể thêm var j = i; trước setTimeout đầu tiên và nhận thông báo để sử dụng j đó. Một cách khác là sử dụng 'with' like so: for (var i = 0; i <someVar.length; i ++) {with ({i: i}) {window.setTimeout (function () {alert ("Giá trị của i là "+ i +" khi bộ hẹn giờ này được đặt ")}, 100);}}
davidbuttar

1
@AndyE Hài hước có thể không phải là từ đúng. Tôi chỉ nhận thấy rằng mọi người thường sử dụng các chức năng tự gọi để giải thích các lần đóng, giống như nhiều câu trả lời trên trang này. Nhưng chức năng gọi lại trong setTimeout cũng là một đóng cửa; nó có thể được coi là "sử dụng thực tế" vì bạn có thể truy cập một số biến cục bộ khác từ cuộc gọi lại. Khi tôi đang tìm hiểu về các bao đóng, nhận ra điều này rất hữu ích với tôi - rằng các bao đóng ở khắp mọi nơi, không chỉ trong các mẫu JavaScript arcade.
antoine

14

Cụ thể, trong ngôn ngữ JavaScript (hoặc bất kỳ ngôn ngữ ECMAScript) nào, việc đóng cửa rất hữu ích trong việc che giấu việc thực hiện chức năng trong khi vẫn tiết lộ giao diện.

Ví dụ, hãy tưởng tượng bạn đang viết một lớp phương thức tiện ích ngày và bạn muốn cho phép người dùng tra cứu tên các ngày trong tuần theo chỉ mục nhưng bạn không muốn họ có thể sửa đổi mảng tên bạn sử dụng trong phần mềm.

var dateUtil = {
  weekdayShort: (function() {
    var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    return function(x) {
      if ((x != parseInt(x)) || (x < 1) || (x > 7)) {
        throw new Error("invalid weekday number");
      }
      return days[x - 1];
    };
  }())
};

Lưu ý rằng daysmảng có thể được lưu trữ đơn giản như một thuộc tính của dateUtilđối tượng nhưng sau đó nó sẽ hiển thị cho người dùng tập lệnh và họ thậm chí có thể thay đổi nó nếu họ muốn, thậm chí không cần mã nguồn của bạn. Tuy nhiên, do được bao bọc bởi chức năng ẩn danh trả về chức năng tra cứu ngày, nên nó chỉ có thể truy cập được bằng chức năng tra cứu nên hiện tại nó đã bị giả mạo.


2
Điều này nghe có vẻ ngu ngốc, nhưng họ không thể tự mở tệp JavaScript và xem triển khai của bạn?
itmichaelwang

1
@Zapurdead: có, tất nhiên họ có thể thấy việc triển khai nhưng họ không thể thay đổi việc thực hiện (vô tình hoặc cố ý) mà không trực tiếp sửa đổi mã nguồn của bạn. Tôi cho rằng bạn có thể so sánh nó với các thành viên được bảo vệ trong Java.
maerics


5

Một cách sử dụng phổ biến khác cho các bao đóng là liên kết thistrong một phương thức với một đối tượng cụ thể, cho phép nó được gọi ở nơi khác (chẳng hạn như một trình xử lý sự kiện).

function bind(obj, method) {
    if (typeof method == 'string') {
        method = obj[method];
    }
    return function () {
        method.apply(obj, arguments);
    }
}
...
document.body.addEventListener('mousemove', bind(watcher, 'follow'), true);

Bất cứ khi nào một sự kiện mousemove cháy, watcher.follow(evt)được gọi.

Đóng cũng là một phần thiết yếu của các hàm bậc cao hơn, cho phép mô hình rất phổ biến của việc viết lại nhiều hàm tương tự như một hàm bậc cao hơn bằng cách tham số hóa các phần không giống nhau. Như một ví dụ trừu tượng,

foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}

trở thành

fooer = function (x) {
    return function (...) {A x B}
}

trong đó A và B không phải là các đơn vị cú pháp mà là các chuỗi mã nguồn (không phải là chuỗi ký tự).

Xem " Hợp lý hóa javascript của tôi với một chức năng " để biết ví dụ cụ thể.


5

Ở đây, tôi có một lời chào mà tôi muốn nói nhiều lần. Nếu tôi tạo một bao đóng, tôi chỉ có thể gọi hàm đó để ghi lại lời chào. Nếu tôi không tạo ra sự đóng cửa, tôi phải vượt qua tên của mình mỗi lần.

Không có đóng cửa ( https://jsfiddle.net/lukeschlangen/pw61qrow/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";
  console.log(message);
}

greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");

Với việc đóng cửa ( https://jsfiddle.net/lukeschlangen/Lb5cfve9/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";

  return function() {
    console.log(message);
  }
}

var greetingBilly = greeting("Billy", "Bob");
var greetingLuke = greeting("Luke", "Schlangen");

greetingBilly();
greetingBilly();
greetingBilly();
greetingLuke();
greetingLuke();
greetingLuke();

1
Tôi không chắc chắn nhưng vẫn không có sự đóng cửa, bạn có thể gọi là var grretBilly = hello ("Billy", "Bob"); và gọi grretBilly (); Nó vẫn sẽ làm như vậy ?? Mặc dù bạn tạo ra sự đóng cửa hay không đó là một vấn đề khác nhau nhưng việc chuyển tên mỗi lần không phải là vấn đề ở đây.
user2906608

4

Nếu bạn cảm thấy thoải mái với khái niệm khởi tạo một lớp theo nghĩa hướng đối tượng (nghĩa là tạo một đối tượng của lớp đó) thì bạn sẽ hiểu được cách đóng.

Hãy nghĩ về nó theo cách này: khi bạn khởi tạo hai đối tượng Person, bạn biết rằng biến "Tên" của thành viên lớp không được chia sẻ giữa các thể hiện; mỗi đối tượng có "bản sao" riêng. Tương tự, khi bạn tạo một bao đóng, biến miễn phí ('được gọi là' trong ví dụ của bạn ở trên) bị ràng buộc với 'thể hiện' của hàm.

Tôi nghĩ rằng bước nhảy vọt về khái niệm của bạn bị cản trở đôi chút bởi thực tế là mọi hàm / hàm được trả về bởi hàm warnUser (bên cạnh: đó là hàm bậc cao hơn ) đóng liên kết 'được gọi là' có cùng giá trị ban đầu (0), trong khi thường tạo ra các bao đóng sẽ hữu ích hơn khi chuyển các bộ khởi tạo khác nhau vào hàm bậc cao hơn, giống như chuyển các giá trị khác nhau cho hàm tạo của một lớp.

Vì vậy, giả sử khi 'được gọi là' đạt đến một giá trị nhất định mà bạn muốn kết thúc phiên của người dùng; bạn có thể muốn các giá trị khác nhau tùy thuộc vào việc yêu cầu đến từ mạng cục bộ hay internet xấu lớn (vâng, đó là một ví dụ giả định). Để đạt được điều này, bạn có thể chuyển các giá trị ban đầu khác nhau cho CallCount vào warnUser (tức là -3 hoặc 0?).

Một phần của vấn đề với tài liệu là danh pháp được sử dụng để mô tả chúng ("phạm vi từ vựng", "biến tự do"). Đừng để nó đánh lừa bạn, việc đóng cửa đơn giản hơn sẽ xuất hiện ... prima facie ;-)


3

Ở đây tôi có một ví dụ đơn giản về khái niệm đóng cửa mà chúng ta có thể sử dụng trong trang web thương mại điện tử của chúng tôi hoặc nhiều người khác nữa. Tôi đang thêm liên kết jsfiddle của tôi với ví dụ. Nó chứa một danh sách sản phẩm nhỏ gồm 3 mặt hàng và một quầy hàng.

Jsfiddle

//Counter clouser implemented function;
var CartCouter = function(){
	var counter = 0;
  function changeCounter(val){
  	counter += val
  }
  return {
  	increment: function(){
    	changeCounter(1);
    },
    decrement: function(){
    changeCounter(-1);
    },
    value: function(){
    return counter;
    }
  }
}

var cartCount = CartCouter();
function updateCart(){
	document.getElementById('cartcount').innerHTML = cartCount.value();
  }

var productlist = document.getElementsByClassName('item');
for(var i = 0; i< productlist.length; i++){
	productlist[i].addEventListener('click',function(){
  	if(this.className.indexOf('selected')<0){
    		this.className += " selected";
        cartCount.increment();
        updateCart();
    } else{
    	this.className = this.className.replace("selected", "");
      cartCount.decrement();
      updateCart();
    }
  })
}
.productslist{
  padding:10px;
}
ul li{
  display: inline-block;
  padding: 5px;
  border: 1px solid #ddd;
  text-align: center;
  width: 25%;
  cursor: pointer;
}
.selected{
  background-color: #7CFEF0;
  color: #333;
}
.cartdiv{
  position: relative;
  float:right;
  padding: 5px;
  box-sizing: border-box;
  border: 1px solid #f1f1f1;
}
<div>
<h3>
Practical Use of JavaScript Closure consept/private variable.
</h3>
<div class="cartdiv">
    <span id="cartcount">0</span>
</div>
<div class="productslist">
    <ul >
    <li class="item">Product 1</li>
     <li class="item">Product 2</li>
     <li class="item">Product 3</li>
    </ul>

</div>
</div>


2

Sử dụng đóng cửa:

Đóng cửa là một trong những tính năng mạnh mẽ nhất của JavaScript. JavaScript cho phép lồng các hàm và cấp cho hàm bên trong toàn quyền truy cập vào tất cả các biến và hàm được xác định bên trong hàm ngoài (và tất cả các biến và hàm khác mà hàm ngoài có quyền truy cập). Tuy nhiên, hàm ngoài không có quyền truy cập vào các biến và hàm được xác định bên trong hàm bên trong. Điều này cung cấp một loại bảo mật cho các biến của hàm bên trong. Ngoài ra, do hàm bên trong có quyền truy cập vào phạm vi của hàm ngoài, các biến và hàm được xác định trong hàm ngoài sẽ sống lâu hơn chính hàm ngoài, nếu hàm bên trong quản lý để tồn tại ngoài vòng đời của hàm ngoài.

Thí dụ :

<script>
var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
console.log(pet.getName());                  // Vivie

console.log(pet.setName("Oliver"));   
console.log(pet.setSex("male"));
console.log(pet.getSex());                   // male
console.log(pet.getName());                  // Oliver
</script>

Trong đoạn mã trên, biến tên của hàm ngoài có thể truy cập được vào các hàm bên trong và không có cách nào khác để truy cập các biến bên trong ngoại trừ thông qua các hàm bên trong. Các biến bên trong của hàm bên trong đóng vai trò lưu trữ an toàn cho các hàm bên trong. Họ giữ dữ liệu "liên tục", nhưng an toàn, để các chức năng bên trong hoạt động. Các hàm thậm chí không phải được gán cho một biến hoặc có tên. đọc ở đây để biết chi tiết


2

Tôi thích ví dụ về nhà máy chức năng của Mozilla .

function makeAdder(x) {

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

var addFive = makeAdder(5);

console.assert(addFive(2) === 7); 
console.assert(addFive(-5) === 0);

11
Đây là loại ví dụ không giúp mọi người hiểu được sự đóng cửa hoặc theo ý kiến ​​của họ. Đã bao nhiêu lần bạn đã viết một bao đóng để trả về một hàm để thêm số, ngoài ví dụ?
Mohamad

2

Mẫu mô-đun JavaScript sử dụng các bao đóng. Mẫu đẹp của nó cho phép bạn có một cái gì đó giống nhau "công khai" và "riêng tư".

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();


1

Chủ đề này đã giúp tôi rất nhiều trong việc hiểu rõ hơn về cách đóng cửa làm việc. Tôi đã thực hiện một số thử nghiệm của riêng mình và đưa ra mã khá đơn giản này có thể giúp một số người khác thấy cách đóng có thể được sử dụng theo cách thực tế và cách sử dụng bao đóng ở các mức khác nhau để duy trì các biến tương tự tĩnh và / hoặc các biến toàn cục mà không có nguy cơ chúng bị ghi đè hoặc nhầm lẫn với các biến toàn cục. Điều này không theo dõi các lần nhấp nút, cả ở cấp độ cục bộ cho từng nút riêng lẻ và cấp độ toàn cầu, đếm từng lần nhấp nút, đóng góp vào một con số. Lưu ý Tôi chưa sử dụng bất kỳ biến toàn cục nào để thực hiện điều này, đây là điểm quan trọng của bài tập - có một trình xử lý có thể được áp dụng cho bất kỳ nút nào cũng đóng góp cho một cái gì đó trên toàn cầu.

Xin các chuyên gia, hãy cho tôi biết nếu tôi đã thực hiện bất kỳ thực hành xấu nào ở đây! Tôi vẫn đang tự học thứ này.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Closures on button presses</title>
<script type="text/javascript">

window.addEventListener("load" , function () {
    /*
    grab the function from the first closure,
    and assign to a temporary variable 
    this will set the totalButtonCount variable
    that is used to count the total of all button clicks

    */
    var buttonHandler = buttonsCount(); 

    /*
    using the result from the first closure (a function is returned) 
    assign and run the sub closure that carries the 
    individual variable for button count and assign to the click handlers 
    */
    document.getElementById("button1").addEventListener("click" , buttonHandler() );
    document.getElementById("button2").addEventListener("click" , buttonHandler() );
    document.getElementById("button3").addEventListener("click" , buttonHandler() );

    // Now that buttonHandler has served its purpose it can be deleted if needs be
    buttonHandler = null;
});



function buttonsCount() {
    /* 
        First closure level 
        - totalButtonCount acts as a sort of global counter to count any button presses
    */
    var totalButtonCount = 0;

    return  function () {
        //second closure level
        var myButtonCount = 0;

        return function (event) {
            //actual function that is called on the button click
            event.preventDefault();
            /*  
               increment the button counts.
               myButtonCount only exists in the scope that is 
               applied to each event handler, therefore acts 
               to count each button individually whereas because 
               of the first closure totalButtonCount exists at 
               the scope just outside, so maintains a sort 
               of static or global variable state 
            */

            totalButtonCount++;
            myButtonCount++;

            /* 
                do something with the values ... fairly pointless 
                but it shows that each button contributes to both 
                it's own variable and the outer variable in the 
                first closure 
            */
            console.log("Total button clicks: "+totalButtonCount);
            console.log("This button count: "+myButtonCount);
        }
    }
}

</script>
</head>

<body>
    <a href="#" id="button1">Button 1</a>
    <a href="#" id="button2">Button 2</a>
    <a href="#" id="button3">Button 3</a>
</body>
</html>

0

Tham khảo: Sử dụng thực tế của đóng cửa

Trong thực tế đóng cửa có thể tạo ra các thiết kế thanh lịch, cho phép tùy chỉnh các tính toán khác nhau, cuộc gọi bị trì hoãn, cuộc gọi lại, tạo phạm vi đóng gói, v.v.

Một ví dụ về phương thức sắp xếp của các mảng chấp nhận làm đối số cho hàm điều kiện sắp xếp:

[1, 2, 3].sort(function (a, b) {
    ... // sort conditions
});

Các hàm ánh xạ là phương thức ánh xạ của các mảng ánh xạ một mảng mới theo điều kiện của đối số chức năng:

[1, 2, 3].map(function (element) {
   return element * 2;
}); // [2, 4, 6]

Thông thường, sẽ thuận tiện khi triển khai các chức năng tìm kiếm bằng cách sử dụng các đối số chức năng xác định các điều kiện gần như không giới hạn cho tìm kiếm:

 someCollection.find(function (element) {
        return element.someProperty == 'searchCondition';
    });

Ngoài ra, chúng tôi có thể lưu ý áp dụng các hàm như, ví dụ, một phương thức forEach áp dụng một hàm cho một mảng các phần tử:

[1, 2, 3].forEach(function (element) {
    if (element % 2 != 0) {
        alert(element);
    }
}); // 1, 3

Một hàm được áp dụng cho các đối số (cho một danh sách các đối số - đang áp dụng và cho các đối số được định vị - trong cuộc gọi):

(function () {
  alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

Các cuộc gọi bị hoãn:

var a = 10;
    setTimeout(function () {
      alert(a); // 10, after one second
    }, 1000);

Chức năng gọi lại:

var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // callback, which will be called deferral ,
  // when data will be ready;
  // variable "x" here is available,
  // regardless that context in which,
  // it was created already finished
  alert(x); // 10
};

Tạo một phạm vi đóng gói cho mục đích ẩn các đối tượng phụ trợ:

var foo = {};
(function (object) {
  var x = 10;
  object.getX = function _getX() {
    return x;
  };
})(foo);
alert(foo.getX());// get closured "x" – 10

0

Phần lớn mã chúng tôi viết bằng JavaScript front-end là dựa trên sự kiện - chúng tôi xác định một số hành vi, sau đó đính kèm nó vào một sự kiện được kích hoạt bởi người dùng (chẳng hạn như nhấp chuột hoặc nhấn phím). Mã của chúng tôi thường được đính kèm dưới dạng gọi lại: một chức năng duy nhất được thực thi để đáp ứng với sự kiện. size12, size14 và size16 hiện là các hàm sẽ thay đổi kích thước văn bản cơ thể thành 12, 14 và 16 pixel tương ứng. Chúng ta có thể gắn chúng vào các nút (trong trường hợp này là liên kết) như sau:

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

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

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

Vĩ cầm


Mặc dù mã này có thể trả lời câu hỏi, việc cung cấp ngữ cảnh bổ sung về cách thức và / hoặc lý do giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời.
Vịt Donald

1
ví dụ này, đối với tôi, có thể được thực hiện mà không cần đóng thông qua một hàm tiêu chuẩn. Tôi đang cố gắng tìm một ví dụ về điều gì đó mà COULDN không được thực hiện mà không đóng cửa
Zach Smith

0

Đóng cửa là một cách hữu ích để tạo ra , một chuỗi tăng theo yêu cầu:

    var foobar = function(i){var count = count || i; return function(){return ++count;}}

    baz = foobar(1);
    console.log("first call: " + baz()); //2
    console.log("second call: " + baz()); //3

Sự khác biệt được tóm tắt như sau:

Hàm ẩn danh Hàm xác định

Không thể được sử dụng như một phương thức Có thể được sử dụng như một phương thức của một đối tượng

Chỉ tồn tại trong phạm vi được xác định Tồn tại trong đối tượng mà nó được định nghĩa trong

Chỉ có thể được gọi trong phạm vi được xác định Có thể được gọi tại bất kỳ điểm nào trong mã

Có thể được gán lại một giá trị mới hoặc bị xóa Không thể xóa hoặc thay đổi

Người giới thiệu


0

Trong mẫu đã cho, giá trị của biến 'bộ đếm' được bảo vệ và chỉ có thể được thay đổi bằng cách sử dụng các hàm đã cho (tăng, giảm). bởi vì nó đang ở trong một đóng cửa

var MyCounter= function (){
    var counter=0;
    return {
    	increment:function () {return counter += 1;},
        decrement:function () {return counter -= 1;},
        get:function () {return counter;}
    };
};

var x = MyCounter();
//or
var y = MyCounter();

alert(x.get());//0
alert(x.increment());//1
alert(x.increment());//2

alert(y.increment());//1
alert(x.get());// x is still 2


0

Giải thích việc sử dụng thực tế cho việc đóng cửa trong JavaScript ---

Khi chúng ta tạo một hàm bên trong một hàm khác, chúng ta sẽ tạo một bao đóng. Đóng cửa rất mạnh bởi vì chúng có khả năng đọc và thao tác dữ liệu của các chức năng bên ngoài của nó. Bất cứ khi nào một chức năng được gọi, một phạm vi mới được tạo cho cuộc gọi đó. Biến cục bộ được khai báo bên trong hàm thuộc về phạm vi đó và chúng chỉ có thể được truy cập từ hàm đó. Khi hàm đã kết thúc thực thi, phạm vi thường bị hủy.

Một ví dụ đơn giản về chức năng như vậy là:

function buildName(name) { 
    const greeting = "Hello, " + name; 
    return greeting;
}

Trong ví dụ trên, hàm buildName () khai báo một lời chào biến cục bộ và trả về nó. Mỗi lệnh gọi hàm tạo một phạm vi mới với một biến cục bộ mới. Sau khi chức năng được thực thi xong, chúng ta không có cách nào để tham khảo lại phạm vi đó, vì vậy đó là rác được thu thập.

Nhưng làm thế nào về khi chúng ta có một liên kết đến phạm vi đó?

Hãy xem chức năng tiếp theo:

function buildName(name) { 
    const greeting = "Hello, " + name + " Welcome "; 
    const sayName = function() {
        console.log(greeting); 
    };
    return sayName; 
}

const sayMyName = buildName("Mandeep");
sayMyName();  // Hello, Mandeep Welcome

Hàm sayName () từ ví dụ này là một bao đóng. Hàm sayName () có phạm vi cục bộ của riêng nó (với biến được chào đón) và cũng có quyền truy cập vào phạm vi của hàm (kèm theo) bên ngoài. Trong trường hợp này, lời chào biến từ buildName ().

Sau khi thực hiện buildName xong, phạm vi không bị hủy trong trường hợp này. Hàm sayMyName () vẫn có quyền truy cập vào nó, vì vậy nó sẽ không được thu gom rác. Tuy nhiên, không có cách nào khác để truy cập dữ liệu từ phạm vi bên ngoài ngoại trừ việc đóng cửa. Việc đóng cửa đóng vai trò là cửa ngõ giữa bối cảnh toàn cầu và phạm vi bên ngoài.


0

Tôi đang cố gắng học clousures và tôi nghĩ ví dụ mà tôi đã tạo ra là trường hợp sử dụng thực tế. Bạn có thể chạy đoạn trích và xem kết quả trong bảng điều khiển. chúng tôi có hai người dùng riêng biệt có dữ liệu riêng biệt. Mỗi người trong số họ có thể thấy trạng thái thực tế và cập nhật nó.

function createUserWarningData(user) {
  const data = {
    name: user,
    numberOfWarnings: 0,
  };

  function addWarning() {
    data.numberOfWarnings = data.numberOfWarnings + 1;
  }

  function getUserData() {
    console.log(data);
    return data;
  }

  return {
    getUserData: getUserData,
    addWarning: addWarning,
  };
}

const user1 = createUserWarningData("Thomas");
const user2 = createUserWarningData("Alex");

//USER 1
user1.getUserData(); // returning data user object
user1.addWarning(); // add one warning to specific user
user1.getUserData(); // returning data user object

//USER2
user2.getUserData(); // returning data user object
user2.addWarning(); // add one warning to specific user
user2.addWarning(); // add one warning to specific user
user2.getUserData(); // returning data user object

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.