자바스크립트 - JavaScript 클로저는 어떻게 작동합니까?




클로저 의 의미 (20)

JavaScript closure를 구성하는 개념 (예 : 함수, 변수 등)에 대한 지식이있는 사람에게 어떻게 설명하겠습니까?하지만 클로저 자체는 이해하지 못합니까?

위키 백과에서 주어진 Scheme 예제를 보았지만 유감스럽게도 도움이되지 않았습니다.


아이들은 부모가 없어지더라도 부모와 공유 한 비밀을 항상 기억할 것입니다. 이것은 함수를위한 클로저입니다.

JavaScript 함수의 비밀은 private 변수입니다.

var parent = function() {
 var name = "Mary"; // secret
}

호출 할 때마다 로컬 변수 "name"이 만들어지고 "Mary"라는 이름이 지정됩니다. 그리고 함수가 종료 할 때마다 변수가 손실되고 이름이 잊혀집니다.

추측 할 수 있듯이 함수가 호출 될 때마다 변수가 다시 작성되고 다른 누구도이를 알 수 없기 때문에 변수가 저장되는 비밀 장소가 있어야합니다. 그것은 비밀 회의소 (Chamber of Secrets) 또는 스택 (stack) 또는 로컬 스코프 (local scope) 로 불릴 수 있지만 실제로는 중요하지 않습니다. 우리는 그들이 어딘가에서 기억 속에 숨어 있음을 알고 있습니다.

그러나 자바 스크립트에는 다른 함수 안에서 만든 함수가 있고, 부모의 로컬 변수를 알 수 있고, 살아있을 때만 사용할 수있는 매우 특별한 기능이 있습니다.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

그래서 우리가 부모 함수에있는 한 비밀 장소에서 비밀 변수를 공유하는 하나 이상의 자식 함수를 만들 수 있습니다.

그러나 슬픈 일은, 만약 아이가 부모 기능의 사적인 변인이라면, 부모가 끝나면 죽을 것이고, 비밀은 그들과 함께 죽을 것입니다.

살기 위해서, 아이는 너무 늦기 전에 떠나야 만합니다.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

그리고 이제 Mary는 "더 이상 뛰지 않는다"고해도 그녀의 기억은 사라지지 않고 그녀의 자녀는 항상 자신의 시간 동안 공유 한 그녀의 이름과 다른 비밀을 기억할 것입니다.

그래서, 아이를 "앨리스"라고 부르면, 그녀는 대답 할 것입니다.

child("Alice") => "My name is Alice, child of Mary"

그게 전부입니다.


밀짚 맨

버튼을 클릭 한 횟수와 세 번째 클릭마다 무언가를하는 횟수를 알아야합니다.

공정한 해결책

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

이제이 작업은 가능하지만 변수를 추가하여 바깥 쪽 범위를 침범합니다. 변수의 수를 추적하는 것이 유일한 목적입니다. 경우에 따라 외부 응용 프로그램에서이 정보에 액세스해야 할 수도 있으므로이 방법이 바람직합니다. 그러나이 경우 세 번째 클릭의 동작 만 변경 되므로이 기능을 이벤트 처리기 안에 포함하는 것이 좋습니다.

이 옵션을 고려하십시오.

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

여기에 몇 가지 유의하십시오.

위의 예제에서 JavaScript의 클로저 동작을 사용하고 있습니다. 이 동작을 통해 모든 함수는 무기한 작성된 범위에 액세스 할 수 있습니다. 이것을 실제로 적용하기 위해 즉시 다른 함수를 반환하는 함수를 호출합니다. 반환 할 함수가 내부 카운트 변수에 액세스 할 수 있기 때문에 (위에서 설명한 클로저 동작으로 인해) 결과적으로 사용량이 개인 범위가됩니다 기능 ... 그리 간단하지 않습니까? 그것을 희석시켜 버리자 ...

간단한 한 줄 폐쇄

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

