클로저(Closure)

자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것을 의미한다. 다르게 표현하면 함수와 그 함수가 선언된 어휘적 환경의 조합이다. 다음 예시를 살펴보자.

출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

가만 보면 뭔가 이상~하다. MakeFunc함수가 실행되면서 지역변수 name을 만들었다. 그리고 실행 중 만든 함수 displayName에서 name을 참조하는 건 이상하지 않다. 근데 이 displayName을 리턴한 이후에도 계속해서 name을 참조하는 듯한 현상이 발생한다! MakeFunc의 실행이 끝나면 지역변수였던 name에 더 이상 접근할 수 없으리라 예상하는 것이 당연한 수순같지만 그렇지 않은 것.

 

이는 바로 자바스크립트가 클로저라는 것을 형성하기 때문이다. 이렇게 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할수 있는 것을 클로저라고 한다. 위 예시의 경우 myFunc는 MakeFunc가 실행될 때 생성된 displayName이란 함수의 인스턴스에 대한 참조인데, displayName의 인스턴스가 변수 name과 함께 살았던(?) 때의 환경을 유지하기 때문에 이런 현상이 가능한 것. 당연한 소리지만 name변수가 자체가 안 죽고 살아 숨쉬는 것이 아니므로 console.log(name)을 하면 에러가 난다. 

 

좀 더 흥미로운 예제를 살펴보자. 

출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

실로 골때리는 예제가 아닐 수 없다. 그와 동시에 재밌는 예제이다. 단일인자 한 개를 받아 새 함수를 리턴하는 makeAdder를 정의했는데, 이 새로 정의된 함수도 단일인자를 받아서 x, y, z의 합을 리턴한다. 중요한 것은 이 예제가 클로저를 형성한다는 것! add5는 5를 인자로 makeAdder를 호출해 결국 x = 5, y = 100이란 클로저를 이룬 상태에서 x + y + z를 리턴하는 함수 인스턴스를 참조하는 녀석이고, add10은 10을 인자로 makeAdder를 호출해 x = 10, y = 100이란 클로저를 이룬 상태에서 x + y + z를 리턴하는 함수 인스턴스를 참조하는 녀석이다. 

 


클로저 활용 - 캡슐화

그럼 이 클로저를 활용하는 예시를 살펴보자. 바로 객체 프로퍼티 등의 캡슐화에 이를 이용할 수 있다. 바로 다음처럼.

createUser함수 내부에서 user객체가 생성될 때 _email이란 지역변수를 참조할 수 있던 클로저를 형성한 채로 있기 때문에 user1._email처럼 직접 접근해 수정할려고 하는 dog억지 땡깡을 막을 수 있는 것.

 

footer와 그 위 요소간에 위처럼 선을 줄려고 하는 상황.

footer에게 border-top으로 선을 주거나 그 위 태그한테 border-bottom을 줘서 선을 만들어야지~하고 생각했는데 그렇게 하면

앗! 이렇게 스크롤이 생겨버린다!!

화면 꽉 채울려고 body, html태그들한테 height: 100%멕이고 body에 기본으로 있는 margin도 없앴는데 이런 억지스크롤이 왜 생기지? 했는데 알고 보니까 border에 선을 줬기 때문..

 

border두께도 전체 높이에 영향을 준다. 그래서 손톱만큼 border를 넣어줘도 억지스크롤이 생김. 

암튼 해결책은 box-shadow! 이걸로 선을 만든 효과를 주면 스크롤이 생기지 않는다.

box shadow 사용법은 https://www.codingfactory.net/10628 참고

근데 저 글에서 y-position이 음수면 위쪽에 만들어진다고 써있는데 양수여야 위쪽에 만들어진다. 이점도 참고~

파일들을 기능별로 분리하는 작업을 모듈화라고 하고, 각각의 파일들을 모듈이라고 부른다. ES6부터 이 모듈화를 할 수 있는 문법이 등장했는데 그것이 바로 export와 import이다. 어떻게 쓰는지 알아보자.

 

모듈 스코프

모듈이 되는 파일들은 독립적인 스코프를 가져야 하며, 모듈 파일이 갖는 스코프를 모듈 스코프라고 한다. 이는 모듈 파일 안에서 선언된 변수나 함수 등은 그 파일 안에서만 사용 가능해야 한다는 것을 의미한다. 일반적으로 HTML문서에서 <script>태그로 단순히 js파일을 불러오면 모듈 스코프가 적용되지 않지만, <script>태그에 type="module"이란 속성값을 주면 그 파일에 모듈 스코프를 줄 수 있다. 이 속성값을 통해 브라우저가 해당 스크립트가 모듈이란 것을 인식한다고 함.

