Closure

April 10, 2023

    ES

Closure

자바스크립트 함수는 함수 외부 변수에 접근하여 수정 변경이 가능한데 이 때, 수정가능한 변수가 담긴 렉시컬 환경(외부)의 범위를 클로저라고 한다. 클로저는 또한 외부 변수를 기억하고 그 변수에 접근할 수 있는 함수를 일컫기도 한다.

Lexical Environment

자바스크립트에서 실행중인 함수, 코드블록, 스크립트 전체는 렉시컬 환경(Lexical Environment)을 갖는다. 렉시컬 환경은 두 부분으로 구성되는데 하나는 코드 블록 내부를 표현하는 환경 레코드(Environment Record)로, 모든 지역변수의 프로퍼티를 갖고있고, 외부 렉시컬 환경(Outer Lexical Environment)는 외부 렉시컬 환경에 대한 참조를 담고있다. 따라서 변수는 환경 레코드의 프로퍼티라고 볼 수 있다. 이 때문에 코드 블럭 내부의 변수를 외부 렉시컬 환경에서 불러올 수 없는 것이다. (지역 변수는 전역 환경 레코드에 프로퍼티가 아니며 외부 렉시컬 환경에도 포함되지 않기 때문에 전역 환경에서 지역변수를 불러 올 수 없다.) 다만, 블럭안에서 외부환경의 변수는 불러오는 것은 가능한데, 그 이유는 해당 렉시컬 레코드에서 변수를 찾지 못하면 외부 렉시컬 환경(Outer Lexical Environment)에서 그 변수를 찾으려 하고, 더이상의 외부 렉시컬 환경이 없을 때까지 반복되기 때문이다.

렉시컬 환경과 함수

그런데 이 특성은 함수의 불러올 수 있다는 특징과 만나 흥미로운 결과를 만든다. 함수는 선언될 자신이 선언된 위치의 렉시컬 환경을 [[Environment]]객체에 저장하게 된다. 그 뒤 함수가 실행되면 자신의 렉시컬 환경을 갖게 되는데, 특이한 점은 함수가 어디에서 실행 됐던, 자신이 선언된 위치의 렉시컬 환경을 [[Environment]]에 저장하고 있기 때문에 함수가 실행중인 위치와 전혀 상관없는 다른 위치의 렉시컬 환경에 영향을 받게 되는 것이다.

function makeCounter() let count = 0; return function(){ return ++count; } } let counter = makeCounter();

위 상황에서 return ++count;의 한줄짜리 함수는 선언 될 때, [[Environment]]객체에 그 위체 대한 렉시컬 환경을 담게 된다. 이 후, makeCounter에 의해 만들어진 counter가 만들어 질 때, 함수의 렉시컬 환경을 갖게 된다. 따라서 원칙적으로 전역 환경에서 만들어진 counter함수는 함수 내의 count변수(makeCounter의 환결 레코드의 프로퍼티)에 접근할 수 없지만 makeCounter가 반환한 함수로 만들어진 counter함수는 [[Environment]]객체에 makeCounter의 렉시컬 환경을 담고 있으므로 makeCounter의 환경 레코드의 속성인 변수 count에 접근하며 변경할 수 있게 된다.

화살표함수

화살표 함수가 일반 함수랑 다른점은 environment record에서 온다. 화살표함수의 환경레코드에는 일반 함수의 환경레코드와는 다르게 this, arguments object, super, new.target이 없다.

arguments 객체

일반 함수는 전달인자를 받을때 자신만의 arguments 객체를 만들어 환경 레코드에 저장하는데 반해 화살표함수는 가진만의 arguments 객체를 가지지 못하고 외부 arguments 객체에 접근한다. 이때문에 아래와 같은 일이 생긴다.

function myFunction() { let arrowFunction = () => { console.log(arguments); }; arrowFunction(1, 2, 3); } myFunction(4, 5, 6); // Output: [object Arguments] { 0: 1, 1: 2, 2: 3 }

화살표 함수의 logs가 가르키는 arguments 객체는 자신의 환경 레코드의 객체가 아닌 부모 렉시컬 환경에서 가지고 와야한다. 여기서 부모 렉시컬 환경은 myFunction의 렉시컬 환경이고, myFunctionarrowFunction과 달리 환경 레코드에 자신만의 arguments객체를 가지므로 arrowFunctionmyFunctionarguments객체에 접근하게 되는 것이다.

this

선언식으로 만들어진 매소드의 this는 함수가 불려진 객체를 나타낸다.

let myObject = { prop: 'Hello', myFunction: function() { console.log(this.prop); } }; myObject.myFunction(); // Output: 'Hello'

위 예제에서 myObject객체 안에 선언된 myFunctionthismyFunction 매소드가 호출될 때의 객체를 가르키게 된다. 하지만 화살표 함수로 선언된 매소드는 자신만의 this가 없다. 대신 화살표 함수를 감싸고 있는 함수의 this를 차용한다.

let user = { firstName: "보라", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // 보라

위에서 arrow 함수 안의 thissayHi함수의 this가 가르키는 객체를 사용한다.

객체 안의 화살표 함수.

let myObject = { prop: 'Hello', regularFunction: function() { console.log(this.prop); }, arrowFunction: () => { console.log(this.prop); } }; myObject.regularFunction(); // Output: 'Hello' myObject.arrowFunction(); // Output: undefined

객체의 환경 레코드에는 this가 없다.

함수안의 객체안의 화살표 함수.

let obj = { name: 'kim', myFunction() { let myObject = { prop: 'Hello', arrowFunction: () => { console.log(this.name); } }; myObject.arrowFunction(); } } obj.myFunction(); // Output: kim

화살표 함수를 감싸는 myFunction함수의 this를 참조함.

Scope

함수의 스코프란 함수가 접근가능한 환경레코드의 집합이라고 할 수 있다. 따라서 스코프는 렉시컬환경과는 약간 다른 개념이다. 위에서 설명 했듯이 함수가 선언될 때, 렉시컬환경의 범위는 내부 환경을 대변하는 환경레코드와 외부렉시컬환경의 참조로 이뤄지지만 만약 함수가 외부 렉시컬환경에서도 변수를 찾기 못하면 함수는 외부렉시컬환경의 외부렉시컬환경으로 변수를 찾아 나서기 때문이다. 이와같은 이유 때문에 스코프의 범위는 함수가 갖는 렉시컬환경 그 이상으로 확대된다.