반환 된 함수 밖의 모든 변수는 반환 된 함수에서 사용할 수 있지만 반환 된 함수 객체에서 직접 사용할 수는 없습니다 ...

func();  // Alerts "val"
func.a;  // Undefined

알 겠어? 따라서 주된 예에서 count 변수는 클로저 내에 포함되어 있으며 항상 이벤트 핸들러에서 사용할 수 있으므로 클릭 한 번으로 상태를 유지합니다.

또한,이 개인 변수 상태는 독서와 그 개인 범위 변수에 할당하기 위해 완전히 액세스 할 수 있습니다.

너 거기 간다. 당신은 이제이 행동을 완전히 캡슐화하고 있습니다.

전체 블로그 게시물 (jQuery 고려 사항 포함)


5 살짜리 아이에게 종업원을 설명해 주시겠습니까? *

나는 여전히 구글의 설명 이 매우 잘 작동하고 간결 하다고 생각한다 .

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

* AC # 질문


클로저는 간단합니다.

다음의 간단한 예제는 JavaScript 클로저의 모든 주요 사항을 다룹니다. *

다음은 더하고 곱할 수있는 계산기를 만드는 팩토리입니다.

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

요점 : 각 호출 make_calculator은 새로운 지역 변수 를 생성합니다.이 변수 는 반환 후에도 n계산기 addmultiply함수 에서 계속 사용할 수 있습니다 make_calculator.

스택 프레임에 익숙하다면이 계산기는 이상하게 보입니다. 반환 n후 어떻게 계속 액세스 할 수 make_calculator있습니까? 그 대답은 JavaScript가 "스택 프레임"을 사용하지 않고 "힙 프레임"을 사용한다는 것을 상상하는 것입니다.이 함수는 함수 호출 후에도 계속 반환 될 수 있습니다.

외부 함수 ** 에서 선언 된 액세스 변수 add와 같은 내부 함수 는 클로저 라고 합니다.multiply

그것은 거의 모든 것이 닫히는 것입니다.


* 예를 들어, 변수가 선언되기 전에 사용할 수 있다는 것을 보여주는 예제 6, 클로저에 대해서는 전혀 알지 못했지만 완전히 알 수없는 좋은 사실이라는 점을 제외하고 는 다른 답변 에서 제공된 "Closure for Dummies"기사의 모든 부분을 다룹니다 . 또한 함수가 인수를 지역 변수 (명명 된 함수 인수)로 복사하는 점 (1)과 (2) 숫자 복사가 새로운 숫자를 생성하지만 객체 참조를 복사한다는 점을 제외 하면 수용된 응답의 모든 점을 포함 합니다 동일한 객체에 대한 다른 참조를 제공합니다. 이것들은 또한 알아두면 좋지만 클로저와 완전히 무관하다. 또한 이 답변 의 예제와 매우 비슷 하지만 조금 짧고 추상적입니다. 요점은 다루지 않습니다.이 답변 이나 JavaScript가 현재의 플러그인을 어렵게하는 이 댓글내부 변수에 대한 루프 변수의 값 : "연결"단계는 내부 함수를 둘러싼 각 루프 반복에서 호출되는 도우미 함수로만 수행 할 수 있습니다. (엄밀히 말하면 내부 함수는 플러그인이있는 것보다는 변수의 도우미 함수 복사본에 액세스합니다.) 다시 말하면 클로저를 만들 때 매우 유용하지만 클로저가 무엇인지 또는 어떻게 작동하는지는 아닙니다. 변수가 저장 공간이 아닌 값에 묶여있는 ML과 같은 기능 언어에서 다르게 작동하는 클로저로 인해 추가적인 혼란이 있습니다. 즉, 클로저를 방식으로 이해하는 사람의 지속적인 흐름 (즉, "플러그 인 방식")입니다. 변수가 항상 저장 공간에 바인딩되고 절대로 값에 바인딩되지 않는 JavaScript의 경우 올바르지 않습니다.