참고 : 모듈은 로컬 파일에서 동작하지 않고, HTTP 또는 HTTPS 프로토콜을 통해서만 동작.
웹 서버 등을 통해 열어야지만 모듈이 정상적으로 동작함(vscode의 경우 live server등을 통해 이용 가능)

 

export, import

모듈스코프를 갖고 있으면 다른 파일에서 그 모듈 파일의 변수&함수 등을 쓸 수가 없음. 이 경우 특정 문법을 통해 모듈 파일의 변수나 함수를 사용 가능한데, 이를 가능하게 하는 것이 export이다. 이 export키워드를 통해서 모듈 파일에서 "너는 다른 데서도 쓸 수 있게 해줄게"라고 지정해주는 것이 가능한 것임. 그러나 단순히 export했다고 해서 다른 파일에서 아무런 제한없이 막 export된 애들을 쓸 수 있는 게 아니라, export된 변수나 함수 등을 쓰고자 하는 파일에서 import를 통해 한 번 더 불러오는 과정이 필요하다.

 

간단한 예시를 보이겠다.

a.js에서 export키워드를 통해 title변수와 func라는 함수를 export 즉 외부에서 쓸 수 있도록 지정했다. 이 export키워드가 붙지 않았다면 외부에서 쓸 수 없다. 이제 다른 js파일, 예를 들면 b.js에서 다음과 같이 import해서 이들을 쓸 수 있다.

title과 func를 a.js로부터 갖고 와 쓰겠다! 라는 의미로 해석하면 된다. 즉 a.js에서 export된 것들 중 내가 쓰길 원하는 애들만 따로 골라 그 목록을 중괄호 {}안에 써서 갖고 올 수 있다는 의미. 객체에서의 destructuring문법과 비슷하다고 볼 수 있다.

 

방금 전에선 export할 때 내보내고 싶은 변수나 함수 앞에 하나하나 export키워드를 붙이는 방법들을 살펴봤다. 근데 사실, export는 다음과 같은 방법들로도 사용 가능하다.

즉 export { name1, name2, ..., nameN}의 방법을 쓰면 내보낼 변수나 함수 앞에 하나하나 export를 붙일 필요 없이 한꺼번에 export할 수 있고, as키워드를 통해 이름을 바꿔서 내보낼 수도 있다는 것이다.

 

import는 다음과 같은 방법으로도 가능하다.

import할 모듈에서 export된 애들을 내가 원하는 이름의 객체로 바인딩해 가져올 수도 있고, export할 때 as로 이름 바꿔주던 것처럼 import할때도 이름 바꿔서 가져올 수도 있다. 그리고 import만 하면 (세 번째 방법) export된 애들 모두를 export된 이름으로 쓸 수 있게 되는 줄 알았으나 그게 아니었다. 단순히 그 특정 모듈 파일을 가져와 한 번 실행하고 끝나는 듯하다. 

 

음..함수에서 인자로 객체를 넘기는 상황에선, 함수 작성시 파라미터로 destructuring문법을 쓰면 인자로 받는 객체에서 원하는 property값들만 가져올 수 있었다. export한다는 것도 export하는 애들을 객체 형태로 묶어서 전달하는 것이고, 그렇기 때문에 import할때 destructuring문법처럼 원하는 애들만 가져올 수 있는 게 아닌가 생각한다. 이건 그냥 본인 뇌피셜임.

 

 

Named Exports, Default Exports

방금 전과 같이 여러 대상을 export하는 방식을 Named Exports라고 한다. 그러나 default키워드를 통해 하나의 대상만을 export하는 것도 가능한데, 이를 Default Export라고 한다.

 

모듈파일 내에서 default키워드는 딱 한 번만 사용 가능하며, export뒤에 default가 붙으면 반드시 하나의 대상만 내보낼 수 있게 된다. import하는 파일에서는 이렇게 내보내진 값을 default로 받을 수 있는데 as키워드를 통해 이름을 붙여서 받아야 한다. 

근데, 이 Default export방식의 장점은 import할 때 default값에 붙여준 이름을 중괄호 밖으로 빼주어 더 편하게 작성할 수 있다는 점이다. 즉 b.js에서 다음과 같은 방식으로 import가 가능해진다. 

즉 중괄호 여부로 default exports와 named exports를 구분할 수 있는 것. 

참고로 default exports와 named exports는 함께 사용 가능하다.

