JavaScript 클로저는 어떻게 작동합니까?


7636

함수, 변수 등으로 구성된 개념을 알고 있지만 클로저 자체를 이해하지 못하는 사람에게 JavaScript 클로저를 어떻게 설명 하시겠습니까?

내가 본 구성표 예를 위키 백과에 제공을하지만, 불행히도 그것은 도움이되지 않았다.


391
이것과 많은 답변에 대한 나의 문제는 자바 스크립트에서 클로저가 왜 필요한지 설명하고 실제로 사용하는 실제 상황을 설명하기보다는 추상적이면서 이론적 인 관점에서 접근한다는 것입니다. 당신은 항상 생각하지만 "왜, 왜?"라고 생각해야하는 tl; dr 기사로 끝납니다. 클로저는 다음 두 가지 JavaScript 현실을 처리하는 깔끔한 방법입니다. 범위는 블록 수준이 아닌 기능 수준에 있으며 b. JavaScript에서 실제로하는 많은 작업은 비동기식 / 이벤트 기반입니다.
제레미 버튼

53
@Redsandro 이벤트 중심 코드를 작성하기가 훨씬 쉽습니다. HTML 또는 사용 가능한 기능에 대한 세부 사항을 결정하기 위해 페이지가로드 될 때 함수를 실행할 수 있습니다. 해당 함수에서 처리기를 정의하고 설정할 수 있으며 처리기를 다시 쿼리하지 않고도 호출자가 호출 될 때마다 모든 컨텍스트 정보를 사용할 수 있습니다. 문제점을 한 번 해결하고 핸들러 재 호출에 대한 오버 헤드를 줄이면서 해당 핸들러가 필요한 모든 페이지에서 재사용하십시오. 동일한 데이터가없는 언어로 동일한 데이터가 두 번 다시 매핑되는 것을 본 적이 있습니까? 클로저는 이런 종류의 것을 피하기가 훨씬 쉽습니다.
Erik Reppen 2016 년

1
@Erik Reppen 의 답변에 감사드립니다. 실제로, 나는 스스로 재사용하고 오버 헤드를 동일하게 줄이면서 랩핑 코드를 100 % 적게 요구하는 closure것과는 대조적으로 읽기 어려운 코드 의 이점에 대해 궁금했습니다 Object Literal.
Redsandro

6
Java 프로그래머에게는 짧은 대답은 내부 클래스와 동등한 기능이라는 것입니다. 내부 클래스는 외부 클래스의 인스턴스에 대한 암시 적 포인터를 보유하며 거의 동일한 목적으로 (즉, 이벤트 핸들러 작성) 사용됩니다.
Boris van Schooten 2016 년

8
이 실용적인 예제가 매우 유용하다는 것을 알았습니다. youtube.com/watch?v=w1s9PgtEoJs
Abhi

답변:


7357

클로저는 다음의 쌍입니다.

  1. 기능
  2. 해당 함수의 외부 범위에 대한 참조 (어휘 환경)

어휘 환경은 모든 실행 컨텍스트 (스택 프레임)의 일부이며 식별자 (예 : 로컬 변수 이름)와 값 사이의 맵입니다.

JavaScript의 모든 함수는 외부 어휘 환경에 대한 참조를 유지합니다. 이 참조는 함수가 호출 될 때 작성된 실행 컨텍스트를 구성하는 데 사용됩니다. 이 참조는 함수 내부의 코드가 함수 호출시기 및 위치에 관계없이 함수 외부에서 선언 된 변수를 "볼"수있게합니다.

함수가 함수에 의해 호출 된 후 다른 함수에 의해 호출 된 경우 외부 어휘 환경에 대한 참조 체인이 작성됩니다. 이 체인을 스코프 체인이라고합니다.

다음 코드에서 변수를 닫으면 호출 inner될 때 작성된 실행 컨텍스트의 어휘 환경으로 클로저를 형성 합니다 .foosecret

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

다시 말해, JavaScript에서 함수는 개인 "상태 상자"에 대한 참조를 가지고 있으며,이 함수 (및 동일한 어휘 환경 내에 선언 된 다른 함수) 만 액세스 할 수 있습니다. 이 상태 상자는 함수 호출자에게는 보이지 않으므로 데이터 숨기기 및 캡슐화를위한 탁월한 메커니즘을 제공합니다.

JavaScript의 함수는 변수 (퍼스트 클래스 함수)처럼 전달 될 수 있습니다. 즉, 이러한 기능 및 상태 쌍은 프로그램 주위에 전달 될 수 있습니다. C ++에서 클래스의 인스턴스를 전달하는 방법과 유사합니다.

JavaScript에 클로저가 없다면 함수간에 더 많은 상태를 명시 적으로 전달해야 하므로 매개 변수 목록이 길어지고 소음이 커집니다.

따라서 함수가 항상 개인 상태에 액세스하도록하려면 클로저를 사용할 수 있습니다.

... 우리 종종 상태를 함수와 연결하려고합니다. 예를 들어, Java 또는 C ++에서 개인 인스턴스 변수 및 메소드를 클래스에 추가하면 상태가 기능과 연관됩니다.

C 및 대부분의 다른 일반 언어에서 함수가 반환 된 후 스택 프레임이 손상되어 모든 로컬 변수에 더 이상 액세스 할 수 없습니다. JavaScript에서 다른 함수 내에 함수를 선언하면 외부 함수의 로컬 변수는 반환 된 후에도 계속 액세스 할 수 있습니다. 이러한 방법으로, 위의 코드에서, secret함수 객체를 사용 가능한 상태로 유지 inner, 가에서 반환되었습니다 foo.

폐쇄 사용

클로저는 함수와 관련된 개인 상태가 필요할 때마다 유용합니다. 이것은 매우 일반적인 시나리오입니다. JavaScript는 2015 년까지 클래스 구문이 없었으며 여전히 개인 필드 구문이 없습니다. 클로저는이 요구를 충족시킵니다.

프라이빗 인스턴스 변수

다음 코드에서 함수 toString는 자동차의 세부 정보를 닫습니다.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

기능적 프로그래밍

다음 코드에서 함수는 inner모두 이상 종료 fnargs.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

이벤트 중심 프로그래밍

다음 코드에서 함수 onClick는 변수를 닫습니다 BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

모듈화

다음 예제에서 모든 구현 세부 사항은 즉시 실행되는 함수 표현식 안에 숨겨져 있습니다. 기능 ticktoString민간 상태 이상 가까이 기능은 그들의 작업을 완료해야합니다. 클로저를 통해 코드를 모듈화하고 캡슐화 할 수있었습니다.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

실시 예 1

이 예제는 로컬 변수가 클로저에 복사되지 않음을 보여줍니다. 클로저는 원래 변수 자체에 대한 참조를 유지 합니다 . 마치 외부 함수가 종료 된 후에도 스택 프레임이 메모리에 계속 살아있는 것처럼 보입니다.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

실시 예 2

다음 코드, 세 가지 방법에서 log, increment그리고 update같은 어휘 환경에 대한 모든 가깝습니다.

그리고 매번 createObject호출 될 때마다 새로운 실행 컨텍스트 (스택 프레임)가 생성되고 완전히 새로운 변수가 생성되고이 새로운 변수 xlog근접한 새로운 기능 세트 등이 생성됩니다.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

실시 예 3

를 사용하여 선언 var된 변수를 사용하는 경우 닫고있는 변수를 이해해야합니다. 를 사용하여 선언 된 변수 var는 게양됩니다. 이는 letand 의 도입으로 인해 최신 JavaScript에서 문제가 훨씬 적습니다 const.

다음 코드에서는 루프를 돌 때마다 새 함수 inner가 작성되어 닫힙니다 i. 그러나 var i루프 외부에 들어 있기 때문에 이러한 모든 내부 함수는 동일한 변수에 대해 닫히므로 i(3) 의 최종 값이 세 번 인쇄됩니다.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

