자바 스크립트가 어떻게 작동합니까?


질문

 

그들이 구성된 개념에 대한 지식을 가진 누군가에게 자바 스크립트 클로저를 설명하겠습니까 (예 : 함수, 변수 등)는 폐쇄를 이해하지 못하는가?

나는 Wikipedia에 주어진 계획 예제를 보았지만 불행히도 도움이되지 않았습니다.


답변

 

폐쇄는 다음과 같은 페어링입니다.

  1. A function and
  2. A reference to that function's outer scope (lexical environment)

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

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

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

다음 코드에서는 foo가 호출 될 때 생성 된 실행 컨텍스트의 어휘 환경의 어휘 환경과 폐쇄를 형성하여 변수 비밀을 닫습니다.

foo () 함수 foo () { const secret = math.trunc (math.random () * 100) 반환 함수 내부 () { console.log (비밀 번호는 $ {비밀}입니다.) } } const f = foo () //`foo` 외부에서 직접 액세스 할 수 없다. f () //`secret`를 검색하는 유일한 방법은`f '를 호출하는 것입니다.

즉, JavaScript에서 함수는 비공개 "박스 상태"에 대한 참조를 운반합니다.이 상태 의이 상자는 기능의 발신자가 보이지 않으므로 데이터 숨기기 및 캡슐화를위한 우수한 메커니즘을 제공합니다.

그리고 기억하십시오 : JavaScript의 함수는 변수 (일류 함수)와 같은 변수 (일류 함수)와 같은 기능을 전달할 수 있습니다. 이러한 기능의 쌍이 프로그램 주위를 전달할 수 있습니다. C ++에서 클래스의 인스턴스를 전달하는 방법과 유사합니다.

JavaScript가 클로저가 없으면 더 많은 상태가 명시 적으로 함수간에 전달되어야하므로 매개 변수 목록을 길게 만들고 시끄럽게 시끄럽게 표시합니다.

따라서 함수가 항상 개인 조각에 액세스 할 수있게하려면 폐쇄를 사용할 수 있습니다.

... 그리고 자주 상태를 함수와 연관시키고 싶습니다.예를 들어, Java 또는 C ++에서는 개인 인스턴스 변수를 추가하고 클래스에 메서드를 추가 할 때 상태와 상태를 연관시킵니다.

C와 대부분의 다른 일반 언어에서 함수가 반환 된 후 스택 프레임이 파괴되므로 모든 로컬 변수가 더 이상 액세스 할 수 없습니다.JavaScript에서는 다른 함수 내에서 함수를 선언하면 외부 함수의 로컬 변수가 반환 한 후에 액세스 할 수 있습니다.이런 식으로 위의 코드에서 비밀은 Foo로부터 반환 된 후에 기능 객체 내부에 사용할 수 있습니다.

클로저의 용도

폐쇄는 기능과 관련된 개인 상태가 필요할 때마다 유용합니다.이것은 매우 일반적인 시나리오이며, JavaScript는 2015 년까지 클래스 구문이 없었으며 여전히 개인 필드 구문이 아직 없습니다.폐쇄는이 필요를 충족시킵니다.

개인 인스턴스 변수

다음 코드에서 tostring 함수는 차의 세부 사항을 닫습니다.

기능 자동차 (제조업체, 모델, 년, 색상) { 반품 { tostring () { `$ {제조업체} $ {model} ($ {year}, $ {Color})` } } } Const Car = New Car ( 'Aston Martin', 'V8 Vantage', '2012', '양자 실버') console.log (car.tostring ())

기능 프로그래밍

다음 코드에서는 기능 내부가 Fn 및 Args를 모두 닫습니다.

기능 카레 (Fn) { const args = [] 반환 기능 내부 (arg) { if (args.length === fn.length) fn (... args) args.push (arg) 내면을 돌려주십시오 } } 기능 추가 (a, b) { + B를 반환합니다 } const currelingadd = 카레 (추가) console.log (CurrelieDD (2) (3) ()) // 5

이벤트 지향 프로그래밍

다음 코드에서 기능 onClick은 변수 background_color를 닫습니다.

