클로저 - Lee-hyuna/33-js-concepts-kr GitHub Wiki

클로저

JavaScript는 매우 함수 지향적인 언어입니다.
그것은 우리에게 많은 자유를 줍니다.
함수는 동적으로 생성 되거나 다른 변수에 복사되거나, 다른 함수에 인수로 전달되어 나중에 전혀 다른곳에서 호출 될 수 있습니다.

우리는 함수가 외부 변수에 접근 할 수 있다는 것을 알고 있습니다. 이 기능은 자주 사용됩니다.

그러나 외부 변수가 변경되면 무슨 일이 일어납니까? 함수가 가장 최근의 값 또는 함수가 생성 되었을 때 존재했던 값을 얻습니까?

또한 함수가 코드의 다른 위치로 이동하여 호출 될 때에는 어떤 일이 발생합니까?
새로운 장소의 외부 변수에 접근할 수 있습니까?

다른 언어는 여기서 다르게 작동하지만 이 챕터에서 우리는 JavaScript의 동작을 다룰겁니다.

몇 가지 질문

먼저 두 가지 상황을 고려한 후 내부 역할을 하나씩 살펴보면 다음 질문과 더 복잡한 질문에 대답을 할 수 있습니다.

  1. sayHi 함수는 외부 변수 이름을 사용합니다. 함수가 실행 될때 어떤 값을 사용할까요?
let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete";

sayHi(); // what will it show: "John" or "Pete"?

이러한 상황은 브라우저 및 서버 사이드 개발부분에서 공통입니다.
함수는 예를 들어 사용자 작업 또는 네트워크 요청 후 생성 된 것보다 나중에 실행 될 수 있도록 예약 될 수 있습니다.
질문 : 최신 변경 사항이 있습니까?

  1. makeWorker 함수는 다른 함수를 만들어 리턴합니다. 그 새로운 기능은 다른 곳에서 호출 될 수 있습니다.
    생성 장소, 호출 장소 둘 다 외부 변수에 접근 할 수 있습니까?
function makeWorker() {
  let name = "Pete";

  return function() {
    alert(name);
  };
}

let name = "John";

// create a function
let work = makeWorker();

// call it
work(); // what will it show? "Pete" (name where created) or "John" (name where called)?

Lexical Environment(어휘 환경)

무슨 일이 일어나고 있는지 이해하기 위해서 먼저 변수가 실제로 무엇인지 알아 봅시다. JavaScript에서는 모든 실행중인 함수, 코드블록 및 스크립트 전체가 Lexical Environment(어휘 환경)라고 알려진
내부(숨겨진) 관련 객체를 가지고 있습니다.

Lexical Environment(어휘 환경) 객체는 다음 두 부분으로 구성됩니다.

  1. Environment Record(환경 기록) - 모든 로컬 변수를 속성으로 저장하는 객체
  2. 외부 lexical environment(어휘 환경)에 대한 참조, 외부 코드와 관련된 것

변수는 특별한 내부 객체인 Environment Record(환경 기록)의 속성 일 뿐입니다.

예를 들어, 이 간단한 코드에는 Lexical Environment가 하나만 있습니다.

이것은 전체 스크립트와 관련된 글로벌 Lexical Environment 라고 불립니다. 위 그림에서 사각형은 Environment Record (가변 저장소)를 의미하고 화살표는 외부 참조를 의미합니다.
글로벌 Lexical Environment(어휘 환경)에는 외부 참조가 없어 null을 가리킵니다.

변수가 정의되고 할달 될 때 변경되는 방식입니다.

오른쪽의 사각형은 실행 중 글로벌 Lexical Environment(어휘 환경)이 어떻게 변하는 지 보여주고 있습니다.

  1. 스크립트가 시작될 때, Lexical Environment(어휘 환경)은 비어 있습니다.
  2. let phrase 이 나타납니다. 값이 지정되지 않았으므로 undefined가 저장됩니다.
  3. phrase에 값이 할당 됩니다.
  4. phrase 값이 변경됩니다.

지금은 모든것이 단순해 보입니다.
요약

  • 변수는 현재 실행중인 블록 / 함수 / 스크립트 와 관련된 특별한 내부 객체의 속성입니다.
  • 변수 작업은 실제로 해당 객체의 속성을 사용 하는 것입니다.

