도움말
인텔리퀀트의 사용방법과 메뉴얼입니다.

12. 클로저

클로저의 사용

클로저는 JavaScript의 고급 기능이라고 생각하기 쉽지만, 클로저를 이해하는 것은 JavaScript를 공부하는데 빠뜨릴 수 없습니다.

다음과 같은 함수를 보면.

Function  init () {
  var  name = "Mozilla" ;

  Function  displayName () {
      alert (name);
  }
  displayName ();
}

init () 함수는 로컬 변수 name 을 생성 한 다음 함수 displayName () 를 정의하고 있습니다. displayName () 은 init () 에 정의 된 내부 함수에서 함수 본체 내부에서만 사용할 수 없습니다. displayName () 자체는 로컬 변수를 가지고 있지 않지만, 외부 함수에서 선언 된 name 변수를 재사용하고 있습니다.

이 코드는 제대로 작동합니다. 실제로 움직여 무슨 일이 일어나는가 확인 해보세요. 이것은 어휘 범위 를 보여주는 예입니다. JavaScript에서 변수의 범위는 소스 코드의 위치에 의해 결정되어 중첩 된 함수는 바깥 쪽 범위에서 선언 된 변수에 액세스 할 수 있습니다.

이번에는 다음과 같은 시나리오를 생각해 봅시다.

Function  makeFunc () {
  var  name = "Mozilla" ;

  Function  displayName () {
      alert (name);
  }
  return  displayName;
}

var  myFunc = makeFunc ();

myFunc ();

이 코드를 실행하면 이전 init () 의 예와 동일하게 문자열 "Mozilla"가 JavaScript 경고 상자에 표시됩니다. 앞의 예와는 다른 흥미로운 점은 내부 함수 displayName () 그것이 실행되기 전에 외부 함수에서 반환한다는 것입니다.

이 코드가 작동한다는 것은 직관적으로 이해 할 수 없을지도 모릅니다. 일반적으로 함수 내부의 지역 변수는 함수가 실행되는 동안에 만 존재합니다. 일단 makeFunc () 의 실행이 완료되면 name 변수는 이제 필요 없게된다고 생각하는 것이 근육 다니고 있습니다. 단지이 코드가 예상대로 움직인다는 것은, 이것은 분명히 사실과 다릅니다.

이 수수께끼에 대한 해답은 myFunc 가 폐쇄 된 것입니다. 클로저는 함수와 함수가 만들어진 환경이라는 두 가지가 일체가 된 특수 개체입니다. 이 환경은 클로저가 만들어진 시점에서 범위 내부에 있던 모든 변수에 의해 구성되어 있습니다. 이 경우 myFunc 그것이 만들어진 때 존재했다 displayName 함수와 문자열 "Mozilla"를 가져온 클로저입니다.

여기에 좀 더 재미있는 경우가 있습니다. makeAdder 함수입니다.

Function  makeAdder (x) {
  return  Function (y) {
      return  x + y;
  }
}

var  add5 = makeAdder (5);
var  add10 = makeAdder (10);

alert (add5 (2)); / / 7로 표시되는
alert (add10 (2)); / / 12로 표시되는

이 예에서는 하나의 인수 x 를 가지고, 새로운 함수를 반환 makeAdder (x) 함수를 정의하고 있습니다. 반환 된 함수는 하나의 인수 y 를 가지고, x 와 y 의 합을 반환합니다.

즉, makeAdder 함수 팩토리입니다. 이것은 주어진 인수에 특정 값을 추가 함수를 만듭니다. 위의 예에서는 함수 팩토리를 사용해 2 개의 새로운 함수를 작성하고 있습니다. 하나는 인수 5를 추가하며, 다른 하나는 10을 추가합니다.

add5 하면 add10 다 클로저입니다. 양자는 동일한 함수 본체의 정의를 공유하고 있지만, 보유하고있는 환경은 다릅니다. add5 환경에서 x 는 5에서 add10 환경에서 x 는 10입니다.

실용적인 폐쇄

이론은 이쯤 해 둔다으로 클로저는 실제로 유용한 것입니까? 클로저의 실질적인 의미를 생각해 봅시다. 클로저를 사용하면 데이터 (환경)을 그것을 조작하는 함수와 연계 할 수 있습니다. 이것은 개체를 사용하여 데이터 (객체의 속성)을 1 붙지 이상 방법과 연결 수있는 객체 지향 프로그래밍과 분명히 유사합니다.

따라서 메서드를 하나만 가진 개체를 사용하고 싶은 상황이라면, 어떤 때라도 클로저를 쓸 수 있습니다.

JavaScript에서는 이러한 상황은 많습니다. 우리가 쓰는 JavaScript 코드는 대부분 이벤트 기반입니다. 즉, 동작을 정의하고 그것을 click과 keypress 등 사용자에 의한 이벤트에 연결합니다. 우리의 대부분의 코드는 콜백, 즉 이벤트에 반응 해 실행되는 단일 함수로 설치됩니다.

