자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것을 의미한다. 다르게 표현하면 함수와 그 함수가 선언된 어휘적 환경의 조합이다. 다음 예시를 살펴보자.
가만 보면 뭔가 이상~하다. MakeFunc함수가 실행되면서 지역변수 name을 만들었다. 그리고 실행 중 만든 함수 displayName에서 name을 참조하는 건 이상하지 않다. 근데 이 displayName을 리턴한 이후에도 계속해서 name을 참조하는 듯한 현상이 발생한다! MakeFunc의 실행이 끝나면 지역변수였던 name에 더 이상 접근할 수 없으리라 예상하는 것이 당연한 수순같지만 그렇지 않은 것.
이는 바로 자바스크립트가 클로저라는 것을 형성하기 때문이다. 이렇게 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할수 있는 것을 클로저라고 한다. 위 예시의 경우 myFunc는 MakeFunc가 실행될 때 생성된 displayName이란 함수의 인스턴스에 대한 참조인데, displayName의 인스턴스가 변수 name과 함께 살았던(?) 때의 환경을 유지하기 때문에 이런 현상이 가능한 것. 당연한 소리지만 name변수가 자체가 안 죽고 살아 숨쉬는 것이 아니므로 console.log(name)을 하면 에러가 난다.
좀 더 흥미로운 예제를 살펴보자.
실로 골때리는 예제가 아닐 수 없다. 그와 동시에 재밌는 예제이다. 단일인자 한 개를 받아 새 함수를 리턴하는 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억지 땡깡을 막을 수 있는 것.
파일들을 기능별로 분리하는 작업을 모듈화라고 하고, 각각의 파일들을 모듈이라고 부른다. 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로 쓰는 건 의미가 없기도 하고