함수 선언

지금까지는 변수만 관찰 했습니다. 이제 함수 선언을 입력하세요.
let 변수와 달리, 실행이 변수에 도달 했을 때가 아니라 Lexical Environment이 생성되었을 때 완전히 초기화 됩니다.
최상위 기능의 경우 스크립트가 시작되는 순서를 의미합니다.
그렇기 때문에 정의하기 전에 함수 선언을 호출 할 수 있습니다.
아래 코드는 Lexical Environment가 처음부터 비어 있지 않음을 보여줍니다.
그것은 함수 선언이기 때문입니다. 그리고 나중에 let으로 선언 된 phrase를 얻습니다.

내외부 Lexical Environment

이제 함수가 외부 변수에 접근 할 때 어떤 일이 발생하는지 살펴 보겠습니다. 호출 중에 say()는 회부 변수 phrase를 사용합니다.
진행 상황에 대해 자세히 살펴 보겠습니다.
함수가 실행되면 새로운 Lexical Environment가 자동으로 작성되어 호출의 로컬 변수 및 매개 변수를 저장합니다.
예를 들어 say("John")의 경우 다음과 같습니다.

따라서 함수 호출 중에는 내부 환경(함수 호출의 경우) 과 외부 환경(글로벌)의 두가지 Lexical Environment이 있습니다.

  • 내부 Lexical Environment은 현재 실행중인 내용에 해당 합니다.
    함수 인수인 'name'이라는 단일 속성이 있습니다. 우리는 say("John")를 호출했고 name의 값은 "John"입니다.
  • 외부 Lexical Environment는 글로벌 Lexical Environment 입니다.
    변수 phrase와 함수 자체가 있습니다.

내부 Lexical Environment에는 외부 Lexical Environment에 대한 참조가 있습니다. 코드가 변수에 접근하려고 할 때, 내부 Lexical Environment가 먼저 검색된 다음 외부 Lexical Environment,
그 다음 Lexical Environment 등 글로벌 Lexical Environment까지 검색이 됩니다.
만약 변수를 어디에서도 찾을 수 없다면 strict 모드에서 오류가 발생합니다.
(strict 모드 사용없이 user="John" 과 같이 존재하지 않는 변수에 할당하면 새로운 전역 변수 user가 생성됩니다.)
이 예에서 검색이 어떻게 진행이 되는지 봅시다.

  • 내부 경고가 이름에 접근하려고 할 때 Lexical Environment 기능에서 즉시 발견합니다.
  • phrase에 접근하려는 경우 로컬에 phrase가 없으므로 주변 Lexical Environment에 대한 참조를 따라 해당 phrase를 찾습니다.

이제 우리는 챕터 시작부분의 첫 질문에 대한 답을 얻을 수 있습니다.
함수는 현재 외부 변수를 가져 오며 가장 최근 값을 사용합니다.
이전 변수 값은 어디에도 저장되지 않습니다.
함수가 변수를 원하면 자체 Lexical Environment 또는 외부 Lexical Environment에서 현재 값을 가져옵니다.
첫 번째 질문에 대한 답은 Pete 입니다.

let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete"; // (*)

sayHi(); // Pete

위 코드의 실행 흐름

  1. 글로벌 Lexical Environment가 가진 name은 "John" 입니다.
  2. (*) 행에서 전역 변수가 변경 되었습니다. 이제부터 name은 "Pete" 입니다.
  3. sayHi() 함수가 실행되면 외부에서 이름을 가져옵니다.
    이곳에서는 이미 "Pete"입니다. 글로벌 Lexical Environment에서 가져온 것입니다.

중첩 함수

함수가 다른 함수 안에 만들어 질 때 중첩되었다라고 한다. JavaScript로 이 작업을 쉽게 할 수 있습니다.
우리는 다음과 같이 코드를 구성할 수 있습니다.

function sayHiBye(firstName, lastName) {

  // helper nested function to use below
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}