최종 포인트 :

  • 함수가 JavaScript로 선언 될 때마다 클로저가 작성됩니다.
  • 반환 function외부 함수 내부 상태를 외부 함수가 실행을 완료 한 후에도, 반환 내부 기능을 암시 사용할 수 있으므로 다른 함수 내부에서 것은 클로저의 고전적인 예이다.
  • eval()함수 안에서 사용할 때마다 클로저가 사용됩니다. eval함수의 로컬 변수를 참조 할 수 있는 텍스트 이며, 엄격하지 않은 모드에서는을 사용하여 새 로컬 변수를 만들 수도 있습니다 eval('var foo = …').
  • 함수 내에서 new Function(…)( Function constructor ) 를 사용하면 어휘 환경을 닫지 않고 전역 컨텍스트를 닫습니다. 새 함수는 외부 함수의 로컬 변수를 참조 할 수 없습니다.
  • 자바 스크립트의 클로저는 참조 (유지처럼 NOT 상단의 전역 객체를 차례로 등등의 외부 범위에 대한 참조를 유지하고, 함수 선언의 시점에서 모든 방법을 범위에 사본) 스코프 체인
  • 함수가 선언되면 클로저가 생성됩니다. 이 클로저는 함수가 호출 될 때 실행 컨텍스트를 구성하는 데 사용됩니다.
  • 함수가 호출 될 때마다 새로운 로컬 변수 세트가 작성됩니다.

연결


74
"JavaScript에서 클로저는 함수가 종료 될 때와 마찬가지로 모든 로컬 변수의 복사본을 유지하는 것과 같습니다." 그러나 몇 가지 이유로 오해의 소지가 있습니다. (1) 클로저를 만들기 위해 함수 호출을 종료하지 않아도됩니다. (2) 지역 변수 의 사본이 아니라 변수 자체입니다. (3) 누가이 변수에 접근 할 수 있는지 말하지 않습니다.
dlaliberte

27
예제 5는 코드가 의도 한대로 작동하지 않는 "gotcha"를 보여줍니다. 그러나 그것을 고치는 방법을 보여주지는 않습니다. 이 다른 대답 은 그것을 수행하는 방법을 보여줍니다.
Matt

190
이 글이 "Closures Are Not Magic"이라는 큰 굵은 글씨로 시작하고 첫 번째 예제 인 "매직은 JavaScript에서 함수 참조에 생성 된 클로저에 대한 비밀 참조가 있다는 것입니다."
앤드류 Macheret

6
예제 # 3은 클로저를 자바 스크립트 게양과 혼합합니다. 이제는 호이스트 동작을 일으키지 않으면 서 폐쇄만을 설명하는 것이 충분히 어렵다고 생각합니다. :이 나에게 가장 도움 Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.에서 developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
CARAMBA

3
ECMAScript 6은이 훌륭한 기사에서 클로저에 대한 내용을 변경할 수 있습니다. 예를 들어, 예 5 let i = 0대신에 를 사용 하면 원래 원하는 것을 인쇄합니다. var i = 0testList()
Nier

3988

JavaScript의 모든 함수는 외부 어휘 환경에 대한 링크를 유지합니다. 어휘 환경은 범위 내 모든 이름 (예 : 변수, 매개 변수)과 해당 값의 맵입니다.

따라서 function키워드 를 볼 때마다 해당 함수 내부의 코드는 함수 외부에서 선언 된 변수에 액세스 할 수 있습니다.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

이것은 기록합니다 16함수 bar가 외부 함수의 어휘 환경에 존재 하는 매개 변수 x와 변수를 닫으 므로tmp 됩니다 foo.

기능 bar은 어휘 기능 환경과의 연계와 함께 foo폐쇄입니다.

클로저를 만들기 위해 함수를 반환 할 필요는 없습니다 . 선언으로 간단히 말해서 모든 함수는 둘러싼 어휘 환경을 닫아 폐쇄를 형성합니다.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

위의 함수는 16을 기록합니다. 내부 코드 는 더 이상 직접 범위에 있지 않더라도 barargument x와 variable 을 계속 참조 할 수 있기 때문 tmp입니다.

그러나 이후 tmp 여전히 bar폐쇄 내부에 매달려 증가시킬 수 있습니다. 전화 할 때마다 증가합니다 bar.

클로저의 가장 간단한 예는 다음과 같습니다.

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

JavaScript 함수가 호출되면 새로운 실행 컨텍스트 ec가 작성됩니다. 함께 함수 인수 및 타겟 오브젝트이 실행 컨텍스트 또한, 위의 예에서는 외측 어휘 환경에서 선언 된 변수 (즉, 호출 실행 콘텍스트의 사전 환경에 대한 링크를 수신 모두 ab )를 사용할 수 있습니다 ec.

모든 함수에는 외부 어휘 환경에 대한 링크가 있으므로 모든 함수는 클로저를 작성합니다.

변수 자체 는 복사본이 아닌 클로저 내에서 볼 수 있습니다 .


24
@feeela : 그렇습니다. 모든 JS 함수는 클로저를 만듭니다. 참조되지 않은 변수는 최신 JS 엔진에서 가비지 콜렉션에 적합 할 수 있지만 실행 컨텍스트를 작성할 때 해당 컨텍스트에 둘러싸는 실행 컨텍스트 및 해당 변수에 대한 참조가 있다는 사실을 변경하지는 않습니다. 이 함수는 원래 참조를 유지하면서 다른 변수 범위로 재배치 될 가능성이있는 객체입니다. 그것이 폐쇄입니다.

@Ali 나는 방금 제공 한 jsFiddle이 delete실패 한 이후 실제로 아무것도 증명하지 못한다는 것을 발견했습니다 . 그럼에도 불구하고 함수가 [[Scope]]로 수행 할 어휘 환경 (및 호출시 자체 어휘 환경의 기본으로 사용)은 어휘 환경을 정의하는 명령문이 실행될 때 결정됩니다. 이는 함수 실제로 참조하는 값과 범위를 이탈하는지 여부에 관계없이 함수 실행 범위의 ENTIRE 내용을 닫고 있음을 의미합니다 . 사양의
Asad Saeeduddin

8
이것은 기본 유형과 참조를 설명 할 때까지 좋은 대답이었습니다. 그것은 완전히 잘못되고 문자가 복사되는 것에 대해 이야기합니다.
Ry-

12
클로저는 클래스 기반의 객체 지향 프로그래밍에 대한 JavaScript의 답변입니다. JS는 클래스 기반이 아니므로 다른 방법으로는 구현할 수없는 것을 구현할 다른 방법을 찾아야했습니다.
Bartłomiej Zalewski

2
이것이 정답이어야합니다. 마법은 내부 기능에서 결코 발생하지 않습니다. 외부 함수를 변수에 할당 할 때 발생합니다. 이는 내부 함수에 대한 새로운 실행 컨텍스트를 작성하므로 "개인 변수"를 누적 할 수 있습니다. 물론 외부 함수에 지정된 변수가 컨텍스트를 유지했기 때문에 가능합니다. 첫 번째 대답은 실제로 일어나는 일을 설명하지 않고 모든 것을 더 복잡하게 만듭니다.
Albert Gao

2442

서문 :이 답변은 질문이 다음과 같은 경우에 작성되었습니다.

알버트가 말한 것처럼 "만약 당신이 6 살짜리 아이에게 설명 할 수 없다면, 당신은 그것을 스스로 이해하지 못한다."

아무도 내가 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();

어른들은 실제 공주를 알고 있지만 유니콘이나 용을 절대 믿지 않을 것입니다. 어른들은 어린 소녀의 상상 속에 만 존재한다고 말했다.

그러나 우리는 진실을 알고 있습니다. 공주님과 함께있는 어린 소녀가

... 실내에 어린 소녀가있는 공주입니다.


340
나는이 설명을 정말로 좋아한다. 그것을 읽고 따르지 않는 사람들에게 비유는 이것입니다. princess () 함수는 개인 데이터를 포함하는 복잡한 범위입니다. 기능 외부에서는 개인 데이터를 보거나 액세스 할 수 없습니다. 공주는 유니콘, 용, 모험 등을 상상력 (개인 데이터)으로 유지하고 어른들은 스스로를 볼 수 없습니다. 그러나 공주의 상상력은 실체가 마술 세계에 노출시키는 story()유일한 인터페이스 인 함수 의 클로저 littleGirl에 담겨 있습니다.
Patrick M