실례를 들어 보자. 한 페이지에 해당 페이지의 텍스트 크기를 조절하는 버튼을 추가하려고하고 있다고합니다. 하나의 방법으로 먼저 body 요소의 font-size를 픽셀 단위로 지정하여 해당 페이지의 (제목 등) 다른 요소의 크기를 상대 단위 em으로 설정합니다.

body {
font-family : Helvetica , Aria, sans-serif ;
font-size : 12px ;
}

h 1  { font-size : 1.5em ;}
h 2  { font-size : 1.2em ;}

앞으로 만들 대화식 텍스트 크기 조정 버튼은 body 요소의 font-size 속성을 변경하고 그 변경은 상대적 단위가 페이지의 다른 요소에도 적용됩니다.

JavaScript 코드 :

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

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

size12 , size14 , size16 각각 body 텍스트 크기를 12,14,16 px로 변경하는 함수입니다. 이들은 다음과 같이 버튼 (이 경우 링크)에 설치됩니다.

Function  setupButtons () {
  document.getElementById ( 'size-12' ). onclick = makeSizer (12);
  document.getElementById ( 'size-14' ). onclick = makeSizer (14);
  document.getElementById ( 'size-16' ). onclick = makeSizer (16);
}
 12 
 14 
 16 

JSFIDDLE에서 확인

클로저 개인 메소드를 만든다

Java 등의 언어는 개인 메소드를 선언 할 수 있습니다. 이것은 같은 클래스에있는 다른 방법에서만 호출 방법입니다.

JavaScript에는 이러한 기능이 내장되어 있지 않지만, 클로저를 사용 전용 메서드를 모방 할 수 있습니다. 전용 메서드는 코드에 대한 액세스를 제한 할 수 있도록 할뿐만 아니라, 코드의 공용 인터페이스가 불필요한 메소드로 가득 차지 않도록 글로벌 네임 스페이스를 관리하는 데 매우 유용하다.

클로저를 사용하여 개인 함수와 변수에 액세스 할 수있는 공용 함수를 정의하려면 이렇게합니다.

var  Counter = ( function () {
  var  privateCounter = 0;

  Function  changeBy (val) {
      privateCounter + = val;
  }

  return  {
      increment : function () {
          changeBy (1);
      }
      decrement : function () {
          changeBy (-1);
      }
      value : function () {
          return  privateCounter;
      }
  }   
}) ();

alert (Counter.value ()); / * 0으로 표시되는 * /
Counter.increment ();
Counter.increment ();
alert (Counter.value ()); / * 2로 표시되는 * /
Counter.decrement ();
alert (Counter.value ()); / * 1로 표시되는 * /

여기에서는 다양한 일을하고 있습니다. 앞의 예에서는 클로저가 자신의 환경을 가지고있었습니다 만,이 경우는 환경이 하나 만들어 그 환경은 Counter.increment , Counter.decrement , Counter.value 세 가지 함수에 의해 공유되어 있습니다.

공유 환경은 정의되는 즉시 실행되는 익명 함수의 본문에서 작성되어 있습니다. 이 환경 변수 privateCounter 와 함수 changeBy 두 개의 개인 항목을 포함하고 있습니다. 이들은 모두 익명 함수의 외부에서 직접 액세스 할 수 없습니다. 대신이 무명 래퍼 함수에서 반환 된 3 개의 공용 함수에서 액세스 할 수 있습니다.

이 3 개의 공용 함수 같은 환경을 공유하는 클로저입니다. JavaScript의 사전 카르 스코핑은 이러한 함수는 각각 변수 privateCounter 와 함수 changeBy 에 액세스 할 수 있습니다.

이렇게해서 클로저를 사용하면 보통은 객체 지향 프로그래밍에 다니는 몇 가지 장점, 특히 데이터의 은폐와 캡슐화를 사용할 수 있습니다.

일반적인 실수 : 루프에서 클로저를 만들

JavaScript 1.7 let 키워드가 도입되기 전까지는 루프 내에서 클로저가 작성되었을 때의 문제가 자주 발생했습니다. 다음과 같은 시나리오를 생각해 봅시다.

여기에 도움이 나타납니다

E 메일 : < input type = "text" id = "email" name = "email" >

이름 : < input type = "text" id = "name" name = "name" >

연령 : < input type = "text" id = "age" name = "age" >

Function  showHelp (help) {
  document.getElementById ( 'help' ). innerHTML = help;
}

Function  setupHelp () {
  var  HelpText =
      { 'id' : 'email' , 'help' : '당신의 E 메일 주소' }
      { 'id' : 'name' , 'help' : '당신의 성명' }
      { 'id' : 'age' , 'help' : '당신의 나이 (17 세 이상)' }
  ];

  for  ( var  I = 0; i < helpText.length; i + +) {
      var  Item = helpText [i];
      document.getElementById (item.id). onfocus = function () {
          showHelp (item.help);
      }
  }
}

