후레임의 프로그래밍
JavaScript closures는 어떻게 작동합니까? 본문
질문
자바 스크립트 클로저로 구성된 개념 (예 : 함수, 변수 등)을 알고 있지만 클로저 자체를 이해하지 못하는 사람에게 어떻게 설명 하시겠습니까?
위키 백과에 제공된 체계 예 를 보았지만 안타깝게도 그랬습니다. 도움이되지 않습니다.
답변
closures는 다음의 쌍입니다.
- 함수 및
- 해당 함수의 외부 범위에 대한 참조 (어휘 환경)
어휘 환경은 모든 실행 컨텍스트 (스택 프레임)의 일부이며 식별자 (예 : 지역 변수 이름)와 값 사이의 맵입니다.
자바 스크립트의 모든 함수는 외부 어휘 환경에 대한 참조를 유지합니다. 이 참조는 함수가 호출 될 때 생성되는 실행 컨텍스트를 구성하는 데 사용됩니다. 이 참조를 사용하면 함수 내부의 코드가 함수가 호출되는시기와 위치에 관계없이 함수 외부에서 선언 된 변수를 "볼"수 있습니다.
함수가 다른 함수에 의해 호출 된 함수가 호출되면 외부 어휘 환경에 대한 참조 체인이 생성됩니다. 이 체인을 범위 체인이라고합니다.
다음 코드에서 inner
는 foo
가 호출 될 때 생성 된 실행 컨텍스트의 어휘 환경으로 클로저를 형성하고 변수를 클로징합니다. 비밀
:
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의 함수는 변수 (일급 함수)처럼 전달 될 수 있습니다. 즉, 이러한 기능과 상태의 쌍이 프로그램 주위에 전달 될 수 있습니다. C ++에서 클래스의 인스턴스를 전달하는 것과 비슷합니다. .
자바 스크립트에 클로저가 없으면 더 많은 상태가 함수간에 명시 적으로 전달되어야하므로 매개 변수 목록이 더 길어지고 코드 잡음이 더 커집니다.
따라서 함수가 항상 비공개 상태에 액세스하도록하려면 클로저를 사용할 수 있습니다.
... 그리고 자주 우리는 상태를 함수와 연관시키고 자 합니다. 예를 들어 Java 또는 C ++에서 전용 인스턴스 변수와 메서드를 클래스에 추가하면 상태를 기능과 연결하게됩니다.
C 및 대부분의 다른 일반적인 언어에서 함수가 반환 된 후 스택 프레임이 파괴되어 모든 지역 변수에 더 이상 액세스 할 수 없습니다. JavaScript에서 다른 함수 내에서 함수를 선언하면 외부 함수의 로컬 변수가 반환 된 후에도 계속 액세스 할 수 있습니다. 이러한 방식으로 위 코드에서 secret
은 foo에서 반환 된 이후 함수 객체
inner
에서 계속 사용할 수 있습니다.
closures 사용
closures는 함수와 관련된 비공개 상태가 필요할 때 유용합니다. 이것은 매우 일반적인 시나리오입니다. 기억하세요. JavaScript에는 2015 년까지 클래스 구문이 없었고 여전히 private 필드 구문도 없습니다. closures는 이러한 요구를 충족합니다.
비공개 인스턴스 변수
다음 코드에서 함수 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
함수는 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 = function() { console.log(x) }
x = x+1
return inner
}
var f = foo()
f() // 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
를 사용하여 선언 된 변수는 호이스트됩니다. 이것은 let
및 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]()
}
정리 :
- 자바 스크립트에서 함수가 선언 될 때마다 클로저가 생성됩니다.
- 외부 함수가 실행을 완료 한 후에도 외부 함수 내부의 상태를 반환 된 내부 함수에서 암시 적으로 사용할 수 있기 때문에 다른 함수 내부에서
함수
를 반환하는 것이 클로저의 고전적인 예입니다. - 함수 내에서
eval ()
을 사용할 때마다 클로저가 사용됩니다.eval
텍스트는 함수의 지역 변수를 참조 할 수 있으며 엄격하지 않은 모드에서는eval ( 'var foo =…')
을 사용하여 새 지역 변수를 만들 수도 있습니다. . new Function (…)
( 함수 생성자 )는 어휘 환경에서 닫히지 않고 대신 전역 컨텍스트에서 닫힙니다. 새 함수는 외부 함수의 지역 변수를 참조 할 수 없습니다.- 자바 스크립트의 클로저는 함수 선언 시점에 범위에 대한 참조 (사본이 아님)를 유지하는 것과 같습니다. 그러면 외부 범위에 대한 참조가 유지됩니다. 스코프 체인의 맨 위에있는 전역 개체로가는 길
- 함수가 선언되면 클로저가 생성됩니다. 이 클로저는 함수가 호출 될 때 실행 컨텍스트를 구성하는 데 사용됩니다.
- 함수를 호출 할 때마다 새로운 지역 변수 집합이 생성됩니다.
링크
- Douglas Crockford가 클로저를 사용하여 객체에 대해 시뮬레이션 한 비공개 속성 및 비공개 메서드 .
- 종료가 어떻게 메모리 누수를 유발할 수 있는지에 대한 훌륭한 설명 IE에서 주의하지 않으면
- 자바 스크립트 클로저 에 대한 MDN 문서
'스택오버플로우(Stack Overflow)' 카테고리의 다른 글
"git pull"이 로컬 파일을 덮어 쓰도록하려면 어떻게해야합니까? (0) | 2020.10.26 |
---|---|
Git 저장소를 이전 커밋으로 되돌리려면 어떻게해야합니까? (0) | 2020.10.26 |
푸시되지 않은 기존 커밋 메시지를 수정하는 방법은 무엇입니까? (0) | 2020.10.26 |
JavaScript에서 "use strict"은 무엇을 하며 그 이유는 무엇입니까? (0) | 2020.10.26 |
"yield"키워드의 기능은 무엇입니까? (0) | 2020.10.26 |