그래서 여기 story폐쇄이지만 된 코드 한 var story = function() {}; return story;littleGirl폐쇄 될 것입니다. 최소한 MDN이 클로저를 사용하는 '비공개'방법을 사용 했을 때의 인상입니다 . "세 가지 공용 함수는 동일한 환경을 공유하는 클로저입니다."
icc97

16
@ icc97 story은 예의 범위 내에서 제공되는 환경을 참조하는 클로저입니다 princess. princess또 다른 암시 적 클로저입니다. 즉 princessand 는 존재하고 있고 정의 된 환경 / 범위에 존재 littleGirl하는 parents어레이에 대한 참조를 공유합니다 . littleGirlprincess
Jacob Swartwood

6
@ BenjaminKrupp 나는 몸 안에 더 많은 연산이 있음을 보여주기 위해 암묵적인 코드 주석을 추가했습니다. princess 작성된 것보다 . 불행히도이 이야기는 이제이 스레드에서 약간 벗어났습니다. 원래 질문은 "5 살짜리에 대한 자바 스크립트 폐쇄 설명"; 내 대답은 그것을 시도조차 한 유일한 것이었다. 나는 그것이 비참하게 실패했을 것이라는 점을 의심하지 않지만, 적어도이 반응은 5 살짜리 아이의 관심을 가질 기회를 가졌을 것이다.
Jacob Swartwood

11
사실, 나에게 이것은 완벽하게 이해되었습니다. 그리고 마지막으로 공주 이야기와 모험을 사용하여 JS 클로저를 이해하면 좀 이상하게 느껴집니다.
결정화

753

문제를 진지하게 고려할 때, 우리는 전형적인 6 살짜리가인지 적으로인지 할 수있는 것을 알아 내야하지만, JavaScript에 관심이있는 사람은 그렇게 일반적이지 않습니다.

아동 개발 : 5 ~ 7 년 은 말한다 :

자녀는 2 단계 지시를 따를 수 있습니다. 예를 들어, 자녀에게 "부엌에 가서 쓰레기 봉투를 가져 오십시오"라고하면 그 방향을 기억할 수 있습니다.

이 예제를 사용하여 다음과 같이 클로저를 설명 할 수 있습니다.

주방은이라는 지역 변수가있는 클로저입니다 trashBags. 부엌 안에는 getTrashBag쓰레기 봉지 하나를 가져 와서 돌려주는 기능이 있습니다.

우리는 이것을 JavaScript로 다음과 같이 코딩 할 수 있습니다 :

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

클로저가 흥미로운 이유를 설명하는 추가 사항 :

  • makeKitchen()호출 될 때마다 고유 한 별도의으로 새 클로저가 생성됩니다 trashBags.
  • 그만큼 trashBags변수는 각각의 부엌 내부에 국부적 인 접근 밖에되지 않지만, 내부의 기능 getTrashBag속성에 대한 액세스 권한을 가지고있다.
  • 모든 함수 호출은 클로저를 생성하지만 클로저 내부에 액세스 할 수있는 내부 함수를 클로저 외부에서 호출 할 수 없다면 클로저를 유지할 필요는 없습니다. getTrashBag함수 와 함께 객체를 반환하면 여기서 수행합니다.

6
실제로, 혼란스럽게도 makeKitchen 함수 호출 은 반환되는 키친 객체가 아니라 실제 클로저입니다.
dlaliberte 2016 년

6
다른 사람들을 통해 나아갈 때이 답변은 무엇을 왜 닫았는지 설명하는 가장 쉬운 방법이라는 것을 알았습니다.
Chetabahana

3
고기와 감자가 충분하지 않은 메뉴와 전채가 너무 많습니다. "클로즈는 클래스가 제공하는 범위 지정 메커니즘이 없기 때문에 함수의 봉인 된 컨텍스트입니다."와 같이 짧은 문장으로 대답을 향상시킬 수 있습니다.
Staplerfahrer

584

밀짚 맨

버튼을 몇 번 클릭했는지 알고 세 번째 클릭마다 무언가를해야합니다 ...

상당히 명백한 해결책

// 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 고려 사항 포함)


11
폐쇄가 무엇인지에 대한 귀하의 정의에 동의하지 않습니다. 자체 호출이 필요한 이유는 없습니다. 또한의 비트 단순한 (부정확)는이 (이 질문에 대한 정상 응답의 의견이에 대해 많은 논의) "반환"해야한다 대답
제임스 몬타

40
@James 당신이 동의하지 않더라도, 그의 예 (및 전체 게시물)는 내가 본 것 중 최고입니다. 질문은 오래되지 않았고 나를 위해 해결되었지만 완전히 +1해야합니다.
전자 Satis

84
"버튼을 몇 번 클릭했는지 알고, 세 번째 클릭마다 무언가를해야합니다 ..." 이 점에 주목했습니다. 유스 케이스와 클로저가 그렇게 신비한 것이 아니며 많은 사람들이 그것을 작성했지만 공식 이름을 정확히 알지 못했음을 보여주는 솔루션.
Chris22

두 번째 예에서 "count"는 "count"값을 유지하고 "element"를 클릭 할 때마다 0으로 재설정되지 않음을 보여 주므로 좋은 예입니다. 매우 유익한!
Adam

+1 폐쇄 동작 . 자바 스크립트에서 클로저 동작함수 로 제한 할 수 있습니까? 아니면이 개념을 언어의 다른 구조에도 적용 할 수 있습니까?
Dziamid

492

클로저는 모든 사람들이 직관적으로 어쨌든 작동 할 것으로 예상되는 동작을 만드는 데 사용되기 때문에 설명하기가 어렵습니다. 나는 그것들을 설명하는 가장 좋은 방법과 그들이 하는 것을 배운 방법을 찾았 습니다.

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

JavaScript 클로저를 알지 못하면 어떻게됩니까? 마지막 줄의 호출을 메소드 본문 (기본적으로 함수 호출이 수행하는 것)으로 바꾸면 다음과 같은 결과가 나타납니다.

console.log(x + 3);

이제 정의는 어디에 x있습니까? 현재 범위에서 정의하지 않았습니다. 유일한 해결책은 그 범위 (또는 부모의 범위)를 plus5 옮기는 것입니다. 이 방법 x은 잘 정의되어 있으며 값 5에 바인딩됩니다.


11
이것은 많은 사람들이 그것이 변경 가능한 변수 자체가 아니라 반환 된 함수에 사용되는 이라고 생각하게하는 일종의 예입니다 . "return x + = y"로 변경되었거나 그 기능과 다른 함수 "x * = y"가 더 좋은 것으로 바뀌면 아무것도 복사되지 않는 것이 분명합니다. 프레임을 쌓는 데 익숙한 사람들에게는 힙 프레임을 대신 사용한다고 상상해보십시오. 함수는 함수가 반환 된 후에도 계속 존재할 수 있습니다.
Matt

14
@Matt 나는 동의하지 않는다. 예를 들어 모든 속성을 철저하게 문서화하지는 않습니다 . 그것은 환원성 이 있고 개념의 두드러진 특징을 설명하기위한 것입니다. OP는 간단한 설명을 요구했다 (“6 살짜리”). 받아들이는 대답을 받아들이십시오. 그것은 철저한 설명을하기 위해 간결한 설명을 제공하는 데 전혀 실패합니다 . (나는 바인딩은 값보다는 참조를 기준으로 이루어집니다 ... 그러나 다시, 성공적인 설명은 최소한으로 감소 하나라는 자바 스크립트의 중요한 특성이다 것을 당신과 동의 않습니다.)
콘라드 루돌프