여기서 중첩 함수 getFullName()은 편의를 위해 만들어졌습니다.
외부 변수에 접근할 수 있으므로 전체 이름을 리턴 할 수 있습니다. 중첩 함수는 JavaScript에서 매우 일반적입니다.
더 흥미로운 점은 중첩 함수를 새 객체의 속성 또는 그 자체로 반환할 수 있다는 것입니다.
그런 다음 다른 곳에서 사용 할 수 있습니다.
위치에 관계겂이 여전히 동일한 외부변수에 접근 할 수 있습니다.
예를들어: 중첩 함수는 생성자 함수에 의해 새 객체에 할당 됩니다.

// constructor function returns a new object
function User(name) {

  // the object method is created as a nested function
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // the method "sayHi" code has access to the outer "name"

그리고 여기서는 "카운팅" 함수를 만들고 반환합니다.

function makeCounter() {
  let count = 0;

  return function() {
    return count++; // has access to the outer "count"
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

makeCounter 예제로 넘어 갑시다.
각 호출에서 다음 번호를 리턴하는 :"카원터" 함수를 작성합니다.
단순하지만 코드의 약간 수정 된 변형은 예를 들어 의사 난수 생성기 등으로 실요적으로 사용됩니다.

카운터는 내부적으로 어떻게 작동하는가?

내부 함수가 실행되면 count ++의 변수가 내부에서 검색됩니다. 위의 예에서 순서는 다음과 같습니다.

  1. 중첩 함수의 지역들
  2. 외부 함수의 변수들
  3. 전역 변수에 도달 할 때까지 계속

이 예제에서는 2단계에서 count를 찾을 수 있습니다.
외부 변수가 수정되면 발견 된 위치에서 변경됩니다.
따라서 count ++는 외부 변수를 찾아 그것이 속한 Lexical Environment에서 증가시킵니다. 카운트 = 1 인 것처럼

고려해야 할 두 가지 질문이 있습니다.

  1. makeCounter에 속하지 않은 코드에서 어떻게 카운터 카운트를 재설정 할 수 있습니까? 예 : 위의 예에서 경고 호출 후
  2. makeCounter ()를 여러 번 호출하면 많은 카운터 함수가 반환됩니다. 그들은 독립적이거나 동일한 수를 공유합니까?

계속 읽기 전에 대답 해보세요.

다 했습니까?
좋습니다. 답을 살표봅시다.

1.방법은 없습니다 : count는 지역 함수 변수이므로 외부에서 접근 할 수 없습니다.
2. makeCounter()를 호출 할 때마다 새로운 함수 인 Lexical Environment가 자체 카운트로 작성됩니다.
결과 카운터 기능은 독립적입니다.

데모는 다음과 같습니다.

function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

alert( counter1() ); // 0
alert( counter1() ); // 1

alert( counter2() ); // 0 (independent)

바라건대, 외부 변수가 있는 상황은 분명합니다.
대부분의 상황에서 그러한 이해는 충분합니다.
간결성을 위해 생략 한 사양에는 세부 사항이 거의 없습니다. 다음 섹션에서는 더 자세한 내용을 다룹니다.

세부적인 환경

다음은 makeCounter 예제에서 진행중인 작업을 단계별로 설명하고 내용을 자세히 알고 있는지 확인하십시오.

추가 속성이 여기에 포함됩니다. 우리는 간결성을 위해 이전에 언급하지 않았습니다.

  1. script가 시작되면 글로벌 Lexical Environment만 존재한다.

그 시작 순간에는 makeCounter함수 만 있습니다. 함수 선언이기 때문입니다. 아직 실행되지 않았습니다.
생성되는 모든 함수들은 Lexical Environment 를 참조하여 숨겨진 속성Environment을 받습니다.
함수가 어디에서 만들어 졌는지 아는 방법을 우리는 아직 말하지 않았습니다.
여기서 makeCounter는 글로벌 Lexical Environment에서 만들어지므로 Environment은 참조를 유지합니다.
다시 말해서, 함수는 만들어진 Lexical Environment를 참조하여 각인됩니다.
Environment은 해당 참조가 있는 숨겨진 함수 속성입니다.

  1. 코드가 실행되고, 새로운 전역 변수 카운터가 선언된 후 makeCounter() 호출의 결과를 얻습니다.
    실행이 makeCounter()의 첫 번째 줄에있는 순간의 스냅 샷입니다.

    makeCounter() 호출시 변수 및 인수를 보유하기 위해 Lexical Environment가 작성됩니다.
    모든 Lexical Environments는 두 가지를 저장합니다.
1. 지역 변수가 있는 Environment Record. 이 경우 count는 유일한 로컬 변수입니다.(let count가 있는 행이 실행될 때 나타납니다).  
2. 외부 lexical 참조는 함수의 [Environment](/Lee-hyuna/33-js-concepts-kr/wiki/Environment) 값으로 설정됩니다.  
   여기서 makeCounter의 [Environment](/Lee-hyuna/33-js-concepts-kr/wiki/Environment)는 글로벌 어휘 환경을 참조합니다.

이제 두 개의 Lexical Environment가 있습니다.
첫 번째 Environment은 전역이고, 두 번째 환경은 현재 makeCounter 호출에 대한 것이며 외부 참조는 전역입니다.

  1. makeCounter()를 실행하는 동안 작은 중첩 함수가 생성됩니다.
    함수가 함수 선언 또는 함수 표현식을 사용하여 작성되는지는 중요하지 않습니다.
    모든 함수는 작성된 Lexical Environment를 참조하는 Environment 특성을 가져옵니다.
    그래서 우리의 새로운 작은 중첩 함수도 그것을 얻습니다.

새로운 중첩 함수의 경우 Environment의 값은 makeCounter()의 현재 Lexical Environment입니다.

이 단계에서 내부 함수가 작성되었지만 아직 호출되지 않았습니다.
function() 내부의 코드 {return count ++; }이 실행되고 있지 않습니다.

  1. 실행이 진행되면서 makeCounter() 호출이 완료되고 결과가 전역 변수 카운터에 할당됩니다.

이 함수에는 한 줄만 있습니다 : return count ++. 우리가 실행할 때 실행될 것입니다.

  1. counter()가 호출되면 새 Lexical Environment가 호출에 작성됩니다.
    카운터에는 자체 로컬 변수가 없으므로 비어 있습니다.
    그러나 카운터의 Environment는 외부 참조로 사용되며 생성 된 이전 makeCounter() 호출의 변수에 대한 액세스를 제공합니다.

    이제 호출에서 count 변수를 찾으면 먼저 자체 Lexical Environment를 찾은 다음 외부 makeCounter() 호출의 Lexical Environment를 찾습니다.

여기에서 메모리 관리 작동 방식에 유의하십시오.
makeCounter() 호출이 얼마 전에 완료되었지만 Environment가 참조하는 중첩 함수가 있기 때문에 Lexical Environment가 메모리에 유지되었습니다.

일반적으로 Lexical Environment 객체는 사용할 수있는 기능이있는 한 존재합니다.
그리고 남아있는 것이 없을 때만 지워집니다.

  1. counter()에 대한 호출은 count의 값을 반환 할 뿐만 아니라 값을 증가시킵니다.
    수정은 "제자리"에서 수행됩니다. count값은 발견 된 환경에서 정확하게 수정됩니다.

  2. 다음 counter() 호출도 동일합니다.
    이 장의 시작 부분에서 두 번째 질문에 대한 답이 분명해졌습니다. 아래 코드의 work() 함수는 외부 Lexical Environment 참조를 통해 원래 위치에서 이름을 가져옵니다.

여기에서 결과는 "Pete"입니다. 그러나 makeWorker()에 let 이름이 없으면 위의 체인에서 볼 수 있듯이 검색이 외부로 이동하여 전역 변수를 가져옵니다.
이 경우 "John"이 됩니다.

코드 블록 및 루프, IIFE

위의 예는 기능에 집중되었습니다. 그러나 모든 코드 블록에 대해 Lexical Environment가 존재합니다.
Lexical Environment는 코드 블록이 실행될 때 작성되며 블록 로컬 변수를 포함합니다. 다음은 몇 가지 예입니다.

If

아래 예에서 사용자 변수는 if 블록에만 존재합니다.

실행이 if 블록으로 들어가면 새로운 "if-only" Lexical Environment가 작성됩니다.

외부에 대한 참조가 있으므로 구를 찾을 수 있습니다.
그러나 if로 선언 된 모든 변수와 함수 표현식은 해당 Lexical Environment에 있으며 외부에서 볼 수 없습니다.
예를 들어 완료되면 아래 알림에 사용자가 표시되지 않으므로 오류가 발생합니다.

For, while

루프의 경우 모든 반복에는 별도의 Lexical Environment가 있습니다. 변수가 for (let ...)에 선언되면 다음에도 있습니다.

for (let i = 0; i < 10; i++) {
  // Each loop has its own Lexical Environment
  // {i: value}
}

alert(i); // Error, no such variable

참고 : 외부에서 시각적으로 let i는 {...} 입니다. for 구문은 여기에서 특별합니다. 루프의 각 반복에는 현재 i가 있는 고유 한 Lexical Environment이 있습니다.
루프 후 i가 보이지 않는 경우에도 마찬가지입니다.

Code blocks

또한 "bare" 코드 블록 {…}을 사용하여 변수를 "로컬 범위"로 분리 할 수 ​​있습니다.
예를 들어, 웹 브라우저에서 모든 script(type = "module"제외)는 동일한 전역 영역을 공유합니다.
따라서 하나의 script에서 전역 변수를 만들면 다른 변수에서도 사용할 수 있게 됩니다.
그러나 두 script가 동일한 변수 이름을 사용하고 서로 덮어 쓰면 충돌의 원인이 됩니다.

변수 이름이 널리 퍼져 있고 script 작성자가 서로를 모르는 경우에 발생할 수 있습니다.

이를 피하려면 코드 블록을 사용하여 전체 script 또는 script의 일부를 분리하십시오.

{
  // do some job with local variables that should not be seen outside

  let message = "Hello";

  alert(message); // Hello
}

alert(message); // Error: message is not defined

블록 자체 또는 다른 script 내부의 코드는 블록 자체에 Lexical Environment가 있기 때문에 블록 내부에 변수가 표시되지 않습니다.

IIFE

과거에는 JavaScript에 블록 레벨 Lexical Environment이 없었습니다.
그래서 프로그래머들은 무언가를 발명해야했습니다. 그리고 그들이 한 것은 “즉시 호출 된 함수 표현”(IIFE로 약칭) 입니다.
요즘 우리가 사용해야 할 것은 아니지만 오래된 script에서 찾을 수 있으므로 이해하는 것이 좋습니다.
IIFE는 다음과 같습니다.

(function() {

  let message = "Hello";

  alert(message); // Hello

})();

여기서 함수 표현식이 작성되어 즉시 호출됩니다. 따라서 코드는 즉시 실행되며 자체 개인 변수가 있습니다.

함수 표현식은 JavaScript가 기본 코드 플로우에서 "함수"를 충족 할 때이를 함수 선언의 시작으로 이해하므로 괄호 (함수 {...})로 묶습니다.
그러나 함수 선언에는 이름이 있어야하므로 이러한 종류의 코드는 오류를 발생시킵니다.

// Try to declare and immediately call a function
function() { // <-- Error: Unexpected token (

  let message = "Hello";

  alert(message); // Hello

}();

JavaScript는 함수 선언을 즉시 호출 할 수 없으므로 작동하지 않습니다.

// syntax error because of parentheses below
function go() {

}(); // <-- can't call Function Declaration immediately

따라서 함수 주위의 괄호는 함수가 다른 표현식의 컨텍스트에서 작성되었음을 JavaScript에 표시하는 트릭이므로 이름이 필요 없으며 즉시 호출 할 수 있습니다.
JavaScript에 함수 표현식을 의미한다고 알리는 괄호 외에 다른 방법이 있습니다.

// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();

위의 모든 경우에 함수 표현식을 선언하고 즉시 실행합니다.
다시 한 번 말씀 드리겠습니다. 요즘에는 그러한 코드를 작성할 이유가 없습니다.

Garbage collection

일반적으로 Lexical Environment는 기능 실행 후 정리 및 삭제됩니다. 예를 들어 :

function f() {
  let value1 = 123;
  let value2 = 456;
}

f();

여기서 두 값은 기술적으로 Lexical Environment의 속성입니다.
그러나 f()가 끝나면 Lexical Environment에 도달 할 수 없게되어 메모리에서 삭제됩니다.

그러나 f 함수가 끝난 후에도 여전히 도달 할 수있는 중첩 함수가있는 경우 외부 Lexical Environment을 참조하는 Environment 특성이 있으므로 도달 가능하고 살아 있습니다.

function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // g is reachable, and keeps the outer lexical environment in memory

f()가 여러 번 호출되어 결과 함수가 저장되면 해당하는 모든 Lexical Environment 객체도 메모리에 유지됩니다.
아래 코드에서 3개 모두 :

function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 3 functions in array, every one of them links to Lexical Environment
// from the corresponding f() run
let arr = [f(), f(), f()];

Lexical Environment 개체는 다른 개체와 마찬가지로 연결할 수 없게되면 죽습니다.
다시 말해, 함수를 참조하는 하나 이상의 중첩 함수가있는 동안에 만 존재합니다.

아래 코드에서 g가 도달 할 수 없게되면 Lexical Environment를 둘러싸는 (따라서 값) 메모리에서 정리됩니다.

function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // while g is alive
// their corresponding Lexical Environment lives

g = null; // ...and now the memory is cleaned up

Real-life optimizations

우리가 보았듯이 이론적으로 함수가 살아있는 동안 모든 외부 변수도 유지됩니다.
그러나 실제로 JavaScript 엔진은 이를 최적화하려고합니다.
변수 사용을 분석하고 코드에서 외부 변수가 사용되지 않는 것이 분명하면 제거됩니다.
V8 (Chrome, Opera)의 중요한 부작용은 디버깅에서 이러한 변수를 사용할 수 없다는 것입니다.
개발자 도구가 열린 상태에서 아래 예제를 Chrome에서 실행 해보십시오.
콘솔 유형에서 일시 중지되면 alert(value)을 입력하십시오.

function f() {
  let value = Math.random();

  function g() {
    debugger; // in console: type alert(value); No such variable!
  }

  return g;
}

let g = f();
g();

보시다시피 – 그러한 변수는 없습니다. 이론적으로는 접근이 가능해야하지만 엔진은 이를 최적화했습니다.
이는 시간이 많이 걸리지 않는 재미있는 디버깅 문제로 이어질 수 있습니다.
그중 하나 – 예상 변수 대신 같은 이름의 외부 변수를 볼 수 있습니다.

let value = "Surprise!";

function f() {
  let value = "the closest value";

  function g() {
    debugger; // in console: type alert(value); Surprise!
  }

  return g;
}

let g = f();
g();

문제

counter들은 독립적입니까?

동일한 makeCounter 함수를 사용하여 counter와 counter2의 두 카운터를 만듭니다. 그들은 독립적입니까? 두 번째 카운터는 무엇입니까? 0,1 또는 2,3 또는 다른 것은?

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ?


답은 0,1입니다.
함수 counter 및 counter2는 makeCounter의 다른 호출로 작성됩니다.
따라서 그들은 독립적 인 외부 Lexical Environment을 가지고 있으며, 각각 고유 한 수를 가지고 있습니다.

Counter object

여기서 counter 객체는 생성자 함수의 도움으로 만들어집니다. 작동합니까? 무엇을 보여줄까요?

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };
  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // ?
alert( counter.up() ); // ?
alert( counter.down() ); // ?

잘 작동합니다.  
두 개의 중첩 함수는 동일한 외부 Lexical Environment  내에 작성되므로 동일한 개수 변수에 대한 액세스를 공유합니다.

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };

  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // 1
alert( counter.up() ); // 2
alert( counter.down() ); // 1

Function in if

코드를보십시오. 마지막 라인에서 호출한 결과는 무엇입니까?

let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();

결과는 오류입니다.

sayHi 함수는 if 내부에 선언되므로 내부에만 존재합니다. 밖에는 sayHi 함수가 없습니다.

Sort by field

정렬 할 객체 배열이 있습니다.

let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" }
];