const $ = document.querySelector.Bind (문서) const background_color = 'RGBA (200, 200, 242, 1)' ' 기능 onClick () { $ ( 'body'). style.background = background_color } $ ( '버튼'). addEventListener ( '클릭', OnClick) <버튼> 배경색 세트

모듈화

다음 예에서는 모든 구현 세부 정보가 즉시 실행 된 함수 식 안에 숨겨져 있습니다.기능은 똑딱 똑똑하고 tostring 자신의 작업을 완료하는 데 필요한 민간 상태와 함수를 닫습니다.클로저를 사용하면 코드를 모듈화하고 캡슐화 할 수있었습니다.

네임 스페이스 = {}를 둡니다. (기능 foo (n) { 숫자 = [] 기능 형식 (n) { return math.trunc (n) } 기능 틱 () { Numbers.Push (math.random () * 100) } 함수 toString () { 반환 값 .map (형식) } n.counter = { 진드기, tostring. } } (네임 스페이스)) const 카운터 = 네임 스페이스. 카운터 counter.tick () counter.tick () console.log (Counter.toString ())

예 1

이 예에서는 로컬 변수가 클로저에서 복사되지 않았 음을 보여줍니다. 클로저는 원래 변수 자체에 대한 참조를 유지 관리합니다.외부 함수가 종료 된 후에도 스택 프레임이 메모리에서 계속 유지되는 것과 같습니다.

foo () 함수 foo () { x = 42를합시다 inner = () => console.log (x) x = x + 1. 내면을 돌려주십시오 } foo () () // logs 43.

예 2

다음 코드에서는 3 가지 메소드가 로그, 증분 증분 및 업데이트와 동일한 어휘 환경을 통해 모두 닫힙니다.

CreateObject가 호출 될 때마다 새로운 실행 컨텍스트 (스택 프레임)가 만들어지고 완전히 새로운 변수 x 이며이 새 변수를 닫으면 새 기능 (로그 등)이 생성됩니다.