@KonradRudolph 나는 당신의 모범의 스타일과 간결함을 좋아합니다. 마지막 부분 인 "유일한 해결책은 ..."이되도록 약간 변경하는 것이 좋습니다. 현재이 사실 않는 시나리오, 또 다른, 더 간단한 솔루션을 하지 자바 스크립트의 연속 요청에 대응하고 수행 연속 요청이 무엇인지에 대한 일반적인 오해에 해당합니다. 따라서 현재 형식의 예는 위험합니다. 이것은 속성을 철저히 나열하는 것과 관련이 없으며 x가 반환 된 함수의 내용을 이해하는 것과 관련이 있습니다.
Matt

@Matt Hmm, 나는 당신을 완전히 이해하지는 못하지만 당신이 유효한 포인트를 가지고 있음을 알기 시작합니다. 설명이 너무 짧기 때문에 요점 / 목장 또는 대화방에서 무슨 의미인지 설명 할 수 있습니까? 감사.
Konrad Rudolph

2
@KonradRudolph 나는 x + = y의 목적에 대해 명확하지 않다고 생각합니다. 목적은 반환 된 함수에 대한 반복 호출이 동일한 변수 x ( 함수가 생성 될 때 사람들이 "삽입"이라고 생각 하는 동일한 과 반대)를 사용하여 계속 유지한다는 것을 보여주었습니다 . 이것은 바이올린의 첫 두 경고와 같습니다. 추가 함수 x * = y의 목적은 반환 된 여러 함수가 모두 같은 x를 공유 함을 보여주는 것입니다.
Matt

376

좋아, 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");


26
잘 연주하고 원래 포스터에 답하십시오. 이것이 가장 좋은 대답이라고 생각합니다. 나는 짐을 비슷한 방식으로 사용할 것입니다. 할머니의 집에 가서 닌텐도 DS 케이스를 케이스 안에 게임 카드와 함께 포장 한 다음 케이스를 배낭 안에 넣고 배낭에 주머니를 넣습니다. 그런 다음 가방 주머니에 더 많은 게임 카드를 넣고 모든 것을 큰 가방에 넣습니다. 할머니의 집에 도착하면 모든 외부 케이스가 열려있는 한 DS에서 모든 게임을 즐길 수 있습니다. 또는 그 효과에 무언가.
slartibartfast

376

TLDR

클로저는 함수와 외부 어휘 (즉, 작성된대로) 환경 사이의 링크입니다. 따라서 해당 환경 내에 정의 된 식별자 (변수, 매개 변수, 함수 선언 등)는 언제 또는 어디서든 함수 내에서 볼 수 있습니다. 함수가 호출되는 곳.

세부

ECMAScript 사양의 용어에서 클로저는 함수가 정의 된 어휘 환경[[Environment]] 을 가리키는 모든 함수 객체 의 참조에 의해 구현된다고 말할 수 있습니다 .

내부 [[Call]]메소드 를 통해 함수가 호출 [[Environment]]되면 함수 오브젝트의 참조 가 새로 작성된 실행 컨텍스트환경 레코드외부 환경 참조 에 복사됩니다. (스택 프레임) .

다음 예제에서 함수 f는 전역 실행 컨텍스트의 어휘 환경을 닫습니다.

function f() {}

다음 예제에서 function h 는 함수의 어휘 환경을 g닫고 전역 실행 컨텍스트의 어휘 환경을 닫습니다.

function g() {
    function h() {}
}

외부 함수가 내부 함수를 리턴하면 외부 함수가 리턴 된 후에 외부 어휘 환경이 지속됩니다. 내부 함수가 결국 호출되면 외부 어휘 환경을 사용할 수 있어야하기 때문입니다.

다음 예제에서 함수 j는 함수의 어휘 환경을 닫습니다 i. 즉 , 함수 가 실행을 완료 한 후에도 변수 x는 함수 내부에서 볼 수 있습니다 .ji

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

클로저에서 외부 어휘 환경 자체 의 변수는 사본이 아닌 사용 가능 합니다.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

외부 환경 참조를 통해 실행 컨텍스트간에 연결된 어휘 환경 체인범위 체인을 형성합니다. 지정된 함수에서 볼 수있는 식별자를 정의합니다.

명확성과 정확성을 향상시키기 위해이 답변은 원본과 크게 달라졌습니다.


56
와우, 그런 식으로 문자열 대체를 사용할 수 있다는 것을 결코 알지 못했습니다 console.log. 다른 사람이 관심이 있다면 : developer.mozilla.org/en-US/docs/DOM/…
Flash

7
함수의 매개 변수 목록에있는 변수도 클로저의 일부입니다 (예 :로만 제한되지 않음 var).
Thomas Eding

클로저는 객체와 클래스 등의 소리와 비슷합니다. 왜 많은 사람들이이 두 가지를 비교하지 않는지 잘 모르겠습니다. 초보자도 배우기 쉬울 것입니다!
almaruf

365

이것은 다른 답변 중 일부에 나타나는 폐쇄에 대한 몇 가지 (가능한) 오해를 정리하려는 시도입니다.

  • 내부 함수를 반환 할 때 클로저가 만들어지는 것은 아닙니다. 실제로, 폐쇄 함수 를 작성하기 위해 엔 클로징 함수 가 전혀 리턴 될 필요는 없습니다 . 대신 내부 범위를 외부 범위의 변수에 할당하거나 즉시 또는 나중에 호출 할 수있는 다른 함수에 인수로 전달할 수 있습니다. 따라서, 내부 함수가 호출 될 때마다 또는 내부 함수가 리턴되기 전이나 후에 내부 함수가 해당 폐쇄에 액세스 할 수 있기 때문에, 폐쇄 함수가 닫히는 즉시 폐쇄 함수의 폐쇄가 작성 될 수 있습니다.
  • 클로저는 해당 범위 에있는 이전 변수 의 사본을 참조하지 않습니다 . 변수 자체는 클로저의 일부이므로 해당 변수 중 하나에 액세스 할 때 표시되는 값은 액세스 시점의 최신 값입니다. 이것이 루프 내부에서 생성 된 내부 함수가 까다로울 수있는 이유입니다. 각 함수는 함수가 생성되거나 호출 될 때 변수의 복사본을 잡지 않고 동일한 외부 변수에 액세스 할 수 있기 때문입니다.
  • 클로저의 "변수"에는 함수 내에 선언 된 명명 된 함수포함 됩니다. 또한 함수의 인수도 포함합니다. 클로저는 또한 클로저를 포함하는 클로저 변수에 대한 전역 액세스 권한을 갖습니다.
  • 클로저는 메모리를 사용하지만 JavaScript 자체는 참조되지 않는 자체 원형 구조를 정리하므로 메모리 누수를 일으키지 않습니다. 클로저를 포함하는 Internet Explorer 메모리 누수는 클로저를 참조하는 DOM 속성 값의 연결을 끊지 않으면 생성되므로 순환 구조에 대한 참조가 유지됩니다.

15
James는 폐쇄 함수가 호출 될 때 클로저가 "아마도"생성되었다고 말했다. 구현이 언젠가는 클로저가 절대적으로 필요하다고 결정할 때 클로저 생성이 지연 될 수 있기 때문이다. 엔 클로징 함수에 내부 함수가 정의되어 있지 않으면 클로저가 필요하지 않습니다. 따라서 첫 번째 내부 함수가 생성 될 때까지 기다린 다음 둘러싸는 함수의 호출 컨텍스트에서 클로저를 만들 수 있습니다.
dlaliberte

9
@ Beetroot-Beetroot 외부 함수가 반환 되기 전에 사용되는 다른 함수로 전달되는 내부 함수가 있고 외부 함수에서 동일한 내부 함수도 반환한다고 가정합니다. 두 경우 모두 동일하게 동일한 함수이지만 외부 함수가 반환되기 전에 내부 함수가 호출 스택에 "바운드"된 반면 반환 된 후에 내부 함수는 갑자기 클로저에 바인딩됩니다. 두 경우 모두 동일하게 작동합니다. 시맨틱은 동일하므로 구현 세부 사항에 대해서만 말하는 것이 아닙니까?
dlaliberte