이를 수행하는 일반적인 방법은 다음과 같습니다.

// by name (Ann, John, Pete)
users.sort((a, b) => a.name > b.name ? 1 : -1);

// by age (Pete, Ann, John)
users.sort((a, b) => a.age > b.age ? 1 : -1);

이처럼 덜 장황하게 만들 수 있습니까?

users.sort(byField('name'));
users.sort(byField('age'));

따라서 함수를 작성하는 대신 byField (fieldName)을 입력하십시오. 이를 위해 사용할 수있는 byField 함수를 작성하십시오.

let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" }
];

function byField(field) {
  return (a, b) => a[field] > b[field] ? 1 : -1;
}

users.sort(byField('name'));
users.forEach(user => alert(user.name)); // Ann, John, Pete

users.sort(byField('age'));
users.forEach(user => alert(user.name)); // Pete, Ann, John

Army of functions

다음 코드는 shooters 배열을 만듭니다. 모든 함수는 숫자를 출력하기위한 것입니다. 그러나 무언가 잘못되었습니다.

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // shooter function
      alert( i ); // should show its number
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // the shooter number 0 shows 10
army[5](); // and number 5 also outputs 10...
// ... all shooters show 10 instead of their 0, 1, 2, 3...

왜 모든 shooters들이 똑같이 보이나요? 의도 한대로 작동하도록 코드를 수정하십시오.

