Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

후레임의 프로그래밍

JavaScript closures는 어떻게 작동합니까? 본문

스택오버플로우(Stack Overflow)

JavaScript closures는 어떻게 작동합니까?

후레임 2020. 10. 26. 00:47
질문

자바 스크립트 클로저로 구성된 개념 (예 : 함수, 변수 등)을 알고 있지만 클로저 자체를 이해하지 못하는 사람에게 어떻게 설명 하시겠습니까?

위키 백과에 제공된 체계 예 를 보았지만 안타깝게도 그랬습니다. 도움이되지 않습니다.



답변

closures는 다음의 쌍입니다.

  1. 함수 및
  2. 해당 함수의 외부 범위에 대한 참조 (어휘 환경)

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

자바 스크립트의 모든 함수는 외부 어휘 환경에 대한 참조를 유지합니다. 이 참조는 함수가 호출 될 때 생성되는 실행 컨텍스트를 구성하는 데 사용됩니다. 이 참조를 사용하면 함수 내부의 코드가 함수가 호출되는시기와 위치에 관계없이 함수 외부에서 선언 된 변수를 "볼"수 있습니다.

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

다음 코드에서 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 (…) ( 함수 생성자 )는 어휘 환경에서 닫히지 않고 대신 전역 컨텍스트에서 닫힙니다. 새 함수는 외부 함수의 지역 변수를 참조 할 수 없습니다.
  • 자바 스크립트의 클로저는 함수 선언 시점에 범위에 대한 참조 (사본이 아님)를 유지하는 것과 같습니다. 그러면 외부 범위에 대한 참조가 유지됩니다. 스코프 체인의 맨 위에있는 전역 개체로가는 길
  • 함수가 선언되면 클로저가 생성됩니다. 이 클로저는 함수가 호출 될 때 실행 컨텍스트를 구성하는 데 사용됩니다.
  • 함수를 호출 할 때마다 새로운 지역 변수 집합이 생성됩니다.

링크



출처 : http://stackoverflow.com/questions/111102