7
@ Beetroot-Beetroot, 의견을 보내 주셔서 감사합니다. 외부 함수의 라이브 컨텍스트와 함수가 반환 될 때 클로저가 될 때 동일한 컨텍스트 사이의 의미 론적 차이는 여전히 보이지 않습니다 (정의를 이해하면). 내부 기능은 신경 쓰지 않습니다. 내부 함수가 컨텍스트 / 클로저에 대한 참조를 유지하므로 가비지 콜렉션은 중요하지 않으며 외부 함수의 호출자는 호출 컨텍스트에 대한 참조를 삭제합니다. 그러나 사람들에게는 혼란스럽고 통화 컨텍스트라고 부르는 것이 좋습니다.
dlaliberte

9
그 기사는 읽기가 어렵지만 실제로 내가 말하는 것을지지한다고 생각합니다. "함수는 함수 객체 [...]를 반환하거나 이러한 함수 객체에 대한 참조를 전역 변수에 직접 할당하여 형성됩니다." 나는 GC가 관련이 없다는 것을 의미하지는 않는다. 오히려 GC 때문에, 그리고 내부 함수가 외부 함수의 호출 컨텍스트 (또는 기사에서 말하는 [[scope]])에 연결되어 있기 때문에, 내부 함수와의 바인딩 때문에 외부 함수 호출이 반환되는지 여부는 중요하지 않습니다. 기능은 중요한 것입니다.
dlaliberte

3
좋은 답변입니다! 한 가지 추가해야 할 것은 모든 함수가 정의 된 실행 범위의 전체 내용에서 모든 함수가 닫히는 것입니다. 부모 범위의 변수 중 일부를 참조하는지 또는 아무 것도 참조하지 않든 상관 없습니다. 부모 범위의 어휘 환경에 대한 참조는 무조건 [[Scope]]로 저장됩니다. 이는 ECMA 사양의 함수 생성 섹션에서 확인할 수 있습니다.
Asad Saeeduddin

236

나는 폐쇄를 설명하는 블로그 게시물을 잠시 썼다. 다음은 내가 원하는지 에 관해 폐쇄에 대해 말한 것입니다.

클로저는 함수가 영구적 인 개인 변수 , 즉 한 함수 만 알고있는 변수를 , 이전에 실행 된 시간으로부터 정보를 추적 할 수있는 위치입니다.

그런 의미에서 함수는 개인 속성을 가진 객체처럼 작동하도록합니다.

전체 게시물 :

이 폐쇄 물건은 무엇입니까?


이 예에서 클로저의 주요 이점을 강조 할 수 있습니까? emailError (sendToAddress, errorString) 함수가 있다고 말한 devError = emailError("devinrhode2@googmail.com", errorString)다음 공유 emailError 함수의 사용자 정의 버전을 말하고 가질 수 있습니까?
Devin G Rhode

이 설명과 (폐쇄 물) 링크와 관련된 완벽한 예는 클로저를 이해하는 가장 좋은 방법이며 맨 위에 있어야합니다!
HopeKing

215

폐쇄는 간단하다 :

다음 간단한 예제는 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가 "스택 프레임"을 사용하지 않고 대신 "힙 프레임"을 사용한다는 것을 상상하는 것입니다. "힙 프레임"은 함수 호출이 반환 된 후에도 지속될 수 있습니다.

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

그것은 폐쇄에 거의 모든 것입니다.



* 예를 들어, 예제 6을 제외하고 는 다른 답변에 제공된 "인류에 대한 클로저"기사의 모든 요점을 다룹니다. 예제 6은 변수가 선언되기 전에 사용할 수 있음을 보여줍니다. 또한 허용 된 답변의 모든 요점을 다룹니다. 함수가 인수를 로컬 변수 (명명 된 함수 인수)에 복사하는 점 (2), 숫자를 복사하면 새로운 숫자가 생성되지만 객체 참조는 복사한다는 점을 제외하고 동일한 객체에 대한 다른 참조를 제공합니다. 이것도 알고 있지만 클로즈와는 전혀 관련이 없습니다. 또한의 예와 매우 유사하다 이 답변 하지만 약간 짧고 덜 추상적입니다. 그것은 요점을 다루지 않습니다이 답변 또는 이 주석은 JavaScript로 인해 현재 플러그를 연결하기가 어렵습니다.내부 함수에 대한 루프 변수의 값 : "플러그인"단계는 내부 함수를 묶고 각 루프 반복에서 호출되는 도우미 함수로만 수행 할 수 있습니다. 엄밀히 말하면, 내부 함수는 무언가를 연결하지 않고 도우미 함수의 변수 복사본에 액세스합니다. 클로저를 만들 때 매우 유용하지만 클로저가 작동하는 방식이나 작동 방식의 일부는 아닙니다. ML과 같은 기능적 언어에서 클로저가 다르게 작동하는 클로저로 인해 변수가 저장 공간이 아닌 값에 바인딩되어 클로저를 이해하는 사람들에게 일정한 방식으로 (즉, "플러그인"방식으로) 변수가 항상 저장 공간에 바인딩되고 값에 바인딩되지 않는 JavaScript의 경우에는 정확하지 않습니다.

** 이 답변이 명확하게 지적하기 때문에 여러 외부 함수가 중첩되거나 전역 컨텍스트 인 경우 외부 함수 .


다음과 같이 호출하면 어떻게됩니까? second_calculator = first_calculator (); second_calculator 대신 make_calculator (); ? 동일해야합니까?
Ronen Festinger

4
@Ronen : first_calculator(함수가 아닌) 객체 이므로 second_calculator = first_calculator;함수 호출이 아닌 대입이므로 괄호를 사용해서는 안됩니다 . 귀하의 질문에 대답하기 위해 make_calculator를 한 번만 호출하면 하나의 계산기 만 작성되며 first_calculator 및 second_calculator 변수는 모두 동일한 계산기를 참조하므로 답변은 3, 403, 4433, 44330입니다.
Matt

204

6 살짜리 아이에게 설명하는 방법 :

어른이 집을 소유 할 수있는 방법을 알고 집이라고 부릅니다. 엄마가 아이를 가질 때 아이는 실제로 아무것도 소유하지 않습니다. 그러나 부모는 집을 소유하고 있기 때문에 누군가 "자녀의 집은 어디입니까?"라고 물을 때마다 "그 집은!"라고 대답하고 부모의 집을 가리킬 수 있습니다. "클로저 (Closure)"는 아이가 집을 소유하고 있다고해도 항상 (해외에도) 집이 있다고 말할 수있는 능력입니다.


200

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 # 질문


11
outerFunction이 리턴 된 후 클로저 사용에 대한 주석 부분을 다루지 않더라도 클로저의 예로 코드가 "올바르다". 따라서 좋은 예가 아닙니다. innerFunction을 반환하지 않는 클로저를 사용할 수있는 다른 방법이 많이 있습니다. 예를 들어 innerFunction은 다른 함수로 전달되어 즉시 호출되거나 나중에 저장되어 나중에 호출 될 수 있으며, 모든 경우에 호출 될 때 작성된 outerFunction 컨텍스트에 액세스 할 수 있습니다.
dlaliberte

6
@syockit 아니오, 이끼가 잘못되었습니다. 함수가 정의 된 범위를 이탈했는지 여부에 관계없이 클로저가 작성 되며, 부모의 어휘 환경에 대한 무조건 작성된 참조는 외부 또는 내부에서 호출되는지 여부에 관계없이 상위 범위의 모든 변수를 모든 함수에 사용할 수있게합니다. 그것들이 만들어진 범위.
Asad Saeeduddin

176

GOOD / BAD 비교를 통해 더 잘 배우는 경향이 있습니다. 나는 누군가가 겪을 가능성이있는 작동 코드와 작동하지 않는 코드를보고 싶다. 나는 비교를하고 내가 생각해 낼 수있는 가장 간단한 설명의 차이점을 정리하려고 하는 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


