[자바스크립트] 클로저

트릴로니·2022년 8월 8일

자바스크립트

목록 보기
26/31
  • 자바스크립트 함수는 일급 객체이다. (함수를 변수에 할당할 수 있고 다른 함수에 인수로 전달하거나 함수의 반환값이 함수가 될 수 있다.)
  • lexical scope : JS 엔진은 함수를 실행하기 전에 코드가 어디서 쓰였는지(선언되었는지) 파악해서 함수가 어떤 변수에 접근할 수 있는지 결정한다.

closure is simply that a combination of function and the lexical enviroment from which it was declared
클로저는 함수와 그 함수가 선언되었을 때 렉시컬 환경과의 조합이다.

  • 클로저는 어떤 함수의 인접한 스코프 혹은 환경이 사라지고 나서도 해당 함수의 변수를 쓸 수 있다.
function a() {
	let grandpa = 'grandpa'
    return function b() {
    	let father = 'father'
        let random = 123
        return function c() {
        	let son = 'son'
            return `${grandpa} > ${father} > ${son}`
        } 
    }
}

a()()()
// grandpa > father > son

function c에서 grandpa랑 father에 어떻게 접근할 수 있었을까?
call stack에서 일어나는 일을 한번 따라가보자.
먼저 a 함수가 호출되면 코드를 실행 후 함수 b를 반환하고 call stack에서 제거된다. b 함수도 a 함수와 같은 프로세스를 따른다.
두 함수 모두 call stack에서 제거될 때 실행 컨텍스트가 제거되는 것이므로 실행 컨텍스트의 변수 환경도 사라진다.

  • 함수 호출이 끝나면 가비지 컬렉터에 의해 렉시컬 환경은 메모리에서 제거되어야 하는데 하위 스코프에서 참조하고 있는 변수면 변수면 메모리에서 제거하지 않는다.

  • c함수를 return 할 때 c의 변수환경에서 먼저 변수들을 찾는다. 변수가 없으면 없으면 grobal scope나 global variable에서 찾는 대신에 스코프 체인으로 필요한 변수를 찾는다.

  • 클로저는 렉시컬 스코핑이라고도 불린다. 렉시컬은 코드가 어디서 선언되었는지를 의미하고 스코핑은 어떤 변수에 접근 가능한지를 의미한다.

  • 스코프체인은 함수 호출 시 생성된다. 함수 컨텍스트의 프로퍼티 중 하나이다. 활성 객체와 이 함수의 내부 프로퍼티인 [[Scope]]로 구성되어 있다.

활성 객체: 함수 컨텍스트의 변수 객체는 활성 객체를 가리킨다. 내부 함수, 지역 변수, arguments 객체를 프로퍼티로 갖는다.

[[Scope]]: 함수 객체의 숨김 프로퍼티. 함수의 생성 단계에서 결정된다. 자신의 실행 환경인 Lexical Enviroment와 자신을 포함하는 외부 실행 환경과 전역 객체를 가리킨다.

function boo(string){
	return function(name){
    	return function(name2){
        	console.log(`${string} ${name} ${name2}`)
        }
    }
}
const boo = string => name => name2 => console.log(`${string} ${name} ${name2}`)

const booString = boo('hi')
const booStringName = booString()
  • boo('hi')를 호출하고 return된 함수를 booString에 할당했다.
  • boo가 call stack에 없는 상태여도 booString이 호출될 때 boo의 매개변수로 전달한 'hi'를 기억하고 있는다.

클로저 예제

function callMeMaybe() {
	const callMe = 'Hi! I am now here!'
    setTimeout(function() {
    	console.log(callMe);
    }, 4000)
}

callMeMaybe();
  • setTimeout에 함수 있음 이 함수는 web API로 보내진다. 4초가 지난 뒤에 callback queue에서 대기하고 있다가 callstack이 비어있으면 push된다.
  • callMeMaybe가 pop off되면 이벤트 루프가 setTimeout의 콜백함수를 callstack에 push한다
  • callbe변수는 callmeMaybe가 callstack에서 pop off되었을 때 같이 없어져야 되지만 setTimeout의 콜백함수는 여전히 callMe에 접근할 수 있다
  • web api에서 실행되더라도 클로저가 적용되기 때문에 상위 스코프의 변수를 참조하고 있는 상태면 변수가 메모리에서 제거되지 않는다.
function callMeMaybe() {
    setTimeout(function() {
    	console.log(callMe);
    }, 4000)
    const callMe = 'Hi! I am now here!'
}

callMeMaybe();
  • 위 코드에서 setTimeout의 콜백함수가 callMe변수에 접근할 수 있는 것은 호이스팅 때문이 아니다.

클로저와 메모리

메모리 효율

function heavyDuty(index){
	const bigArray = new Array(7000).fill('hi')
    return bigArray[index]
};

heavyDuty(300);
  • 위 코드와 같이 큰 배열을 생성하는 함수가 있다. 만약 함수가 호출될 때마다 매번 배열을 새로 생성하고 함수의 실행이 끝나면 제거된다면 효율이 매우 떨어지게 된다.
  • 클로저를 이용하면 효율적으로 코드를 바꿀 수 있다.
function heavyDuty2(){
	const bigArray = new Array(7000).fill('hi')
    return function (index){ 
   		returnbigArray[index]
    }
};

const getHeavyDuty = heavyDuty();
getHeavyDuty(100);
getHeavyDuty(200);
getHeavyDuty(300);

encapsulation

const makeNuclearButton = () => {
	let timeWithoutDestruction = 0;
    const passTime = () => timeWithoutDestruction++;
    const totalPeaceTime = () => timeWithoutDestruction
    const launch = () => return {
    	timeWithoutDestruction = -1;
        return 'Boom!'
    };
    setInterval(passTime, 1000);
    return{
    	launch: launch,
        totalPeaceTime: totalPeaceTime
    }
}

const ohno = makeNuclearButton();
  • 외부에 접근하면 안되는 데이터의 경우 클로저를 이용한다

클로저 활용

let view;
function initialize() {
	view = 'hi'
    console.log('view has been set!')
}
initialize() 
initialize() 
initialize() 
  • initialize() : view를 초기화하는 함수
  • 한번만 호출되어야한다는 조건
let view;
function initialize() {
	let called = 0;
    return () => {
    	if(called > 0) return;
     	view = 'hi'
        called++
    	console.log('view has been set!')   
    }                                                  
}
cosnt startOnce = initialize();
startOnce();
const array = [1, 2, 3, 4];
for(var i=0; i<array.length; i++){
 	setTimeout(function(){
    	console.log('I am at index' + i)
    }, 3000)
}
const array = [1, 2, 3, 4];
for(let i=0; i<array.length; i++){
 	setTimeout(function(){
    	console.log('I am at index' + i)
    }, 3000)
}

let allows us to use block scoping so that this block(for 루프) which is these curly brackets create scope for each i
so i is scoped with here

vs
var i => it was part of gloval scope because we didn't really have fucntion surrounding it so that this set time out when it finally returned by that point, for loop has already gone thorugh everything and has turned i into four

const array = [1, 2, 3, 4];
for(var i=0; i<array.length; i++){
	(function() {
    	setTimeout(function(){
    	console.log('I am at index' + i)
    }, 3000)
    })	
}

0개의 댓글