makeArmy에서 수행 한 작업을 살펴보면 솔루션이 명확 해집니다. 1. 빈 배열을 만듭니다.

let shooters = [];

shooters.push (function ...)를 통해 루프에 채 웁니다. 모든 요소는 함수이므로 결과 배열은 다음과 같습니다.

shooters = [
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); }
];

배열은 함수에서 반환됩니다. 그런 다음 나중에 army5를 호출하면 배열에서 army[5] 요소를 가져 와서 함수로 만듭니다. 이제 왜 그러한 기능이 모두 같은가요? 그것은 shooters 함수 안에 지역 변수 i가 없기 때문입니다.
이러한 함수가 호출되면 외부 Lexical Environment에서 i를 가져옵니다. i의 가치는 무엇입니까? 소스를 보면 :

function makeArmy() {
  ...
  let i = 0;
  while (i < 10) {
    let shooter = function() { // shooter function
      alert( i ); // should show its number
    };
    ...
  }
  ...
}

현재 makeArmy() 실행과 관련된 Lexical Environment에 존재한다는 것을 알 수 있습니다.
그러나 army5가 호출되면 makeArmy는 이미 작업을 완료했으며 마지막 값은 10입니다. 결과적으로 모든 shooters 함수는 외부 Lexical Environment에서 동일하게 마지막 값 i = 10을 얻습니다. 변수 정의를 루프로 이동하여 문제를 해결할 수 있습니다.