1
감사합니다. 더 명확하게하기 위해 반복 할 때마다 "bad"배열이 "bad"루프에서 어떻게 생성되는지 상상할 수 있습니다. 1 차 반복 : [function () {return 'n ='+ 0;}] 2 차 반복 : [( function () {return 'n ='+ 1;}), (function () {return 'n ='+ 1;})] 세 번째 반복 : [(function () {return 'n ='+ 2;}) , (함수 () {return 'n ='+ 2;}), (함수 () {return 'n ='+ 2;})] 등. 따라서 인덱스 값이 변경 될 때마다 모든 함수에 반영됩니다. 이미 배열에 추가되었습니다.
Alex Alexeev

3
를 사용 하여 차이 letvar수정합니다.
Rupam Datta

여기 "폐쇄 완료"가 "폐쇄 내부 폐쇄"의 예가 아닙니까?
TechnicalSmile

내 말은, 모든 함수는 기술적으로 폐쇄이지만 중요한 부분은 함수가 새로운 변수를 정의한다는 것입니다. get 함수 n는 새로운 클로저에서 생성 된 참조 만 반환합니다 . 배열에 저장하고 나중에 호출 할 수 있도록 함수를 반환합니다.
Chev

첫 번째 반복에서 배열에 결과를 저장하려면 다음과 같이 인라인 할 수 arr[index] = (function (n) { return 'n = ' + n; })(index);있습니다. 그러나 내 예제의 요점을 잃는 호출하는 함수 대신 결과 문자열을 배열에 저장합니다.
Chev

164

클로저 위키 백과 :

컴퓨터 과학에서 클로저는 해당 함수의 로컬이 아닌 이름 (자유 변수)에 대한 참조 환경과 함께 사용되는 함수입니다.

기술적으로 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.

ems

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


2
이것은 JavaScript 클로저에 대한 가장 좋은 설명입니다. 선택한 답변이어야합니다. 나머지는 충분히 재미 있지만 실제로는 실제 JavaScript 코더에 실용적으로 유용합니다.
geoidesic

136

대화식 JavaScript 자습서를 작성하여 클로저 작동 방식을 설명했습니다. 폐쇄 란 무엇입니까?

예제 중 하나는 다음과 같습니다.

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

128

아이들은 부모가 사라진 후에도 부모와 공유 한 비밀을 항상 기억합니다. 이것이 함수에 대한 클로저입니다.

JavaScript 함수의 비밀은 개인 변수입니다

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

호출 할 때마다 로컬 변수 "name"이 생성되고 이름은 "Mary"입니다. 그리고 함수가 종료 될 때마다 변수가 손실되고 이름이 잊혀집니다.

짐작할 수 있듯이, 함수가 호출 될 때마다 변수가 다시 작성되므로 아무도 알 수 없으므로 변수가 저장되는 비밀 장소가 있어야합니다. 비밀 회의소 또는 스택 또는 로컬 범위 라고 할 수 있지만 실제로는 중요하지 않습니다. 우리는 그들이 어딘가에 기억 속에 숨겨져 있다는 것을 알고 있습니다.

그러나 JavaScript에는 다른 함수 내에서 작성된 함수가 부모의 로컬 변수를 알고 수명이 유지되는 한 유지할 수있는 매우 특별한 것이 있습니다.

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 

그리고 지금, 마리아가 더 이상 달리지 않더라도, 그녀의 기억은 상실되지 않으며 그녀의 아이는 그들의 시간 동안 함께 공유 한 그녀의 이름과 다른 비밀들을 항상 기억할 것입니다.

따라서 자녀를 "Alice"라고 부르면 응답합니다.

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

그게 다야.


15
이것은 기술적 용어에 대한 중요한 사전 지식을 가정하지 않기 때문에 나에게 가장 적합한 설명입니다. 여기서 가장 투표가 잘된 설명은 클로저를 이해하지 못하는 사람이 '어휘 범위'및 '실행 컨텍스트'와 같은 용어를 완전히 이해하고 있다고 가정합니다. 이러한 개념을 개념적으로 이해할 수는 있지만 내가해야 할대로 세부 사항에 편안하고, 전문 용어가없는 설명은 클로저가 마침내 나를 클릭하게 만든 것입니다. 감사합니다. 보너스로, 범위가 매우 간결하게 설명되어 있다고 생각합니다.
Emma W

103

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

폐쇄는 다음과 같습니다.

var a = 42;

function b() { return a; }

예. 아마도 하루에 여러 번 사용할 것입니다.


클로저가 특정 문제를 해결하기위한 복잡한 디자인 해킹이라고 믿을 이유가 없습니다. 아니요, 클로저는 함수가 선언 된 위치 (실행되지 않음)의 관점에서 더 높은 범위 에서 오는 변수를 사용하려고합니다 .

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


5
이 답변은 사람들을 혼란스럽게하는 데 도움이되지 않는 것 같습니다. 기존의 프로그램 언어에서의 거친 해당 객체의 방법으로 () B를 만들 수 있습니다 또한 개인 일정이나 속성이 있습니다 a. 놀랍게도 JS 범위 객체 a는 상수가 아닌 속성으로 효과적으로 제공 됩니다. 당신이로, 그것을 수정하는 경우 그리고 당신은 단지 중요 동작을 알 수 있습니다return a++;
존 쿰즈

1
Jon이 정확히 말한 것. 마지막으로 클로저를 정리하기 전에 실용적인 예제를 찾는 데 어려움을 겪었습니다. 예, floribon은 클로저를 만들었지 만, 교육을받지 못한 사람에게는 이것이 전혀 아무것도 가르쳐주지 않았을 것입니다.
Chev

3
클로저가 무엇인지 정의하는 것이 아니라 클로저를 사용하는 예제 일뿐입니다. 그리고 그것은 범위가 끝날 때 발생하는 뉘앙스를 다루지 않습니다. 나는 모든 범위가 여전히 주위에있을 때, 특히 전역 변수의 경우 어휘 범위에 대해 질문하지 않았다고 생각합니다.
Gerard ONeill

91

dlaliberte의 첫 번째 포인트 예 :

내부 함수를 반환 할 때 클로저가 만들어지는 것은 아닙니다. 실제로, 둘러싸는 함수는 전혀 반환 할 필요가 없습니다. 대신 내부 함수를 외부 범위의 변수에 할당하거나 즉시 사용할 수있는 다른 함수에 인수로 전달할 수 있습니다. 따라서, 내부 함수가 호출 되 자마자 내부 함수에 액세스 할 수 있으므로 둘러싸 기 함수가 닫 혔을 때 둘러싸는 함수의 클로저가 이미 존재할 수 있습니다.

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

가능한 모호성에 대한 작은 설명. "실제로, 둘러싸는 함수는 전혀 반환 할 필요가 없습니다." 나는 "가치가 없다"는 것이 아니라 "여전히 활동적"이라는 의미였다. 따라서이 예제에서는 이러한 측면을 보여주지 않지만 내부 함수가 외부 범위로 전달 될 수있는 다른 방법을 보여줍니다. 내가 만들려고했던 주요 요점 은 폐쇄 함수가 생성 되는 시점 에 관한 것입니다 (폐쇄 함수의 경우). 일부 사람들은 폐쇄 함수가 반환 될 때 발생한다고 생각하기 때문에. 함수가 호출 될 때 클로저가 생성되었음을 나타내는 다른 예가 필요합니다 .
dlaliberte

88

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


35
설명의 절반에 불과합니다. 클로저에 대해 알아야 할 중요한 점은 외부 함수가 종료 된 후에 내부 함수가 여전히 참조되는 경우 외부 함수의 이전 값을 여전히 내부 함수에서 사용할 수 있다는 것입니다.
pcorcoran

22
실제로 내부 함수에 사용 가능한 것은 외부 함수 의 이전 이 아니라 일부 변수가 변경 될 수있는 경우 새 값을 가질 수 있는 이전 변수 입니다.
dlaliberte

86

나는 이미 많은 솔루션이 있다는 것을 알고 있지만이 작고 간단한 스크립트가 개념을 설명하는 데 유용 할 수 있다고 생각합니다.

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

82

잠을 자고 Dan을 초대합니다. Dan에게 하나의 XBox 컨트롤러를 가져 오라고 지시합니다.

댄은 폴을 초대합니다. 댄은 폴에게 컨트롤러 하나를 가져 오라고 요청합니다. 파티에 몇 명의 컨트롤러를 가져 왔습니까?

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.");

