JavaScript 폐쇄는 어떻게 작동합니까?
JavaScript 클로징은 어떤 개념(예를 들어 함수, 변수 등)을 알고 있지만 클로징 자체는 이해하지 못하는 사람에게 어떻게 설명할 수 있습니까?
Wikipedia에 있는 Scheme의 예를 보았습니다만, 유감스럽게도 도움이 되지 않았습니다.
폐쇄는 다음 요소의 조합입니다.
- 기능 및
- 해당 기능의 외부 범위(렉시컬 환경)에 대한 참조
어휘 환경은 모든 실행 컨텍스트(스택 프레임)의 일부이며 식별자(로컬 변수 이름)와 값 사이의 맵입니다.
JavaScript의 모든 함수는 외부 어휘 환경에 대한 참조를 유지합니다.이 참조는 함수를 호출할 때 생성되는 실행 컨텍스트를 구성하기 위해 사용됩니다.이 참조를 통해 함수가 호출된 시기와 위치에 관계없이 함수 내부의 코드가 함수 외부에 선언된 변수를 볼 수 있습니다.
함수에 의해 함수가 호출되고, 다른 함수에 의해 호출된 경우, 외부 어휘 환경에 대한 참조 사슬이 생성됩니다.이 체인을 스코프 체인이라고 합니다.
다음 코드에서는inner
다음 경우에 생성된 실행 컨텍스트의 사전 환경과 함께 폐쇄를 형성합니다.foo
호출되어 변수 위로 닫힙니다.secret
:
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에 closes가 없다면 함수 간에 더 많은 상태를 명시적으로 전달해야 하므로 파라미터 목록이 더 길고 코드 노이즈가 더 많이 발생합니다.
따라서 함수가 항상 개인 상태에 액세스할 수 있도록 하려면 닫기를 사용할 수 있습니다.
...그리고 우리는 종종 상태를 함수와 연관짓고 싶어합니다.예를 들어 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
둘 다 닫다fn
그리고.args
.
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
close over 변수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>
모듈화
다음 예제에서는 모든 구현 세부 정보가 즉시 실행된 함수 식 안에 숨겨져 있습니다.기능tick
그리고.toString
작업을 완료하기 위해 필요한 개인 상태 및 기능을 닫습니다.폐쇄로 인해 우리는 코드를 모듈화하고 캡슐화할 수 있게 되었습니다.
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 = () => console.log(x)
x = x + 1
return inner
}
foo()() // logs 43
예 2
다음 코드에서는 세 가지 방법이 있습니다.log
,increment
,그리고.update
모두 같은 어휘 환경에서 가깝습니다.
그리고 매번createObject
호출되어 새로운 실행 컨텍스트(스택 프레임)가 생성되고 완전히 새로운 변수가 생성됩니다.x
및 새로운 기능 세트(log
등)이 생성되어 이 새 변수를 닫습니다.
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
인양되어 있습니다.이것은 최신 JavaScript의 도입으로 인해 훨씬 덜 문제가 됩니다.let
그리고.const
.
다음 코드에서는 루프를 돌 때마다 새로운 함수가 생성됩니다.inner
생성되어 닫힙니다.i
근데 왜냐하면var i
이 내부 함수는 모두 같은 변수 상에서 닫힙니다.즉, 이 값은 루프의 최종값입니다.i
(3)은 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에서 함수가 선언될 때마다 closure가 생성됩니다.
- 반환하다
function
외부 함수의 실행이 완료된 후에도 외부 함수의 상태가 반환된 내부 함수에 암묵적으로 사용 가능하기 때문에 다른 함수의 내부가 닫힘의 전형적인 예입니다. - 사용할 때마다
eval()
함수 내부에서 폐쇄가 사용됩니다.당신의 텍스트eval
함수의 로컬 변수를 참조할 수 있습니다.또, 비표준 모드에서는, 를 사용해 새로운 로컬 변수를 작성할 수도 있습니다.eval('var foo = …')
. - 사용할 때
new Function(…)
(함수 생성자) 함수 내에서는 사전 환경을 닫지 않고 글로벌 컨텍스트를 닫는다.새 함수는 외부 함수의 로컬 변수를 참조할 수 없습니다. - JavaScript의 클로징은 함수 선언 지점에서 스코프에 대한 참조(복사가 아님)를 유지하는 것과 같습니다.이것에 의해 스코프 체인 상단의 글로벌오브젝트에 대한 참조가 유지됩니다.
- 함수가 선언되면 닫힘이 생성됩니다.이 닫힘은 함수가 호출될 때 실행 컨텍스트를 구성하는 데 사용됩니다.
- 함수를 호출할 때마다 새로운 로컬 변수 세트가 생성됩니다.
링크
- 더글라스 크록포드의 시뮬레이션된 개인 속성과 폐쇄를 이용한 개인 방법.
- 주의하지 않으면 IE에서 메모리 누설이 발생할 수 있는 폐기에 대한 자세한 설명입니다.
- JavaScript Closes에 관한 MDN 문서.
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을 기록합니다.왜냐하면,bar
아직 논거를 참조할 수 있다x
및 변수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
작성됩니다.함수 인수 및 타깃오브젝트와 함께 이 실행 컨텍스트는 호출 실행 컨텍스트의 사전 환경 링크도 받습니다.즉, 외부 어휘 환경에서 선언된 변수(위의 예에서는 양쪽 모두)를 의미합니다.a
그리고.b
)는, 다음의 사이트에서 입수할 수 있습니다.ec
.
모든 함수는 외부 어휘 환경에 대한 링크를 가지고 있기 때문에 모든 함수는 폐쇄를 생성합니다.
변수 자체는 복사가 아닌 폐쇄 내에서 볼 수 있습니다.
서문: 이 답변은 다음과 같은 경우에 작성되었습니다.
앨버트가 말한 것처럼, "만약 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();
어른들이 진짜 공주를 알더라도 유니콘이나 용을 볼 수 없기 때문에 믿지 않을 것이다.어른들은 어린 소녀의 상상 속에만 존재한다고 말했다.
하지만 우리는 진실을 알고 있어요. 공주와 함께 있는 어린 소녀는...
공주님 안에 어린 여자애가 있는 것 같아요
이 질문을 진지하게 받아들이면서 우리는 일반적인 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
함수는 여기서 그렇게 합니다.
빨대맨
버튼을 몇 번 클릭했는지 알아야 하며 3번 클릭할 때마다 작업을 수행해야 합니다.
지극히 명백한 솔루션
// 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의 closure 동작을 사용하고 있습니다.이 동작에 의해, 모든 함수는, 작성한 범위에 무제한으로 액세스 할 수 있습니다.이것을 실제로 적용하기 위해서, 나는 즉시 다른 함수를 반환하는 함수를 호출하고, 내가 반환하는 함수는 (위에서 설명한 닫힘 동작 때문에) 내부 카운트 변수에 액세스할 수 있기 때문에, 결과적으로 함수에 의한 전용의 범위가 됩니다.그렇게 간단하지 않아요?희석해서...
한 줄의 간단한 폐쇄
// _______________________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
알 수 있습니까? 이 주요 예에서는 카운트 변수가 폐쇄 내에 포함되어 이벤트 핸들러가 항상 사용할 수 있으므로 클릭할 때마다 상태가 유지됩니다.
또한 이 개인 변수 상태는 판독치와 개인 범위 변수에 할당 모두에서 완전히 액세스할 수 있습니다.
자, 이제 이 동작을 완전히 캡슐화했습니다.
폐쇄는 모든 사람들이 직관적으로 작동하기를 기대하는 행동들을 작동시키기 위해 사용되기 때문에 설명하기가 어렵다.나는 그들을 설명하는 가장 좋은 방법(그리고 내가 그들이 하는 일을 배운 방법)은 그들이 없는 상황을 상상하는 것이라고 생각한다.
const makePlus = function(x) {
return function(y) { return x + y; };
}
const plus5 = makePlus(5);
console.log(plus5(3));
JavaScript가 폐쇄를 인식하지 못하면 여기서 무슨 일이 일어날까요?마지막 행의 콜을 메서드 본문(기본적으로 함수 콜의 기능)으로 치환하면 다음과 같이 됩니다.
console.log(x + 3);
자, 이제, 의 정의는 무엇일까?x
현재 범위에서는 정의하지 않았습니다.유일한 해결방법은 다음과 같이 하는 것이다.plus5
그 범위(혹은 부모 범위)를 가지고 다니다이쪽입니다.x
잘 정의되어 있으며 값 5에 바인딩되어 있습니다.
TLDR
폐쇄는 함수와 외부 어휘(즉, 작성 시) 환경 간의 연결로, 해당 환경에서 정의된 식별자(변수, 매개변수, 함수 선언 등)가 함수가 호출되는 시기와 위치에 관계없이 함수 내에서 볼 수 있도록 합니다.
세부 사항
ECMAScript 규격의 용어에서 폐쇄는 함수가 정의되는 어휘 환경을 가리키는 모든 함수 객체의 참조에 의해 구현된다고 할 수 있다.
내부 메서드를 통해 함수가 호출되면 function-object 상의 참조가 새로 작성된 실행 컨텍스트(스택프레임)의 환경 레코드의 외부 환경 참조로 복사됩니다.
다음 예제에서는 함수가f
는 글로벌 실행 컨텍스트의 사전 환경에서 종료됩니다.
function f() {}
다음 예제에서는 함수가h
기능의 어휘적 환경을 닫다g
그러면 글로벌 실행 컨텍스트의 사전 환경이 닫힙니다.
function g() {
function h() {}
}
외측에서 내부 함수를 반환하면 외측 기능이 반환된 후에도 외측 어휘 환경이 유지됩니다.이는 내부 기능이 최종적으로 호출될 경우 외부 어휘 환경을 사용할 수 있어야 하기 때문이다.
다음 예제에서는 함수가j
기능의 어휘적 환경을 닫다i
(즉, 이 변수를 의미합니다)x
내부 기능에서 볼 수 있습니다.j
, 기능 한참 후i
가 실행을 완료했습니다.
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
외부 환경 참조를 통해 실행 컨텍스트 간에 링크된 어휘 환경 체인은 스코프 체인을 형성하고 주어진 함수에서 볼 수 있는 식별자를 정의합니다.
명확성과 정확성을 높이기 위해 이 답변은 원본에서 상당히 변경되었습니다.
좋아, 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");
이는 다른 답변 중 일부에 나타나는 폐쇄에 대한 몇 가지 오해를 해소하기 위한 시도입니다.
- 내부 함수를 반환할 때만 폐쇄가 작성되는 것은 아닙니다.실제로 폐쇄를 생성하기 위해 폐쇄 함수는 반환할 필요가 없습니다.대신 내부 함수를 외부 범위의 변수에 할당하거나 인수로서 다른 함수에 전달할 수 있습니다.이 함수는 즉시 호출하거나 나중에 호출할 수 있습니다.따라서 내부 함수가 호출될 때마다 내부 함수가 호출될 때마다 해당 폐쇄에 액세스할 수 있기 때문에 폐쇄 함수는 호출되는 즉시 생성될 수 있습니다.
- 폐쇄는 해당 범위에 있는 변수의 이전 값 복사본을 참조하지 않습니다.변수 자체는 폐쇄의 일부이므로 이러한 변수 중 하나에 액세스할 때 표시되는 값이 액세스 시점의 최신 값입니다.그렇기 때문에 루프 내부에 생성되는 내부 함수는 함수를 만들거나 호출할 때 변수의 복사본을 얻는 것이 아니라 각각 동일한 외부 변수에 액세스할 수 있기 때문에 까다로울 수 있습니다.
- 폐쇄의 "변수"에는 함수 내에서 선언된 명명된 함수가 포함됩니다.또한 함수의 인수도 포함됩니다.폐쇄는 포함된 폐쇄 변수에 대한 액세스 권한도 가지며 전역 범위까지 확장됩니다.
- 닫힘은 메모리를 사용하지만 JavaScript 자체에서 참조되지 않는 원형 구조가 지워지기 때문에 메모리 누수를 일으키지는 않습니다.닫힘과 관련된 Internet Explorer 메모리 누수는 닫힘을 참조하는 DOM 속성 값을 연결 해제하지 못할 때 생성되므로 원형 구조에 대한 참조를 유지할 수 있습니다.
나는 얼마 전에 폐쇄에 대해 설명하는 블로그 글을 썼다.왜 폐쇄를 원하는지에 대해 말씀드린 바 있습니다.
닫힘은 함수에 영구적인 개인 변수, 즉 한 함수만 알고 있는 변수, 즉 함수가 실행된 이전 시간의 정보를 추적할 수 있도록 하는 방법입니다.
그런 의미에서 함수는 개인 속성을 가진 개체처럼 기능하도록 합니다.
전체 투고:
원래 질문에는 다음과 같은 인용구가 있었습니다.
여섯 살짜리 아이에게 설명하지 못한다면, 당신은 정말 이해하지 못할 것입니다.
실제 여섯 살배기에게 이렇게 설명하려고 합니다.
어른들이 어떻게 집을 소유할 수 있는지 알아? 그리고 그들은 그걸 집이라고 부르지?엄마가 아이를 낳으면 아이는 아무것도 소유하지 않죠?하지만 부모가 집을 가지고 있기 때문에 "집이 어디예요?"라고 물으면 아이는 "그 집!"이라고 대답하고 부모의 집을 가리킬 수 있다.
"폐쇄"는 아이가 집을 소유한 부모임에도 불구하고 항상 자신의 집을 참조할 수 있는 능력입니다.
폐쇄는 간단합니다.
다음 간단한 예에서는 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
이 계산기는 계속 사용할 수 있습니다.add
그리고.multiply
한참 뒤에 기능하다make_calculator
돌아온다.
스택 프레임에 대해 잘 알고 있는 경우는, 다음의 계산기가 이상합니다.어떻게 계속 접속할 수 있을까요?n
끝나고make_calculator
반품하시겠습니까?정답은 JavaScript가 "스택 프레임"을 사용하지 않고 "히프 프레임"을 사용한다고 상상하는 것입니다. 이 프레임은 반환된 함수 호출 후에도 지속될 수 있습니다.
내부 기능은 다음과 같습니다.add
그리고.multiply
외부** 함수로 선언된 액세스 변수를 폐쇄라고 합니다.
그것이 폐쇄에 대한 거의 모든 것이다.
* 예를 들어, 변수 선언 전에 사용할 수 있음을 보여주는 예 6을 제외한 다른 답변에 제시된 "Closures for Dummies" 기사의 모든 요점을 다루고 있습니다. 이 예 6은 변수를 선언하기 전에 사용할 수 있다는 것을 보여주는 좋은 사실이지만 폐쇄와는 전혀 관련이 없습니다.또한 승인된 답변의 모든 포인트에 대해 설명합니다.다만, (1) 함수를 통해 인수를 로컬 변수에 복사하는 포인트(이름 있는 함수 인수) 및 (2) 번호를 복사하면 새로운 번호가 생성되지만, 객체 참조를 복사하면 동일한 개체에 대한 다른 참조가 제공됩니다.이것들도 알아두면 좋지만, 이것도 폐쇄와는 전혀 관련이 없습니다.또한 이 답변의 예시와 매우 유사하지만 약간 짧고 추상적이지 않습니다.그것은:이 대답의 또는 부분은 자바 스크립트 어렵다는 내부 기능에 루프 변수의 현재 값을 연결하는 것으로 만들어 주는 것으로 이 의견, 포함하지 않는다.그"플러그를 꽂"단계만, 각 루프 반복에 호출됩니다 여러분 내면의 기능을 둘러싼 도우미 함수와 함께.(엄밀히 말하면, 그 내부 기능, 오히려 모든 것에서 막히는 것보다 해당 변수의 도우미 기능의 복사본에 액세스 합니다.) 할 수 있다.때 폐쇄를 만들고 다시지만, 폐쇄적 또는 그것을 어떻게 된 일의 한 부분이 아닌 유용하다.에는 폐쇄 다르게 맥스, 변수보다는 저장 공간에보다 값에 바인딩 되어 같은 기능적 주는 언어는 단순히 자바 스크립트, 변수를 항상 stora 수밖에 없다의 잘못된 방식(즉"플러그를 꽂"방법)에 폐쇄를 이해하는 사람들의 일정한 스트림을 제공하는 데 작업 때문에 추가 혼란이 있다.그 장소, 가치지 않습니다.
** 외부 함수(복수가 중첩되어 있는 경우 또는 이 답변이 명확하게 나타내는 글로벌 컨텍스트 내에서도 마찬가지).
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);
* C# 질문
나는 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
외부 스코프에 연결되어 있습니다.
JavaScript 폐쇄는 어떻게 작동합니까? 그러면 새로운 범위가 생성되고
n
는 그 범위에 바인드 되어 있습니다.즉, 각 반복에 대해1개씩 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()
는, 루프의 반복 마다 1 개의 스코프가 아니고, 이 기능에 대해서 1 개의 스코프만이 작성되었을 때에 호출됩니다.이 함수 내에서 다음과 같은 변수가 있습니다.
index
정의되어 있습니다.루프가 실행되고 반환되는 어레이에 함수가 추가됩니다.index
주의해 주세요.index
에 정의되어 있습니다.createClosureArray
한 번만 호출되는 함수입니다.왜냐하면, 이 영역에는
createClosureArray()
기능.index
는, 그 범위내의 값에만 바인드 됩니다.즉, 루프가 루프의 값을 변경할 때마다index
이 범위 내에서 참조하는 모든 항목에 대해 변경됩니다.어레이에 추가된 모든 함수가 동일한 값을 반환합니다.
index
첫 번째 예시와 같이 10개의 다른 스코프에서 10개의 다른 스코프가 아닌 정의된 부모 스코프에서 변수를 가져옵니다.결과적으로 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
컴퓨터 과학에서 폐쇄는 해당 함수의 로컬이 아닌 이름(자유 변수)에 대한 참조 환경과 함께 함수입니다.
엄밀히 말하면 자바스크립트에서는 모든 함수는 폐쇄입니다.항상 주변 범위에서 정의된 변수에 액세스할 수 있습니다.
JavaScript의 스코프 정의 구성은 다른 많은 언어에서와 같이 코드 블록이 아닌 함수이기 때문에 보통 JavaScript에서 닫는 것은 이미 실행된 주변 함수에 정의되어 있는 비로컬 변수와 함께 작동하는 함수입니다.
폐쇄는 일부 숨겨진 개인 데이터가 있는 함수를 만드는 데 종종 사용됩니다(그러나 항상 그렇지는 않습니다).
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
를 실행하고 나중에 실행하며 호출할 때마다 데이터베이스 함수를 생성합니다.생성된 각 함수에는 고유한 숨겨진 데이터베이스 개체가 있습니다.폐쇄의 또 다른 사용 예는 함수를 반환하지 않고 여러 가지 목적을 위해 여러 개의 함수를 포함하는 객체를 반환하는 경우입니다. 각 함수는 동일한 데이터에 액세스할 수 있습니다.
클로징의 구조를 설명하기 위해 인터랙티브한 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
아이들은 부모님이 돌아가신 후에도 부모와 나눈 비밀을 절대 잊지 않을 것이다.이게 바로 폐쇄가 기능을 위한 것입니다.
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
그리고 지금은 메리가 "더 이상 출마하지 않는다"고 해도, 메리에 대한 기억은 사라지지 않고 그녀의 아이는 메리의 이름과 그들이 함께 했던 다른 비밀들을 항상 기억할 것입니다.
그러니까 앨리스라고 부르면 아이가 대답할 거야
child("Alice") => "My name is Alice, child of Mary"
더 이상 말하지 않겠습니다.
나는 왜 답이 이렇게 복잡한지 이해할 수 없다.
종료는 다음과 같습니다.
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);
폐쇄는 내부 함수가 외부 함수의 변수에 액세스할 수 있는 곳입니다.그게 아마 폐쇄에 대한 가장 간단한 설명일 겁니다.
이미 많은 솔루션이 있다는 것을 알지만, 이 작고 단순한 스크립트가 개념을 보여주는 데 도움이 될 것으로 생각합니다.
// 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
당신은 하룻밤 자고 댄을 초대하는군요댄한테 XBox 컨트롤러 하나만 가져오라고 해
댄이 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의 저자는 폐업에 대해 꽤 잘 설명했고, 폐업을 이해하는 데 필요한 Lexical Environment에 대해서도 설명했습니다.
요약은 다음과 같습니다.
변수가 액세스되지만 로컬 변수가 아닌 경우에는 어떻게 해야 합니까?다음과 같은 경우:
이 경우 인터프리터는 외부 객체에서 변수를 찾습니다.
프로세스는 다음 두 단계로 구성됩니다.
- 첫째, 함수 f가 생성될 때 빈 공간에 생성되지 않습니다.현재 Lexical Environment 객체가 있습니다.위의 경우 창(a는 함수 생성 시 정의되지 않음)입니다.
함수가 생성되면 현재 LexicalEnvironment를 참조하는 숨김 속성인 Scope가 생성됩니다.
변수를 읽었지만 어디에서도 찾을 수 없는 경우 오류가 발생합니다.
중첩 함수
함수를 서로 중첩하여 Lexical Environments 체인을 형성할 수 있습니다.이 체인은 스코프 체인이라고도 불립니다.
따라서 함수 g는 g, a 및 f에 접근할 수 있습니다.
폐쇄
중첩된 기능은 외부 기능이 완료된 후에도 계속 작동할 수 있습니다.
어휘 환경 표시:
보시는 바와 같이this.say
는 사용자 객체의 속성이기 때문에 사용자가 완료된 후에도 계속 활성화됩니다.
그리고 기억한다면, 언제?this.say
생성되어 (모든 기능이) 내부 참조를 얻습니다.this.say.[[Scope]]
현재 Lexical Environment에 대응합니다.따라서 현재 사용자 실행의 LexicalEnvironment는 메모리에 남습니다.사용자의 모든 변수도 해당 속성이기 때문에 평소처럼 정크되지 않고 주의 깊게 보관됩니다.
요점은 내부 함수가 향후 외부 변수에 액세스할 경우 외부 변수에 액세스할 수 있도록 하는 것입니다.
요약:
- 내부 함수는 외부 Lexical Environment에 대한 참조를 유지합니다.
- 내부 함수는 외부 함수가 종료된 경우에도 언제든지 변수에 액세스할 수 있습니다.
- 브라우저는 Lexical Environment를 참조하는 내부 함수가 있을 때까지 Lexical Environment와 모든 속성(변수)을 메모리에 유지합니다.
이를 폐쇄라고 합니다.
JavaScript 함수는 다음 항목에 액세스할 수 있습니다.
- 논쟁들
- 로컬(즉, 로컬 변수와 로컬 함수)
- 다음을 포함한 환경:
- 글로벌(DOM 포함)
- 외부 기능 중 하나
함수가 해당 환경에 액세스할 경우 함수는 폐쇄입니다.
외부 기능은 필수는 아니지만 여기서 설명하지 않는 이점을 제공합니다.폐쇄는 해당 환경의 데이터에 액세스하여 해당 데이터를 존속시킵니다.외부/내부 함수의 경우 외부 함수는 로컬 데이터를 생성하고 최종적으로 종료할 수 있지만 외부 함수가 종료된 후 내부 함수가 생존하면 내부 함수는 외부 함수의 로컬 데이터를 활성 상태로 유지합니다.
글로벌 환경을 사용하는 폐쇄의 예:
스택 오버플로우 투표업 버튼 및 투표다운 버튼 이벤트가 클로저, 투표업클릭 및 투표다운클릭으로 구현되어 글로벌하게 정의되어 있는 외부변수 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
}
이 네 가지 기능은 모두 환경에 접근하기 때문에 폐쇄됩니다.
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);
지침들
데이터: 데이터는 사실의 집합입니다.숫자, 단어, 측정, 관찰 또는 사물에 대한 단순한 설명이 될 수 있습니다.만질 수도, 냄새도, 맛도 볼 수 없어요.쓰고, 말하고, 들을 수 있어요.컴퓨터를 사용하여 터치 냄새와 맛을 낼 수 있습니다.그것은 코드를 사용하는 컴퓨터에 의해 유용하게 쓰일 수 있다.
CODE: 위의 모든 글은 코드라고 불립니다.JavaScript로 작성되어 있습니다.
자바스크립트: 자바스크립트는 언어입니다.영어, 프랑스어, 중국어가 언어인 것처럼.컴퓨터와 다른 전자 프로세서에 의해 이해되는 많은 언어들이 있다.컴퓨터에서 JavaScript를 이해하려면 통역사가 필요합니다.러시아어만 할 줄 아는 선생님이 학교에서 당신의 반을 가르치기 위해 온다고 상상해 보세요.선생님이 '아까부터'라고 하면 반 학생들은 이해하지 못할 것이다.하지만 다행히도 당신 반에 러시아인 학생이 있는데, 그는 모두에게 이것이 "모두 앉아"라는 의미라고 말한다 - 여러분 모두 그렇다는 것이다.수업은 컴퓨터 같고 러시아 학생은 통역입니다.JavaScript에서 가장 일반적인 인터프리터는 브라우저라고 불립니다.
브라우저: 웹 사이트를 방문하기 위해 컴퓨터, 태블릿 또는 전화로 인터넷에 연결할 때 브라우저를 사용합니다.예를 들어 Internet Explorer, Chrome, Firefox 및 Safari 등이 있습니다.브라우저는 JavaScript를 이해하고 컴퓨터에 무엇을 해야 하는지 알려줄 수 있습니다.JavaScript 명령어는 함수라고 불립니다.
기능: JavaScript의 함수는 팩토리와 같습니다.기계가 한 대밖에 없는 작은 공장일 수도 있어요.아니면 다른 많은 작은 공장들이 있을 수도 있고, 각각의 공장들은 서로 다른 일을 하는 많은 기계들을 가지고 있을 수도 있습니다.실제 의류 공장에서는 많은 천과 실밥이 들어가고 티셔츠와 청바지가 나올 수 있습니다.당사의 JavaScript 공장에서는 데이터 처리만 가능하며, 바느질이나 드릴로 구멍을 뚫거나 금속을 녹일 수 없습니다.당사의 JavaScript에서는 공장 데이터가 입력되고 데이터가 출력됩니다.
이 모든 데이터들은 다소 지루하게 들리지만, 정말 멋집니다. 저녁으로 무엇을 만들어야 할지 로봇에게 알려주는 기능이 있을지도 모릅니다.당신과 당신의 친구를 우리 집으로 초대한다고 칩시다.당신은 닭다리를 가장 좋아하고, 나는 소시지를 좋아합니다. 당신의 친구는 항상 당신이 원하는 것을 원하며, 내 친구는 고기를 먹지 않습니다.
쇼핑할 시간이 없어서 냉장고 안에 뭐가 있는지 알아야 결정을 내릴 수 있어요.재료마다 조리시간이 다르므로 로봇에 의해 모든 것이 동시에 뜨겁게 제공되기를 바랍니다.우리가 좋아하는 것에 대한 데이터를 기능에 제공해야 하고, 기능은 냉장고와 '통화'할 수 있고, 기능은 로봇을 제어할 수 있습니다.
함수에는 보통 이름, 괄호 및 중괄호가 있습니다.다음과 같이 합니다.
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
주의:/*...*/
그리고.//
브라우저에서 코드 읽기를 중지합니다.
이름: 원하는 단어에 대해 함수를 호출할 수 있습니다.예를 들어 "cook Meal"은 일반적으로 두 단어를 결합하고 두 번째 단어는 처음에 대문자를 붙입니다.그러나 이것은 필수는 아닙니다.공백이 없어야 하고 숫자 자체일 수도 없습니다.
괄호: "부모" 또는()
는 JavaScript 함수 팩토리 문에 있는 레터 박스 또는 공장으로 정보 패킷을 보내기 위한 거리의 우체통입니다.우편함에 다음과 같이 표시될 수 있습니다. cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
이 경우 어떤 데이터를 제공해야 하는지 알고 있습니다.
브레이스: 이렇게 생긴 '브레이스'{}
우리 공장의 색칠된 창문입니다.공장 안에서는 밖을 볼 수 있지만 밖에서는 볼 수 없다.
위의 긴 코드 예시
우리의 코드는 함수라는 단어로 시작되므로, 우리는 그것이 함수라는 것을 안다!그러면 함수의 이름은 sing - 함수의 내용에 대한 저만의 설명입니다.그런 다음 괄호()를 입력합니다.괄호는 함수에 대해 항상 존재합니다.때때로 그들은 비어있고, 때때로 그들은 무언가를 가지고 있다.이 단어에는 다음과 같은 단어가 있습니다.(person)
이 뒤에 이런 브레이스가 있어요.{
sing() 함수의 시작을 나타냅니다.이렇게 sing()의 끝을 표시하는 파트너가 있습니다.}
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
그래서 이 기능은 노래와 관련이 있을 수 있고 사람에 대한 데이터가 필요할 수 있습니다.이 데이터에는 그 데이터로 무언가를 하도록 지시하는 내용이 들어 있습니다.
여기서 sing() 함수 뒤에 코드 끝에 있는 행이 있습니다.
var person="an old lady";
VARILE: variable은 "variable"을 나타냅니다.변수는 봉투와 같습니다.겉에 이 봉투에는 "사람"이라고 표시되어 있다.그 내부에는 우리의 기능이 필요로 하는 정보가 담긴 종이 조각이 들어 있고, 몇 개의 글자와 공간이 마치 끈(끈이라고 함)처럼 합쳐져 "할머니"라는 문구가 쓰여 있다.봉투에는 숫자(정수), 명령(함수), 목록(배열) 등 다른 종류가 포함될 수 있습니다.이 변수는 모든 괄호 외부에 쓰여져 있기 때문에{}
괄호 안에 있을 때는 색칠된 창을 통해 밖을 볼 수 있기 때문에 이 변수는 코드의 어느 곳에서도 볼 수 있습니다.우리는 이것을 '글로벌 변수'라고 부릅니다.
글로벌 변수: 인물은 글로벌 변수입니다.즉, 그 값을 "노부인"에서 "젊은 남자"로 바꾸면 그 사람은 다시 바꾸기로 결정할 때까지 계속 젊은 남자이고 코드의 다른 기능은 자신이 젊은 남자임을 알 수 있습니다.버튼을 누르거나 옵션 설정을 보고 브라우저 개발자 콘솔을 열고 "person"을 입력하여 이 값을 확인합니다.유형person="a young man"
변경 후 "사용자"를 다시 입력하여 변경되었는지 확인합니다.
이 뒤에 라인이 있습니다.
sing(person);
이 라인은 마치 개를 부르는 것처럼 함수를 호출합니다.
"노래해, 사람 잡으러 와!"
브라우저가 JavaScript 코드를 로드하고 이 행에 도달하면 기능을 시작합니다.브라우저가 실행에 필요한 모든 정보를 가지고 있는지 확인하기 위해 끝에 줄을 긋습니다.
기능은 동작을 정의합니다.주요 기능은 노래에 관한 것입니다.여기에는 firstPart라는 변수가 포함되어 있어 곡의 각 구절마다 적용되는 사람에 대한 노래에 적용됩니다. "There was " + person + " when throughen"콘솔에 firstPart를 입력하면 변수가 함수에 잠겨 있기 때문에 답변을 얻을 수 없습니다.브라우저는 중괄호의 색칠된 창을 볼 수 없습니다.
Closures: 클로저는 큰 sing() 함수 안에 있는 작은 함수입니다.큰 공장 안에 있는 작은 공장들.그들은 각각 그들만의 교정기를 가지고 있는데, 이것은 그들 안의 변수들이 밖에서 볼 수 없다는 것을 의미한다.따라서 변수(생성 및 결과)의 이름이 폐쇄에 반복될 수 있지만 값은 다를 수 있습니다.콘솔 창에 이러한 변수 이름을 입력하면 색칠된 창의 두 계층에 의해 숨겨지기 때문에 값을 얻을 수 없습니다.
closures는 모두 sing() 함수의 변수가 firstPart라고 불리는 것을 알고 있습니다.이는 색칠된 창에서 밖을 볼 수 있기 때문입니다.
문을 닫은 후에 선이 나타납니다.
fly();
spider();
bird();
cat();
sing() 함수는 이들 함수를 지정된 순서대로 호출합니다.그러면 sing() 함수의 작업이 완료됩니다.
좋아요, 여섯 살짜리 아이와 대화할 때, 아마 다음과 같은 연관성을 사용했을 거예요.
상상해 보세요 - 여러분이 집 전체에서 남동생과 여동생들과 놀고 있고, 장난감을 가지고 돌아다니고 있고, 장난감을 몇 개 가지고 오빠의 방으로 데려오고 있습니다.잠시 후, 당신의 오빠는 학교에서 돌아와 방에 들어갔고, 그 안에 자물쇠를 채웠기 때문에, 당신은 거기에 남겨진 장난감들에 직접 접근할 수 없게 되었습니다.하지만 문을 두드리고 동생에게 장난감을 달라고 부탁할 수도 있어요.이것은 장난감의 폐쇄라고 불리는데, 당신의 동생이 당신을 위해 그것을 보충했고, 그는 이제 바깥 범위로 들어갔습니다.
통풍으로 문을 잠그고 안에 아무도 없는 경우(일반 기능 실행), 국소적인 화재가 발생하여 방을 태워버리고(쓰레기 수집기: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이 있습니다.
JavaScript의 함수는 명령어 집합(C 언어)에 대한 참조일 뿐만 아니라 사용하는 모든 비로컬 변수(캡처 변수)에 대한 참조로 구성된 숨겨진 데이터 구조도 포함합니다.이러한 투피스 기능을 클로저라고 합니다.JavaScript의 모든 함수는 종료로 간주할 수 있습니다.
폐쇄는 주(州)와 함께 하는 기능입니다."this"도 함수에 대한 상태를 제공하지만 함수와 "this"는 별도의 객체라는 점에서 "this"와 다소 유사합니다(이것은 단지 고급 파라미터일 뿐이며 함수에 영구적으로 바인딩하는 유일한 방법은 닫힘을 만드는 것입니다)."this"와 함수는 항상 별도로 존재하지만 함수는 닫힘에서 분리될 수 없으며 언어는 캡처된 변수에 액세스할 수 있는 수단을 제공하지 않습니다.
Because all these external variables referenced by a lexically nested function are actually local variables in the chain of its lexically enclosing functions (global variables can be assumed to be local variables of some root function), and every single execution of a function creates new instances of its local variables, it follows that every execution of a function returning (or otherwise transferring it out, such as registering it as a callback) a nested function creates a new closure (with its own potentially unique set of referenced nonlocal variables which represent its execution context).
Also, it must be understood that local variables in JavaScript are created not on the stack frame, but on the heap and destroyed only when no one is referencing them. When a function returns, references to its local variables are decremented, but they can still be non-null if during the current execution they became part of a closure and are still referenced by its lexically nested functions (which can happen only if the references to these nested functions were returned or otherwise transferred to some external code).
An example:
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();
An answer for a six-year-old (assuming he knows what a function is and what a variable is, and what data is):
Functions can return data. One kind of data you can return from a function is another function. When that new function gets returned, all the variables and arguments used in the function that created it don't go away. Instead, that parent function "closes." In other words, nothing can look inside of it and see the variables it used except for the function it returned. That new function has a special ability to look back inside the function that created it and see the data inside of it.
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
Another really simple way to explain it is in terms of scope:
Any time you create a smaller scope inside of a larger scope, the smaller scope will always be able to see what is in the larger scope.
Perhaps a little beyond all but the most precocious of six-year-olds, but a few examples that helped make the concept of closure in JavaScript click for me.
A closure is a function that has access to another function's scope (its variables and functions). The easiest way to create a closure is with a function within a function; the reason being that in JavaScript a function always has access to its containing function’s scope.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
innerFunction();
}
outerFunction();
ALERT: monkey
In the above example, outerFunction is called which in turn calls innerFunction. Note how outerVar is available to innerFunction, evidenced by its correctly alerting the value of outerVar.
Now consider the following:
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
ALERT: monkey
referenceToInnerFunction is set to outerFunction(), which simply returns a reference to innerFunction. When referenceToInnerFunction is called, it returns outerVar. Again, as above, this demonstrates that innerFunction has access to outerVar, a variable of outerFunction. Furthermore, it is interesting to note that it retains this access even after outerFunction has finished executing.
And here is where things get really interesting. If we were to get rid of outerFunction, say set it to null, you might think that referenceToInnerFunction would loose its access to the value of outerVar. But this is not the case.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
outerFunction = null;
alert(referenceToInnerFunction());
ALERT: monkey ALERT: monkey
But how is this so? How can referenceToInnerFunction still know the value of outerVar now that outerFunction has been set to null?
The reason that referenceToInnerFunction can still access the value of outerVar is because when the closure was first created by placing innerFunction inside of outerFunction, innerFunction added a reference to outerFunction’s scope (its variables and functions) to its scope chain. What this means is that innerFunction has a pointer or reference to all of outerFunction’s variables, including outerVar. So even when outerFunction has finished executing, or even if it is deleted or set to null, the variables in its scope, like outerVar, stick around in memory because of the outstanding reference to them on the part of the innerFunction that has been returned to referenceToInnerFunction. To truly release outerVar and the rest of outerFunction’s variables from memory you would have to get rid of this outstanding reference to them, say by setting referenceToInnerFunction to null as well.
//////////
Two other things about closures to note. First, the closure will always have access to the last values of its containing function.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
outerVar = "gorilla";
innerFunction();
}
outerFunction();
ALERT: gorilla
Second, when a closure is created, it retains a reference to all of its enclosing function’s variables and functions; it doesn’t get to pick and choose. And but so, closures should be used sparingly, or at least carefully, as they can be memory intensive; a lot of variables can be kept in memory long after a containing function has finished executing.
I'd simply point them to the Mozilla Closures page. It's the best, most concise and simple explanation of closure basics and practical usage that I've found. It is highly recommended to anyone learning JavaScript.
And yes, I'd even recommend it to a 6-year old -- if the 6-year old is learning about closures, then it's logical they're ready to comprehend the concise and simple explanation provided in the article.
ReferenceURL : https://stackoverflow.com/questions/111102/how-do-javascript-closures-work
'programing' 카테고리의 다른 글
실행 중인 JVM의 매개 변수 가져오기 (0) | 2022.09.14 |
---|---|
C 코드 블록은 왜 곱슬 괄호로 묶습니까? (0) | 2022.09.14 |
포인트별 MySQL MariaDB 그룹 타임스탬프 (0) | 2022.09.14 |
MySQL은 단일 행에서와 같이 최소 열과 최대 열에 대해 일치하는 행을 가져옵니다. (0) | 2022.09.14 |
보기에 대한 외부 키 만들기 (0) | 2022.09.14 |