function createObject () { x = 42를 소지하고; 반품 { log () {console.log (x), 증가 () {x ++}, 업데이트 (값) {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을 사용하여 선언 된 변수는 호이스트됩니다.이것은 LET 및 Const의 도입으로 인해 현대 자바 스크립트의 문제가 훨씬 적습니다.

다음 코드에서 루프 주위의 매번 새로운 기능이 생성되므로 i를 닫습니다.그러나 var i는 루프 밖에서 호이스트가 있기 때문에 이러한 모든 내부 기능이 동일한 변수를 닫습니다. 즉 i (3)의 최종 값이 3 번 인쇄됩니다.

foo () 함수 foo () { var 결과 = [] for (var i = 0; i <3; i ++) { result.Push (함수 내부 (함수) {console.log (i)}) } 결과를 반환합니다 } const 결과 = foo () // 다음은 3 번, 3 번 인쇄 할 것입니다 ... for (var i = 0; i <3; i ++) { 결과 [i] () }

최종 포인트 :

JavaScript Closure에서 함수가 선언 될 때마다 생성됩니다. 다른 기능 내부에서 함수를 반환하면 외부 함수의 상태가 완료된 후에도 외부 함수 내부의 상태가 반환 된 내부 함수에서 암시 적으로 사용할 수 있기 때문에 클로저의 클래식 예입니다. 함수 내에서 eval ()을 사용할 때마다 클로저가 사용됩니다. Eval 텍스트는 함수의 로컬 변수를 참조 할 수 있으며 엄격하지 않은 모드에서 Eval ( 'var foo = ...')를 사용하여 새 로컬 변수를 만들 수도 있습니다. 함수 내에서 새로운 기능 (...) (함수 생성자)을 사용하면 어휘 환경을 닫지 않습니다. 대신 전역 컨텍스트를 닫습니다. 새로운 기능은 외부 함수의 로컬 변수를 참조 할 수 없습니다. JavaScript의 폐쇄는 함수 선언 시점에서의 범위에 대한 참조 (복사가 아님)를 유지하는 것과 같습니다. 이는 바깥 쪽 범위에 대한 참조를 유지하고 전역의 맨 위에있는 전역 객체로의 모든 방법으로 범위 사슬. 함수가 선언 될 때 클로저가 생성됩니다. 이 클로저는 함수가 호출 될 때 실행 컨텍스트를 구성하는 데 사용됩니다. 함수가 호출 될 때마다 새로운 로컬 변수 세트가 생성됩니다.

연결

Douglas Crockford의 시뮬레이션 된 개인 속성 및 폐쇄를 사용하여 객체의 비공개 메소드. 당신이 조심하지 않으면 폐쇄가 어떻게 메모리 누수를 일으킬 수 있는지에 대한 훌륭한 설명입니다. 자바 스크립트 클로저에 대한 MDN 문서.



답변

JavaScript의 모든 기능은 외부 어휘 환경에 대한 링크를 유지합니다.어휘 환경은 범위 내의 모든 이름 (예 : 변수, 매개 변수)의지도이며 값이있는 범위 내에서.

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

foo (x) { var tmp = 3; 기능 바 (y) { console.log (x + y + (++ tmp));//는 16을 기록합니다 } 바 (10); } foo (2);

함수 막대는 파라미터 X와 변수 TMP를 닫아서 ourly 함수 foo의 어휘 환경에 존재하는 변수 막대가 닫히기 때문에 이것은 16을 기록합니다.

기능 막대는 기능의 어휘 환경과 함께 Foo의 어휘 환경과 함께 폐쇄입니다.

폐쇄를 만들기 위해 함수가 반환 할 필요가 없습니다.단순히 선언 덕분에 모든 함수는 동봉 된 어휘 환경을 폐쇄하여 폐쇄를 형성합니다.

foo (x) { var tmp = 3; 반환 기능 (y) { console.log (x + y + (++ tmp));// 16을 기록합니다 } } var bar = foo (2); 바 (10);// 16. 바 (10);// 17.

위의 기능은 막대 내부의 코드가 여전히 인수 x 및 변수 TMP를 참조 할 수 있지만 더 이상 범위가 더 이상 없을지라도 로그 16을 기록합니다.

그러나 TMP가 여전히 바의 폐쇄 안에 매달려 있기 때문에 증가 할 수 있습니다.바를 콜 할 때마다 증가 할 것입니다.

폐쇄의 가장 간단한 예는 다음과 같습니다.

var a = 10; 기능 검사() { console.log (a);// 10을 출력합니다 console.log (b);//는 6을 출력합니다 } var b = 6; 테스트();

JavaScript 함수가 호출되면 새 실행 컨텍스트 EC가 생성됩니다.함수 인수와 대상 객체와 함께이 실행 컨텍스트는 호출 실행 컨텍스트의 어휘 환경에 대한 링크를받습니다. 즉, 외부 어휘 환경에서 선언 된 변수 (위의 예에서는 A 및 B 모두에서)를 의미합니다.EC.

모든 함수에는 모든 함수가 바깥 쪽 어휘 환경에 대한 링크가 있기 때문에 폐쇄가 생성됩니다.

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



답변

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

오래된 알버트처럼 "6 살짜리 세에 그것을 설명 할 수 없다면 정말로 그것을 이해하지 못합니다.". 나는 27 년 된 친구에게 JS 클로저를 설명하려고 노력하고 완전히 실패했습니다. 누구도 내가 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();

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

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

... 정말로 어린 소녀가있는 공주입니다.



답변

심각하게 질문을하고, 우리는인지 적으로 전형적인 6 살이 능력이있는 것을 알아야합니다. 그러나 틀림없이 자바 스크립트에 관심이있는 사람은 그렇게 전형적이지 않습니다.

어린 시절 개발 : 5 ~ 7 년 :

자녀가 2 단계의 지시를 따를 수 있습니다.예를 들어, 당신이 당신의 자녀에게 "부엌에 가서 휴지통으로 가서 그 방향을 기억할 수있을 것입니다.

우리는이 예제를 사용하여 다음과 같이 폐쇄를 설명 할 수 있습니다.

주방은 쓰레기 주머니라고하는 지역 변수가있는 폐쇄입니다.Gettrashbag라는 부엌 내부에는 하나의 휴지통 가방을 가져오고 반환합니다.

우리는 이와 같이 자바 스크립트 에서이 코드를 코딩 할 수 있습니다.

함수 makekitchen () { var trashbags = [ 'a', 'b', 'c'];// 처음에는 3 번 밖에 없습니다 반품 { gettrashbag : 함수 () { trashbags.pop ()을 반환합니다. } }; } var 주방 = makekitchen (); console.log (Kitchen.getTrashBag ());// 쓰레기 백을 반환합니다 console.log (Kitchen.getTrashBag ());// 쓰레기 가방 B를 반환합니다 console.log (Kitchen.getTrashBag ());// 쓰레기 봉투를 반환합니다

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

makekitchen ()이 호출 될 때마다 새로운 폐쇄가 자체 별도의 쓰레기 가방으로 만들어집니다. trashbags 변수는 각 주방의 내부로 로컬이며 외부에 액세스 할 수 없지만 GetTrashBag 속성의 내부 기능이 액세스 할 수 있습니다. 모든 기능 호출은 폐쇄를 만듭니다. 그러나 폐쇄의 내부에 액세스 할 수있는 내부 함수가 폐쇄 밖에서 호출 할 수없는 경우를 제외하고는 폐쇄를 유지할 필요가 없습니다.GetTashBag 함수로 객체를 반환하면 여기 있습니다.



답변

짚사

버튼을 클릭하고 세 번째 클릭에 대해 몇 번이나 뭔가를 할 수 있는지 알아야합니다 ...

상당히 분명한 해결책

// 이벤트 핸들러 범위 외부 카운터를 선언합니다 var 카운터 = 0; var 요소 = document.getElementById ( '버튼'); element.addeventListener ( "클릭", 함수 () { // 카운터 외부 증분 카운터 ++; if (Counter === 3) { // 세 번째마다 무언가를하십시오 console.log ( "세 번째 시간은 매력!"); // 카운터 재설정 카운터 = 0; } }); <버튼 ID = "버튼"> 클릭하십시오!

이제는이 일이 일어나지 만 변수를 추가하여 바깥 쪽 범위에 침입하여 유일한 목적이 카운트를 추적하는 것입니다.일부 상황에서는 외부 응용 프로그램 이이 정보에 대한 액세스가 필요할 수 있으므로이 정보가 바람직합니다.그러나이 경우, 우리는 세 번째 클릭의 동작 만 변경하기 때문에이 기능을 이벤트 핸들러 내에 묶는 것이 바람직합니다.

이 옵션을 고려하십시오

var 요소 = document.getElementById ( '버튼'); element.addeventListener ( "클릭", (함수 (함수 () { // init to 0. var count = 0; 반환 기능 (e) {// <-이 함수는 클릭 처리기가됩니다. 카운트 ++;// 위의`count`에 대한 액세스 권한을 유지합니다. if (count === 3) { // 세 번째마다 무언가를하십시오 console.log ( "세 번째 시간은 매력!"); // 카운터 재설정 카운트 = 0; } }; }) ()); <버튼 ID = "버튼"> 클릭하십시오!

여기에 몇 가지 사항을 확인하십시오.

위의 예에서는 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 고려 사항 포함)



답변

폐쇄는 모두가 직관적으로 어쨌든 일하기를 기대할 수있는 행동을하는 데 사용되기 때문에 설명하기가 어렵습니다.나는 그들을 설명하는 가장 좋은 방법을 찾는다 (그리고 그들이하는 일을 배웠던 방식)은 그들없이 상황을 상상하는 것입니다 :

const makeplus = 함수 (x) { 반환 기능 (y) {return x + y;}; } const plus5 = makeplus (5); console.log (플러스 5 (3));

자바 스크립트가 폐쇄를 알지 못한 경우 여기에서 일어날 것입니까?마지막 줄의 통화를 메소드 본문 (기본적으로 기능이 어떤 함수가 수행)으로 바뀌는 것만으로 만듭니다.

console.log(x + 3);

이제 x의 정의는 어디에 있습니까?우리는 현재 범위에서 그것을 정의하지 않았습니다.유일한 해결책은 플러스 5가 그 범위 (또는 오히려 부모의 범위)를 운반하는 것입니다.이렇게하면 X는 잘 정의되어 있으며 값 5에 묶여 있습니다.

출처:https://stackoverflow.com/questions/111102/how-do-javascript-closures-work