80

Closures 의 저자는 클로저 를 꽤 잘 설명했으며, 클로저가 필요한 이유를 설명하고 클로저를 이해하는 데 필요한 LexicalEnvironment도 설명했습니다.
요약은 다음과 같습니다.

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

여기에 이미지 설명을 입력하십시오

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

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

  1. 먼저, 함수 f가 작성 될 때 빈 공간에 작성되지 않습니다. 현재 LexicalEnvironment 오브젝트가 있습니다. 위의 경우에는 창입니다 (a는 함수 작성시 정의되지 않음).

여기에 이미지 설명을 입력하십시오

함수를 만들면 현재 LexicalEnvironment를 참조하는 [[Scope]]라는 숨겨진 속성이 나타납니다.

여기에 이미지 설명을 입력하십시오

변수를 읽지 만 어디에서나 찾을 수 없으면 오류가 발생합니다.

중첩 함수

함수는 다른 함수 안에 중첩되어 범위 체인이라고도하는 LexicalEnvironments 체인을 형성 할 수 있습니다.

여기에 이미지 설명을 입력하십시오

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

폐쇄

외부 함수가 완료된 후에 중첩 함수가 계속 작동 할 수 있습니다.

여기에 이미지 설명을 입력하십시오

Lexical 환경 마크 업 :

여기에 이미지 설명을 입력하십시오

보시다시피 this.say, 사용자 개체의 속성이므로 User가 완료된 후에도 계속 유지됩니다.

그리고 당신이 기억 될 때 this.say, 그것이 만들어 질 때 , (모든 함수로서) this.say.[[Scope]]현재 LexicalEnvironment에 대한 내부 참조 를 얻습니다. 따라서 현재 사용자 실행의 LexicalEnvironment는 메모리에 남아 있습니다. User의 모든 변수는 속성이므로 일반적으로 정크가 아닌 신중하게 유지됩니다.

요점은 내부 함수가 나중에 외부 변수에 액세스하려는 경우 그렇게 할 수 있는지 확인하는 것입니다.

요약:

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

이를 폐쇄라고합니다.


78

JavaScript 함수는 다음에 액세스 할 수 있습니다.

  1. 인수
  2. 지역 (즉, 지역 변수 및 지역 기능)
  3. 환경 :
    • DOM을 포함한 글로벌
    • 외부 기능의 모든 것

함수가 환경에 액세스하면 해당 함수가 닫힙니다.

외부 기능은 필요하지 않지만 여기서는 설명하지 않는 이점을 제공합니다. 클로저는 해당 환경의 데이터에 액세스하여 해당 데이터를 유지합니다. 외부 / 내부 함수의 하위 사례에서 외부 함수는 로컬 데이터를 생성하고 결국 종료 할 수 있지만 외부 함수가 종료 된 후 내부 함수가 남아 있으면 내부 함수는 외부 함수의 로컬 데이터를 유지합니다 살아 있는.

글로벌 환경을 사용하는 클로저의 예 :

Stack Overflow Vote-Up 및 Vote-Down 버튼 이벤트가 클로저, voteUp_click 및 voteDown_click으로 구현되어 외부 변수 isVotedUp 및 isVotedDown에 액세스 할 수 있으며,이 변수는 전역 적으로 정의됩니다. (간단하게하기 위해 응답 투표 단추 배열이 아니라 StackOverflow의 질문 투표 단추를 참조하고 있습니다.)

사용자가 VoteUp 버튼을 클릭하면 voteUp_click 함수는 isVotedDown == true인지 여부를 확인하여 투표를 할 것인지 아니면 단순히 투표를 취소 할 것인지를 결정합니다. voteUp_click 함수는 환경에 액세스하고 있기 때문에 클로저입니다.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

이 네 가지 기능은 모두 환경에 액세스 할 때 클로저입니다.


59

6 살짜리의 아버지로서 현재 어린 아이들을 가르치고 있으며 (정식 교육을받지 않고 코딩을하는 상대적인 초보자이므로 수정이 필요합니다), 나는 수업이 실습을 통해 가장 잘 진행될 것이라고 생각합니다. 6 살짜리가 폐쇄가 무엇인지 이해할 준비가되면, 그들은 스스로 갈 수있을 정도로 오래되었습니다. 코드를 jsfiddle.net에 붙여 넣고 조금 설명하고 독창적 인 노래를 만들기 위해 혼자 남겨 두는 것이 좋습니다. 아래 설명 텍스트는 아마도 10 살짜리 어린이에게 더 적합 할 것입니다.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

명령

DATA : 데이터는 사실의 모음입니다. 숫자, 단어, 측정, 관찰 또는 사물의 설명 일 수도 있습니다. 만지거나 냄새를 맡거나 맛볼 수 없습니다. 당신은 그것을 적고 말하고들을 수 있습니다. 당신은 그것을 사용하여 만들 수 있습니다컴퓨터를 사용하여 터치 냄새와 맛 . 코드를 사용하여 컴퓨터에서 유용하게 사용할 수 있습니다.

코드 : 위의 모든 글을 code 라고 합니다 . JavaScript로 작성되었습니다.

자바 : JavaScript는 언어입니다. 영어와 같이 프랑스어 나 중국어는 언어입니다. 컴퓨터 및 기타 전자 프로세서에서 이해하는 언어가 많이 있습니다. 컴퓨터가 JavaScript를 이해하려면 통역사가 필요합니다. 러시아어 만 할 줄 아는 선생님이 학교에서 수업을 가르 치러 온다고 상상해보십시오. 교사가 "все садятся"라고 말하면 수업이 이해되지 않습니다. 그러나 운 좋게도 모든 사람에게 "모든 사람이 앉는다"는 말을하는 러시아 학생이 있습니다. 수업은 컴퓨터와 같고 러시아 학생은 통역사입니다. JavaScript의 경우 가장 일반적인 통역을 브라우저라고합니다.

브라우저 : 컴퓨터, 태블릿 또는 휴대폰에서 인터넷에 연결하여 웹 사이트를 방문 할 때 브라우저를 사용합니다. Internet Explorer, Chrome, Firefox 및 Safari가 있습니다. 브라우저는 JavaScript를 이해하고 컴퓨터에 필요한 것을 알려줍니다. JavaScript 명령어를 함수라고합니다.

기능 : JavaScript의 함수는 팩토리와 같습니다. 내부에 기계가 하나만있는 작은 공장 일 수 있습니다. 또는 여러 다른 작은 공장이있을 수 있으며 각 공장마다 다른 작업을 수행합니다. 실제 의류 공장에서는 옷감과 실 보빈이 들어가고 티셔츠와 청바지가 나올 수 있습니다. 우리의 JavaScript 팩토리는 데이터 만 처리하며, 꿰매거나 구멍을 뚫거나 금속을 녹일 수 없습니다. JavaScript 팩토리에서 데이터가 들어오고 데이터가 나옵니다.

이 모든 데이터는 약간 지루한 것처럼 들리지만 정말 멋지다. 우리는 저녁 식사를 위해 무엇을 만들어야하는지 로봇에게 알려주는 기능을 가지고있을 것입니다. 내가 당신과 당신의 친구를 내 집에 초대한다고 가정 해 봅시다. 당신은 닭 다리를 가장 좋아하고, 소시지를 좋아하고, 친구는 항상 당신이 원하는 것을 원하고 내 친구는 고기를 먹지 않습니다.

쇼핑하러 갈 시간이 없어서, 그 기능은 냉장고에있는 것들을 알아야 의사 결정을해야합니다. 각 재료마다 조리 시간이 다르기 때문에 로봇이 모든 것을 동시에 가열해야합니다. 우리는 기능에 우리가 좋아하는 것에 대한 데이터를 제공해야하며, 기능은 냉장고에 '말할'수 있으며, 기능은 로봇을 제어 할 수 있습니다.

함수에는 일반적으로 이름, 괄호 및 중괄호가 있습니다. 이처럼 :

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

참고 /*...*///정지 코드는 브라우저에 의해 읽을.