function makeArmy() {

  let shooters = [];

  for(let i = 0; i < 10; i++) {
    let shooter = function() { // shooter function
      alert( i ); // should show its number
    };
    shooters.push(shooter);
  }

  return shooters;
}

let army = makeArmy();

army[0](); // 0
army[5](); // 5

이제 (let i = 0 ...) {...}에 대한 코드 블록이 실행될 때마다 해당 변수 i를 사용하여 새로운 Lexical Environment가 작성되므로 올바르게 작동합니다. 그래서, i의 가치는 이제 조금 더 가까이 산다.
makeArmy() Lexical Environment가 아니라 현재 루프 반복에 해당하는 Lexical Environment에 있습니다.
이것이 바로 지금 작동하는 이유입니다.

여기서 우리는 잠시 동안 다시 썼습니다. 또 다른 요령이 가능할 수 있습니다.

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let j = i;
    let shooter = function() { // shooter function
      alert( j ); // should show its number
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // 0
army[5](); // 5

while 루프는 매 실행마다 새로운 Lexical Environment을 만듭니다.
그래서 우리는 그것이 범인에게 올바른 가치를 얻도록합니다. let j = i를 복사합니다. 이것은 루프 본문을 로컬 j로 만들고 i의 값을 복사합니다.
기본사항은 "by value"로 복사되므로 실제로 현재 루프 반복에 속하는 i의 완전한 독립 사본을 얻습니다.