배열 helpText 는 3 개의 도움말을 정의하고, 각 문서의 입력 필드의 ID와 연결되어 있습니다. 루프가 이러한 정의를 순회하며 각 입력 필드의 onfocus 이벤트를 그것과 관련된 도움말을 표시하는 메서드와 연결되어 있습니다.

이 코드를 실행 해 보면, 예상대로 움직이지 않는 것을 알 수 있습니다. 어떤 필드에 포커스해서 나타나는 것은 나이에 대한 메시지입니다.

이렇게되는 이유는 onfocus에 할당 된 함수가 클로저 때문입니다. 이 클로저는 함수 정의와 setupHelp 함수의 범위에서 포착 된 환경 이루어져 있습니다. 클로저는 3 개 만들어졌지만, 이들은 모두 하나의 같은 환경을 공유하고 있습니다. onfocus 콜백이 실행될 때 루프는 종료하고, 변수 item (3 클로저 모든 공유 된)은 helpText 목록의 마지막 항목을 보여 채로되어 있습니다.

이런 경우의 해결책의 하나로서, 더 클로저를 사용하는 방법이 있습니다. 구체적으로는 이전에 언급 한 함수 팩토리를 사용합니다.

Function  showHelp (help) {
  document.getElementById ( 'help' ). innerHTML = help;
}

Function  makeHelpCallback (help) {
  return  Function () {
      showHelp (help);
  }
}

Function  setupHelp () {
  var  HelpText =
      { 'id' : 'email' , 'help' : '당신의 E 메일 주소' }
      { 'id' : 'name' , 'help' : '당신의 성명' }
      { 'id' : 'age' , 'help' : '당신의 나이 (17 세 이상)' }
  ];

  for  ( var  I = 0; i < helpText.length; i + +) {
      var  Item = helpText [i];
      document.getElementById (item.id). onfocus = makeHelpCallback (item.help);
  }
}

예상 한대로 움직입니다. 모든 콜백이 하나의 환경을 공유하는 것이 아니라, makeHelpCallback 함수가 각각에 새로운 환경을 만들고 있으며, 거기에서 help 가 배열 helpText 해당 문자열을 참조하고 있습니다.

JavaScript 1.7 이상을 사용하고 있다면, 블록 레벨 범위의 변수를 만들 수 let 키워드를 사용하여이 문제를 해결할 수 있습니다.

for  ( var  I = 0; i < helpText.length; i + +) {
  let item = helpText [i];

  document.getElementById (item.id). onfocus = function () {
      showHelp (item.help);
  }
}

let 키워드로 변수 item 블록 레벨 범위에서 생성되므로 for 루프의 반복마다 새로운 참조가 만들어집니다. 즉 각각의 클로저에 대해 별도의 변수가 포착되기 때문에 환경의 공유에 의해 발생하는 문제를 해결합니다.

성능에 대한 배려

있는 작업을 수행 할 때, 폐쇄가 필요하지 않는데 쓸데없이 함수를 다른 함수에서 작성하는 것은 스크립트의 성능에 악영향을 미치므로 그다지 현명한 방법이 없습니다.

예를 들어, 새로운 객체 / 클래스를 만들 때 일반적으로 메서드는 개체의 생성자에서 정의하는 것이 아니라 객체의 프로토 타입으로 연결한다. 생성자에서 정의 해 버리면, 생성자가 호출 될 때마다 (즉 개체를 만들 때마다) 메소드가 중복 지정되어 버리게되기 때문입니다.

다음 실천적하지 궁행을위한 케이스를 생각합니다.

Function  MyObject (name, message) {
  this . name = String (name);

  this . message = String (message);

  this . getName = function () {
      return  this . name;
  }

  this . getMessage = function () {
      return  this . message;
  }
}

위의 코드는 클로저를 사용해서 얻는 것이 아무것도 없기 때문에, 다시 구성해야합니다.

Function  MyObject (name, message) {
  this . name = String (name);
  this . message = String (message);
}

MyObject.prototype = {
  getName : function () {
      return  this . name;
  }
  getMessage : function () {
      return  this . message;
  }
};

혹은

Function  MyObject (name, message) {
  this . name = String (name);
  this . message = String (message);
}

MyObject.prototype.getName = function () {
  return  this . name;
}

MyObject.prototype.getMessage = function () {
  return  this . message;
}

위의 두 예에서는 프로토 타입이 상속 된 모든 개체가 공유되기 때문에 개체가 생성 될 때마다 메소드를 정의하지 않을 수 있습니다. 자세한 내용은 개체 모델에 대한 자세한 을 참조하십시오.

저작권 공지

이 문서의 모든 저작권은 Mozilla.org에 있습니다. 이 문서는 "모질라 기여자"들에 의해 작성 되었습니다. 원문 보기
저희가 한글로 번역한 2차적저작물에 대한 저작권 역시 Mozilla.org에 있습니다.