이름 : 원하는 단어에 관계없이 함수를 호출 할 수 있습니다. 예를 들어 "cookMeal"은 두 단어를 함께 연결하고 두 번째 단어를 처음에 대문자로 표시하는 데 일반적입니다. 그러나 이것은 필요하지 않습니다. 그 안에 공백을 넣을 수 없으며 그 자체로는 숫자가 될 수 없습니다.

부모 : "Parentheses" ()는 JavaScript 기능 팩토리 도어의 레터 박스 또는 거리에있는 정보 박스로 팩토리에 정보 패킷을 보내기위한 것입니다. 때때로 우편함이 예를 들어 표시 될 수 있으며 cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime),이 경우 우편함에 어떤 데이터를 제공해야하는지 알고 있습니다.

브레이스 : 이와 같이 보이는 "브레이스" {}는 우리 공장의 착색 된 창입니다. 공장 내부에서는 볼 수 있지만 외부에서는 볼 수 없습니다.

위의 긴 코드 예

우리의 코드는 function 이라는 단어로 시작 하므로 그것이 하나라는 것을 알고 있습니다! 그런 다음 함수의 이름은 노래입니다 -그것은 함수에 관한 나의 설명입니다. 그런 다음 괄호 () . 함수에는 항상 괄호가 있습니다. 때때로 그들은 비어 있고 때로는 무언가를 가지고 있습니다.이 단어는 다음과 같습니다 (person). 이 후에는 이와 같은 괄호가 {있습니다. 이것은 sing () 함수의 시작을 나타냅니다 . sing () 의 끝을 다음 과 같이 표시하는 파트너가 있습니다.}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

따라서이 기능은 노래와 관련이있을 수 있으며 사람에 대한 데이터가 필요할 수 있습니다. 내부에는 해당 데이터로 무언가를 수행하기위한 지침이 있습니다.

이제 sing () 함수 다음에 코드 끝 근처에 줄이 있습니다.

var person="an old lady";

변수 : 문자 var 는 "변수"를 나타냅니다. 변수는 봉투와 같습니다. 이 봉투 외부에는 "사람"이라고 표시되어 있습니다. 내부에는 함수에 필요한 정보가 담긴 종이 한 장이 들어 있습니다. 일부 문자와 공백은 "노인"이라는 문구를 만드는 문자열 조각 (문자열이라고 함)과 같이 결합되었습니다. 우리의 엔벨로프는 숫자 (정수), 명령어 (함수),리스트 ( 배열 ) 와 같은 다른 종류의 것들을 포함 할 수 있습니다 . 이 변수는 모든 괄호 외부에 작성되며 괄호 {}안에있을 때 색조 창을 통해 볼 수 있기 때문에이 변수는 코드의 어느 곳에서나 볼 수 있습니다. 이것을 '전역 변수'라고합니다.

GLOBAL VARIABLE : person 은 전역 변수입니다. 즉, 값을 "노인"에서 "청년" 으로 변경하면 다시 변경하기로 결정할 때까지 해당 사람 이 계속 청년이됩니다. 코드는 젊은이임을 알 수 있습니다. 를 눌러 F12옵션 설정에서 버튼을 누르거나보기는 브라우저의 개발자 콘솔을 열고이 값이 무엇을보고 "사람"을 입력합니다. 입력 person="a young man"을 변경 한 다음 "person"을 다시 입력 하여 변경되었는지 확인하십시오.

이 후 우리는 라인을 가지고

sing(person);

이 라인은 마치 개를 호출하는 것처럼 함수를 호출합니다.

" 노래 와서 사람을 잡아 !"

브라우저가 JavaScript 코드를로드하면이 줄에 도달하면 기능이 시작됩니다. 브라우저를 실행하는 데 필요한 모든 정보가 브라우저에 있는지 확인하기 위해 끝에 줄을 넣었습니다.

함수는 동작을 정의합니다. 주요 기능은 노래에 관한 것입니다. 여기에는 노래의 각 구절에 적용되는 사람에 대한 노래에 적용되는 firstPart 라는 변수가 포함되어 있습니다 . ""+ person + "was swallowed" 콘솔에 firstPart 를 입력 하면 변수가 함수에 잠겨 있기 때문에 답을 얻지 못합니다. 브라우저는 괄호의 색조 창 내부를 볼 수 없습니다.

클로저 : 클로저는 큰 sing () 함수 안에있는 더 작은 함수입니다 . 큰 공장 안에있는 작은 공장들. 그들은 각각 자신의 중괄호를 가지고 있으며, 이는 내부 변수가 외부에서 볼 수 없음을 의미합니다. 그렇기 때문에 변수 이름 ( 생물체결과 )을 클로저에서 반복 할 수 있지만 다른 값으로 반복 할 수 있습니다. 콘솔 창에 이러한 변수 이름을 입력하면 색조가 지정된 두 레이어로 숨겨져 있기 때문에 값을 얻지 못합니다.

클로저 는 틴트 창에서 확인할 수 있기 때문에 firstPart 라는 sing () 함수의 변수 가 무엇인지 알고 있습니다.

폐쇄 후 라인

fly();
spider();
bird();
cat();

sing () 함수는 주어진 순서대로 각 함수를 호출합니다. 그러면 sing () 함수의 작업이 완료됩니다.


56

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 이 있습니다.


49

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

그것을 설명하는 또 다른 간단한 방법은 범위 측면입니다.

더 큰 범위 내에서 더 작은 범위를 만들 때마다 더 작은 범위는 항상 더 큰 범위에있는 것을 볼 수 있습니다.


49

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

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

어휘 중첩 함수가 참조하는 이러한 모든 외부 변수는 실제로 어휘 포함 함수 체인에있는 로컬 변수이므로 (글로벌 변수는 일부 루트 함수의 로컬 변수라고 가정 할 수 있음) 함수를 실행할 때마다 새 인스턴스가 작성됩니다. 지역 변수에 따라, 함수를 반환하거나 콜백으로 등록하는 등 함수를 전달할 때마다 함수가 실행될 때마다 새로운 클로저를 생성합니다. 문맥).

또한 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();

47

아마도 가장 조잡한 6 살짜리 아이를 제외하고는 조금 있지만 JavaScript에서 클로저라는 개념을 만드는 데 도움이되는 몇 가지 예가 저를 위해 클릭합니다.

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

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

outerFunction();

경고 : monkey

위의 예제에서 outerFunction이 호출되어 innerFunction이 호출됩니다. outerVar의 값을 올바르게 경고하여 innerFunction에서 outerVar를 사용할 수있는 방법에 유의하십시오.

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

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

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

경고 : monkey

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());

경고 : 원숭이 경고 : 원숭이

그러나 이것은 어떻습니까? outerFunction이 null로 설정되었으므로 referenceToInnerFunction에서 어떻게 outerVar의 값을 알 수 있습니까?

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

//////////

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

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

    innerFunction();
}

outerFunction();

경고 : 고릴라

둘째, 클로저가 만들어 질 때 모든 클로저 함수의 변수와 함수에 대한 참조를 유지합니다. 선택하고 선택할 수 없습니다. 그러나 클로저는 메모리를 많이 사용할 수 있으므로 드물게 또는 적어도주의해서 사용해야합니다. 포함 함수가 실행 된 후에도 많은 변수를 메모리에 보관할 수 있습니다.


45

모질라 클로저 페이지 를 가리키고 자합니다 . 가장 간결하고 간단한 설명입니다내가 찾은 클로저 기본 사항과 실제 사용법에 입니다. JavaScript를 배우는 모든 사람에게 적극 권장됩니다.

그렇습니다. 6 살짜리 아이에게도 권합니다. 6 살짜리 아이가 클로저에 대해 배우 면 기사에 제공된 간결하고 간단한 설명 을 이해할 준비가 된 것 입니다.


동의합니다 : 언급 된 Mozilla 페이지는 특히 간단하고 간결합니다. 놀랍게도 귀하의 게시물은 다른 사람들만큼 널리 인정되지 않았습니다.
Brice Coustillas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.