근데 이렇게 같이 섞어 쓰면 난잡하니까..보통 export할 대상이 여러 개면 named방식을, 한개일때만 default방식을 사용한다.

javascript에서 함수 작성 시 작성한 parameter의 수보다 더 많은 arguments를 써도 된다. 파라미터는 3개 받지만 실제 호출 시 인자들을 5개 7개 이렇게 써도 문제가 되지 않는다는 의미. 그러나 각 인자와 파라미터가 순서대로 매칭되고 남은 인자들은 낙동강 오리알 신세가 된다. 

 

그.러.나 우리의 자바스크립트는 친절하게도 함수 내에서 인자들로 받은 값들을 따로 다룰 수 있는 방법이 존재한다. 바로 arguments객체이다. 이 객체엔 우리가 인자로 전달한 값들이 유사배열 형태(즉 배열과 관련된 메소드는 못 씀)로 모조리 담겨있다. 낙동강 오리알 신세가 된 인자들까지 이걸로 다룰 수 있다. 

 

그러나 argument객체를 쓰는 방법은 유사배열을 활용하는 방법이라 아쉽게도 배열의 메소드를 쓰지는 못한다는 단점이 있다. ES6부터는 이런 점을 보완하는 방법으로 Rest Parameter를 지원한다. 특정 파라미터 앞에 ...을 붙인 것을 Rest parameter라고 부르는 것. 우선 다음을 보자.

동작 자체는 결국 방금 살펴본 arguments객체를 활용하는 방법과 다를 게 없어보인다. 그러나 위 방법은 내가 전달한 kim ~ cho까지의 인자들을 모두 args라는 파라미터로 받은 형태이다. 정확히는 배열 형태로. Rest Parameter는 그 이름답게 나머지 인자들을 모두 받는다는 점에서 강력한 힘을 가진다. 즉 다음과 같이 활용 가능

참고로 Rest Parameter는 나머지 인자들을 모두 자기가 가져가는 양심없는 놈이기 때문에..파라미터 작성 시 마지막 파라미터를 Rest로 해주는 것이 좋다. 맨 마지막 놈이 아닌데 Rest로 쓰는 건 의미가 없기도 하고

 

 

길이가 같은 두 배열 A, B에 대해

S = A의 같은 인덱스항들의 곱들의 합

으로 정의할 때, B를 건드리지 않고 A의 순서만 건드려서 S의 최솟값을 찾는 문제.

 

무식하게 풀려면 A를 나열하는 모든 경우의 수 = N!개에 대해 따져야 하니 타임아웃.

단순히 A만을 오름차순 / 내림차순 하면 어떨까라고 생각해봤으나 B가 어떤 순서로 주어지는지 알 길이 없기 때문에 이것 역시 적절한 방법이 아니었다.

 

이에 대한 해결은 A는 오름차순, B는 내림차순으로 정렬해 각 항의 곱들의 합을 구하는 것.

곱해서 나오는 값들의 수를 최대한 작게 나오게 하는 게 관건이므로 A의 가장 작은 값을 B의 가장 큰 값과 짝지어주고, 또다시 남은 A의 가장 작은 값을 남은 B의 가장 큰 값과 짝짓는 과정을 반복해야 하므로 B도 내림차순으로 정렬할 필요가 있다.

 

뭐 정말 나는 B를 건드리기 싫다! 하면 A만 정렬하고 매번 B의 max값을 찾아 매치해줄 수 있지만 굳이 그럴 필요 없이 B도 정렬해주면 될 것 같다는 생각에서 했다.


정당성 증명 - 왜 가장 작은 값과 큰 값을 매칭해야 하는가

B를 내림차순으로 일단 한 상황에서, A를 왜 가장 작은 값부터 매칭시켜줘야 하는가. 

a1과 a2, b1과 b2라는 수가 있고 a1 >= a2, b1 > b2라고 하자. 이 때 주어진 논제를 귀류법으로 증명하기 위해

a2b1 + a1b2이 a1b1 + a2b2보다 작다고 해보자. 우리가 원하는 건 a1b1 + a2b2 >= a2b1 + a1b2임을 보이는 것이다.

뭐 복잡하게 생각할 것 없이, 그냥 이항해서 정리하면 (a1 - a2)b1 >= (a1 - a2)b2임을 보여야 하는 상황이 되는데, 양변을 (a1 - a2)로 나누면 b1 >= b2가 되는데 문제의 조건상 b1 > b2이므로 참이다. 이로써 증명 끝.

 

곱의 합을 구할 땐 가장 큰 값끼리 곱해준 게 제일 큰 답이 나온다.

+ Recent posts