** 이 답변이 명확하게 지적했듯이 , 여러 외부 함수, 중첩 된 경우 또는 전역 컨텍스트에서도 가능합니다 .


서문 :이 답변은 질문이있을 때 작성되었습니다.

옛 앨버트 (Albert)와 마찬가지로 : "6 살짜리에게 설명 할 수 없다면, 당신은 정말로 그것을 이해할 수 없습니다."나는 JS 폐쇄를 27 세 친구에게 설명하려고 시도했지만 완전히 실패했다.

아무도 내가 6 살이며 그 주제에 이상하게 관심이 있다고 생각할 수 있습니까?

나는 처음 질문을 문자 그대로 받아들이려고 시도한 유일한 사람들 중 한 명이라고 확신한다. 그 이후로 질문은 여러 번 변이되었으므로 이제 내 대답은 어리석은 것처럼 보이고 어리석은 것처럼 보일 수 있습니다. 바라건대 이야기의 일반적인 아이디어는 일부 사람들에게는 재미있을 것입니다.

저는 어려운 개념을 설명 할 때 비유와 은유를 좋아합니다. 그래서 이야기로 손을 들어 봅니다.

옛날 옛적에:

공주가 있었어.

function princess() {

그녀는 모험이 넘치는 멋진 세상에서 살았습니다. 그녀는 그녀의 챠밍 왕자를 만났고, 유니콘을 타고 그녀의 세계를 돌아 다녔고, 용과 싸우고, 말하는 동물과 마주 쳤습니다. 그리고 다른 많은 환상적인 것들을 보았습니다.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

그러나 그녀는 항상 지루하고 어른스러운 세상으로 돌아 가야합니다.

    return {

그리고 그녀는 종종 공주로서 그녀의 최신 놀라운 모험을 그들에게 말할 것입니다.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

하지만 그들이 볼 수있는 것은 어린 소녀뿐입니다.

var littleGirl = princess();

... 마술과 환상에 관한 이야기.

littleGirl.story();

그리고 어른들이 진짜 공주를 알았을지라도 그들은 결코 그들을 볼 수 없기 때문에 유니콘이나 용을 결코 믿지 않을 것입니다. 어른들은 어린 소녀의 상상 속에서만 존재했다고 말했다.

그러나 우리는 진실을 알고 있습니다. 공주님이 안에있는 어린 소녀 ...

... 정말 어린 소녀와 함께 공주입니다.


이것은 몇 가지 다른 답변에 나타나는 클로저에 대한 몇 가지 (가능한) 오해를 해결하려는 시도입니다.

  • 클로저는 내부 함수를 반환 할 때 생성 될뿐만 아니라 사실, 닫는 함수를 생성하기 위해 닫는 함수 를 전혀 반환 할 필요가 없습니다 . 대신 내부 함수를 외부 범위에있는 변수에 할당하거나 나중에 즉시 호출 할 수있는 다른 함수에 인수로 전달할 수 있습니다. 따라서 내부 함수가 호출 될 때마다 내부 함수가 호출 될 때마다 내부 함수가 해당 클로저에 액세스 할 수 있기 때문에 내부 함수 가 호출되는 즉시 내부 함수가 닫히게됩니다.
  • 클로저는 해당 범위 에있는 이전 변수 의 복사본을 참조하지 않습니다 . 변수 자체는 클로저의 일부이므로 해당 변수 중 하나에 액세스 할 때 표시되는 값은 액세스 할 때의 최신 값입니다. 이것은 루프 내부에서 생성 된 내부 함수가 까다로울 수 있습니다. 함수가 생성되거나 호출 될 때 변수 복사본을 얻는 대신 각 외부 변수가 동일한 외부 변수에 액세스 할 수 있기 때문입니다.
  • 클로저의 "변수"에는 함수 내에 선언 된 명명 된 함수포함 됩니다. 또한 함수의 인수를 포함합니다. 클로저는 클로저의 변수를 전역 범위까지 액세스 할 수 있습니다.
  • 클로저는 메모리를 사용하지만 자바 스크립트는 자체적으로 참조되지 않은 순환 구조를 정리하므로 메모리 누수가 발생하지 않습니다 . 클로저를 포함하는 Internet Explorer 메모리 누수는 클로저를 참조하는 DOM 특성 값의 연결이 끊어 질 때 만들어 지므로 순환 구조에 대한 참조가 유지됩니다.

클로저는 모든 사람들이 어쨌든 직관적으로 작업 할 것으로 예상되는 일부 동작 작업을 작성하는 데 사용되기 때문에 설명하기 어렵습니다. 나는 그들을 설명 할 수있는 가장 좋은 방법을 찾는다. (그리고 그들이하는 일을 배웠던 방식) 그들없이 상황을 상상하는 것이다.

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

자바 스크립트 클로저를 알지 못하면 어떻게 될까요? 마지막 줄의 메서드를 메서드 본문 (기본적으로 함수 호출이하는 기능)으로 바꾸면됩니다.

console.log(x + 3);

자, x 의 정의는 어디에 있습니까? 현재 범위에서 정의하지 않았습니다. 유일한 해결책은 plus5 가 범위 (또는 부모의 범위)를 전달하도록하는 것입니다. 이렇게하면 x 는 잘 정의되어 있고 값 5에 바인딩됩니다.


6 살짜리 아이에 대한 대답 (함수가 무엇인지, 변수가 무엇인지, 데이터가 무엇인지 알고 있다고 가정)

함수는 데이터를 반환 할 수 있습니다. 함수에서 반환 할 수있는 데이터 중 하나는 또 다른 함수입니다. 새 함수가 반환되면 함수를 만든 함수에서 사용 된 모든 변수와 인수가 사라지지 않습니다. 대신 해당 부모 함수는 "닫습니다." 다른 말로하면, 그 안을 들여다보고 반환 된 함수를 제외하고는 사용 된 변수를 볼 수있는 것이 없습니다. 이 새로운 함수는 함수를 생성 한 함수를 되돌아보고 내부의 데이터를 볼 수있는 특수 기능을 가지고 있습니다.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

그것을 설명 할 수있는 또 하나의 간단한 방법은 범위에 있습니다.

더 큰 범위 안에 더 작은 범위를 만들면 항상 작은 범위가 더 큰 범위에있는 것을 볼 수 있습니다.


폐쇄에 Wikipedia :

컴퓨터 과학에서 클로저는 해당 함수의 비 지역적 이름 (자유 변수)에 대한 참조 환경과 함께 함수입니다.

기술적으로 JavaScript 에서는 모든 함수가 클로저 입니다. 항상 주변 범위에 정의 된 변수에 액세스 할 수 있습니다.

때문에 자바 스크립트의 범위 - 정의 건설은 함수 가 아닌 다른 언어에서 같은 코드 블록 에 의해 우리는 일반적으로 무엇을 의미 폐쇄 자바 스크립트가 A는 이미 실행 된 주변 기능에 정의 된 로컬이 아닌 변수 작업 기능 .

클로저는 숨겨진 개인 데이터를 사용하여 함수를 만드는 데 자주 사용됩니다 (그러나 항상 그런 것은 아닙니다).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

전자

위의 예제는 한 번 실행 된 익명의 함수를 사용합니다. 하지만 그렇게 할 필요는 없습니다. 이름을 지정 mkdb하고 나중에 실행할 수 있으며 , 호출 될 때마다 데이터베이스 기능을 생성합니다. 생성 된 모든 함수에는 고유 한 숨겨진 데이터베이스 객체가 있습니다. 클로저의 또 다른 사용 예는 함수를 반환하지 않을 때이지만 여러 목적을 위해 여러 함수를 포함하는 객체이며 각 함수는 동일한 데이터에 액세스 할 수 있습니다.


나는 대답이 왜 그렇게 복잡한지 이해하지 못한다.

다음은 클로저입니다.

var a = 42;

function b() { return a; }

예. 당신은 아마 하루에 여러 번 사용합니다.


클로저가 특정 문제를 해결하기위한 복잡한 디자인 해킹이라고 생각할 이유는 없습니다. 아니요, 클로저는 함수가 선언 된 (실행되지 않음) 관점에서 상위 범위 에서 오는 변수를 사용하는 것 입니다.

이제 무엇을 할 수 있습니다 당신이 더 아름다운 될 수 있습니다 할 다른 답변을 볼 수 있습니다.


dlaliberte에 의한 첫 번째 예제의 예제 :

클로저는 내부 함수를 반환 할 때 생성 될뿐만 아니라 사실, 둘러싼 함수는 전혀 반환 할 필요가 없습니다. 대신 내부 함수를 외부 범위의 변수에 할당하거나 인수로 사용하여 다른 함수에 즉시 사용할 수 있습니다. 따라서 내부 함수가 호출 되 자마자 내부 함수에 액세스 할 수 있기 때문에 내부 함수의 클로저는 내부 함수가 호출 된 시점에 이미 존재합니다.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

아마 6 살짜리 아이 중 가장 조숙 한 아이 였을 지 모르지만 자바 스크립트에서 클로저 개념을 만드는 데 도움이되는 몇 가지 예가 나를 클릭합니다.

클로저는 다른 함수의 범위 (변수 및 함수)에 액세스 할 수있는 함수입니다. 클로저를 만드는 가장 쉬운 방법은 함수 내의 함수를 사용하는 것입니다. JavaScript에서 함수는 항상 함수를 포함하는 범위에 액세스 할 수 있기 때문입니다.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERT : 원숭이

위의 예제에서 innerFunction을 호출하는 outerFunction이 호출됩니다. innerVunction이 outerVar의 값을 올바르게 경고 함으로서 어떻게 입증되는지를 주목하라.

이제 다음 사항을 고려하십시오.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERT : 원숭이

referenceToInnerFunction은 outerFunction ()으로 설정되어 단순히 innerFunction에 대한 참조를 반환합니다. referenceToInnerFunction이 호출되면 outerVar를 반환합니다. 다시 위와 같이 innerFunction이 outerFunction의 변수 인 outerVar에 액세스 할 수 있음을 보여줍니다. 또한 outerFunction이 실행을 마친 후에도이 액세스를 유지한다는 점이 흥미 롭습니다.

그리고 여기서 일이 정말로 흥미로워집니다. outerFunction을 없애고 null로 설정하면 referenceToInnerFunction이 outerVar 값에 대한 액세스를 잃어 버릴 것이라고 생각할 수 있습니다. 그러나 이것은 사실이 아닙니다.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERT : 원숭이 ALERT : 원숭이

하지만이게 어때? outerFunction이 null로 설정되었으므로 referenceToInnerFunction이 outerVar의 값을 여전히 알 수 있습니까?

referenceToInnerFunction이 여전히 outerVar 값에 액세스 할 수있는 이유는 innerFunction을 outerFunction에 배치하여 클로저를 처음 만들 때 innerFunction이 outerFunction의 범위 (해당 변수 및 함수)에 대한 참조를 해당 범위 체인에 추가했기 때문입니다. 이것이 의미하는 바는 innerFunction이 outerVar를 포함한 outerFunction의 모든 변수에 대한 포인터 또는 참조를 가짐을 의미합니다. 따라서 outerFunction이 실행을 마쳤거나 심지어 삭제되거나 null로 설정된 경우에도 outerVar와 같은 해당 범위의 변수는 반환 된 innerFunction 부분에서 내부 참조를 사용하여 메모리에 고정됩니다 referenceToInnerFunction. outerVar와 나머지 outerFunction 변수를 메모리에서 완전히 해제하려면이 뛰어난 참조를 제거해야합니다.referenceToInnerFunction을 null로 설정하여 말하십시오.

//////////

주목할 클로저에 대한 다른 두 가지. 첫째, 클로저는 항상 포함 함수의 마지막 값에 액세스 할 수 있습니다.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

고지 : 고릴라

둘째, 클로저가 만들어지면 클로저 함수의 변수와 함수 모두에 대한 참조가 유지됩니다. 그것은 선택하고 선택하지 않습니다. 그렇지만 폐쇄는 메모리 집약적 일 수 있으므로 최소한 또는 조심스럽게 사용해야합니다. 포함 된 함수가 실행을 마친 후에 많은 변수를 메모리에 보관할 수 있습니다.


좋아, 6 살짜리 아이와 이야기하면서, 나는 아마도 다음과 같은 협회를 사용할 것입니다.

상상해보십시오 - 당신은 집 전체에서 형제와 자매와 놀고 있습니다. 그리고 당신은 장난감으로 주위를 돌아 다니며 형제의 방으로 가져 왔습니다. 잠시 후 동생이 학교에서 돌아와서 자기 방으로 갔다가 안에 잠겨 있었으므로 지금은 더 이상 직접적인 방법으로 장난감에 접근 할 수 없었습니다. 하지만 문을 두드리고 형제에게 그 장난감을 물어볼 수 있습니다. 이를 장난감의 폐쇄 라고합니다 . 당신의 형제가 당신을 위해 그것을 만들었고, 그는 이제 바깥 범위에 있습니다.

문이 초안에 잠겨서 아무도 내부 기능을 수행하지 못하는 상황 (일반 기능 실행)과 비교하여 일부 지역 화재가 발생하여 (가비지 컬렉터 : D) 방을 태우고 새로운 방이 만들어져 이제 퇴원 할 수 있습니다 거기에 또 하나의 장난감 (새 함수 인스턴스)이 있지만 첫 번째 실 인스턴스에 남겨진 동일한 장난감을 결코 얻지 못합니다.

상급 자녀의 경우 다음과 같은 것을 넣을 것입니다. 그것은 완벽하지는 않지만 그것이 무엇인지 느껴지게합니다 :

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

보시다시피, 방에 남아있는 장난감은 방이 잠겨 있어도 형제를 통해 접근 할 수 있습니다. 여기 에 놀 수 있는 jsbin 이 있습니다.


Closures 작성자는 Closures 에 대해 설명하고 클로저가 필요한 이유를 설명하고 클로저를 이해하는 데 필요한 LexicalEnvironment를 설명합니다.
요약은 다음과 같습니다.

변수에 액세스했지만 로컬 변수가 아닌 경우에는 어떻게해야합니까? 여기처럼 :

이 경우 인터프리터는 외부 LexicalEnvironment객체 에서 변수를 찾습니다 .

프로세스는 두 단계로 구성됩니다.

  1. 첫째, 함수 f가 생성되면 빈 공간에 생성되지 않습니다. 현재 LexicalEnvironment 객체가 있습니다. 위의 경우 창 (a는 함수 생성시 정의되지 않음)입니다.

함수가 생성되면 현재 [LexicalEnvironment]를 참조하는 숨겨진 속성 [[Scope]]을 얻습니다.

변수를 읽었지만 어디에서도 찾을 수없는 경우 오류가 생성됩니다.

중첩 된 함수

함수는 스코프 체인이라고도 부를 수있는 LexicalEnvironments의 체인을 형성하여 다른 함수의 내부에 중첩 될 수 있습니다.

따라서 함수 g는 g, a 및 f에 액세스 할 수 있습니다.

마감

중첩 된 함수는 외부 함수가 완료된 후에도 계속 살 수 있습니다.

LexicalEnvironments 마크 업 :

우리가 볼 때, this.say사용자 개체의 속성입니다, 그래서 사용자가 완료된 후 거주하고 있습니다.

그리고 기억하면, 언제 this.say만들어 this.say.[[Scope]]지나요? (모든 함수와 마찬가지로) 현재 LexicalEnvironment에 대한 내부 참조 를 얻습니다. 따라서 현재 사용자 실행의 LexicalEnvironment는 메모리에 남아 있습니다. 사용자의 모든 변수도 속성이므로 일반적으로 정크가 아닌 신중하게 보관됩니다.

전체적인 요점은 내부 함수가 미래에 외부 변수에 액세스하려고한다면 그렇게 할 수 있다는 것입니다.

요약:

  1. 내부 함수는 외부 LexicalEnvironment에 대한 참조를 유지합니다.
  2. 내부 함수는 외부 함수가 완료 되더라도 언제든지 변수를 액세스 할 수 있습니다.
  3. 브라우저는 LexicalEnvironment와 모든 속성 (변수)을 참조하는 내부 함수가있을 때까지 메모리에 LexicalEnvironment 및 모든 속성 (변수)을 유지합니다.

이를 클로저 (closure)라고합니다.


JavaScript의 함수는 (C 언어에서와 같이) 일련의 명령어에 대한 참조 일뿐만 아니라 사용되는 모든 비 지역 변수 (캡처 된 변수)에 대한 참조로 구성된 숨겨진 데이터 구조도 포함합니다. 이러한 두 조각 기능을 클로저 (closure)라고합니다. JavaScript의 모든 함수는 클로저로 간주 될 수 있습니다.

클로저는 상태가있는 함수입니다. "this"는 함수에 대한 상태도 제공한다는 점에서 "this"와 다소 비슷하지만 "this"는 별도의 객체입니다 ( "this"는 단지 멋진 매개 변수 일 뿐이며이를 영구적으로 바인딩하는 유일한 방법입니다. 함수는 클로저를 만드는 것입니다.) "this"와 함수는 항상 별도로 존재하지만, 함수는 그 closure와 분리 될 수 없으며 언어는 캡처 된 변수에 액세스 할 수있는 방법을 제공하지 않습니다.

어휘 적으로 중첩 된 함수가 참조하는 이러한 외부 변수는 모두 어휘 적으로 묶는 함수의 체인에있는 로컬 변수이므로 (전역 변수는 루트 함수의 로컬 변수라고 가정 할 수 있음) 함수의 모든 단일 실행은 그것의 지역 변수를 가지면, 중첩 된 함수는 새로운 클로저를 생성합니다 (콜백으로 등록하는 등의 기능을 수행 할 때마다 실행을 나타내는 참조 된 비 로컬 변수 세트가 있음) 문맥).

또한 JavaScript의 로컬 변수는 스택 프레임이 아니라 힙에 만들어지고 아무도 참조하지 않는 경우에만 삭제된다는 점을 알아야합니다. 함수가 반환되면 로컬 변수에 대한 참조가 감소하지만 현재 실행 중에 클로저의 일부가되었고 어휘 적으로 중첩 된 함수에 의해 참조되는 경우에도 여전히 null이 될 수 있습니다 (이 경우에는 이러한 중첩 된 함수는 반환되거나 다른 외부 코드로 전송됩니다.

예 :

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

OK, 6 살짜리 팬이 닫힙니다. 가장 단순한 폐쇄 사례를 듣고 싶습니까?

다음 상황을 상상해보십시오 : 운전자가 차에 앉아 있습니다. 그 차는 비행기 안에 있어요. 비행기가 공항에 있습니다. 운전자가 자신의 차 바깥에있는 물건에 접근 할 수는 있지만 비행기 내부에는 비행기가 공항을 떠나더라도 폐쇄가 가능합니다. 그게 전부 야.27 세가되면 더 자세한 설명 이나 아래의 예를보십시오.

여기 내 비행기 이야기를 코드로 변환 할 수있는 방법이 있습니다.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");


나는 좋은 / 나쁜 비교에 의해 잘 배우는 경향이 있습니다. 나는 작동 코드가 뒤따를 때 누군가가 만날 가능성이있는 작동 코드를보고 싶어한다. 나는 jsFiddle 을 비교해 보았고 비교를 해보고 가장 간단한 설명과의 차이점을 생각해 냈습니다.

클로저 완료 :

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • 위의 코드 createClosure(n)에서 루프의 모든 반복에서 호출됩니다. 새로운 함수 범위에서 생성 된 새로운 변수이고 외부 범위에 바인딩 된 것과 같은 변수가 아니라는 n점을 강조하기 위해 변수의 이름을 지정 했습니다 .index

  • 이렇게하면 새 범위가 만들어 n지고 해당 범위에 바인딩됩니다. 즉, 각 반복마다 하나씩 10 개의 분리 된 범위가 있음을 의미합니다.

  • createClosure(n) 해당 범위 내의 n을 반환하는 함수를 반환합니다.

  • 각 스코프 내 에서 호출 된 n값은 무엇이든간에 바인딩 createClosure(n)되므로 반환 된 중첩 된 함수는 항상 호출 n되었을 때 의 값을 반환합니다 createClosure(n).

클로저가 잘못됨 :

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • 위의 코드에서 루프는 createClosureArray()함수 내에서 이동 되었고 함수는 이제 완성 된 배열을 반환합니다.이 배열은 언뜻보기에 더 직관적으로 보입니다.

  • 명확하지 않은 것은 이후 createClosureArray()루프가 반복 될 때마다 하나의 범위가 만들어 지기 때문에이 함수에 대해 만들어진 이후 에만 호출 된다는 것입니다.

  • 이 함수 내에서 명명 index된 변수 가 정의됩니다. 루프가 돌아가서 배열에 기능을 추가하고 추가합니다 index. 그는 주 index내에서 정의되고 createClosureArray오직 한 번만 호출됩니다 기능.

  • createClosureArray()함수 내에 하나의 범위 만 있기 때문에 해당 범위 내의 index값에만 바인딩됩니다. 즉, 루프가 값을 변경 할 때마다 index해당 범위 내 에서 루프 를 참조하는 모든 항목에 대해 루프가 변경 됩니다.

  • 배열에 추가 된 모든 함수 index는 첫 번째 예제와 같이 10 개의 서로 다른 범위의 10 가지 변수 대신 정의 된 상위 범위 의 SAME 변수를 반환합니다 . 최종 결과는 모든 10 개의 함수가 동일한 범위에서 동일한 변수를 반환한다는 것입니다.

  • 루프가 끝나고 index수정이 끝나면 최종 값은 10 이었으므로 배열에 추가 된 모든 함수 index는 이제 단일 변수 의 값을 반환합니다.이 값 은 이제 10으로 설정됩니다.

결과

클로즈드 원 클릭
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

폐쇄가 잘못되었습니다.
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


나는 폐쇄를 설명하면서 잠시 블로그 포스트를 썼다. 여기에 클로저에 대해 내가 당신이 원하는지 에 관해 말한 내용이 있습니다.

클로저는 함수에 영구적 인 전용 변수 , 즉 하나의 함수만이 알고있는 변수가 있고, 실행 된 이전 시간의 정보를 추적 할 수있는 방법입니다.

이러한 의미에서 함수는 private 속성을 가진 객체처럼 약간 동작합니다.

전체 게시물 :

그래서이 폐쇄 장치는 무엇입니까?


잠을 자고 있는데 댄을 초대합니다. Dan에게 하나의 XBox 컨트롤러를 가져 오라고 말합니다.

댄은 폴을 초대합니다. Dan은 Paul에게 하나의 컨트롤러를 가져 오라고합니다. 얼마나 많은 컨트롤러가 파티에 가져 왔습니까?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

클로저는 내부 함수가 외부 함수의 변수에 액세스 할 수있는 곳입니다. 아마도 클로저를 위해 얻을 수있는 가장 단순한 한 줄짜리 설명 일 것입니다.





closures