ECMAScript 6 Module로 코드 캡슐화하기
JavaScript의 “모든 공유” 방식의 코드 로드는 JavaScript를 오류가 발생하기 쉬운 가장 혼란스러운 언어 중 하나로 만듭니다. 다른 언어에서는 패키지와 같은 개념을 사용하여 코드 범위를 정의하지만 ECMAScript 6 이전에는 응용 프로그램의 모든 JavaScript 파일에 정의된 모든 것이 하나의 전역 Scope을 공유했습니다. 웹 애플리케이션이 더욱 복잡해지고 JavaScript 코드가 더많이 사용됨에 따라 이러한 접근 방식은 이름 충돌 및 보안 문제와 같은 문제를 야기했습니다. ECMAScript 6의 한가지 목표는 Scope 문제를 해결하고 JavaScript 응용 프로그램에 순서를 지정하는 것이 었습니다. 이것이 Module이 도입된 이유입니다.
Module은 무엇일까요?
Module은 다른 모드로 로드되는 JavaScript 파일입니다 (Script는 JavaScript가 작동하는 원래 방식으로 로드 됨). Module은 Script와 매우 다른 의미를 가지고 있기 때문에 다른 모드가 필요합니다.
- Module 코드는 자동으로 strict 모드에서 실행되며 strict 모드를 거부 할 방법이 없습니다.
- Module의 최상위 레벨에서 작성된 변수는 공유된 전역 범위에 자동으로 추가되지 않습니다. Module의 최상위 범위에만 존재합니다.
- Module의 최상위 레벨에있는
this
의 값은 `undefined ‘입니다. - Module은 코드 내에서 HTML 스타일의 주석을 허용하지 않습니다 (JavaScript의 초기 브라우저 시절 남은 기능).
- Module은 Module 외부에서 사용할 수 있어야하는 모든 것을 export 해야합니다.
- Module은 다른 Module에서 바인딩을 가져올 수 있습니다.
이 차이는 언뜻보기에 작게 보일 수 있지만 JavaScript 코드가 로드되고 평가되는 방식에 중요한 변화를 나타냅니다. 이것은 이장 전체에서 논의 할 것입니다. Module의 진정한 힘은 파일의 모든 것보다는 필요한 바인딩만 내보내고 가져 오는 기능입니다. Module이 scripts와 어떻게 다른지 이해하기 위해서는 내보내기(export)와 가져 오기(import)를 잘 이해해야합니다.
export 기초
export
키워드를 사용하여 코드의 일부를 다른 Module에 노출할 수 있습니다. 가장 단순한 경우에, 변수, 함수 또는 클래스 선언 앞에export
를 두어 다음과 같이 Module에서 내보낼 수 있습니다.
|
|
이 예에서 주의해야 할 몇가지 사항이 있습니다. 첫째, export
키워드를 제외하고 모든 선언은 다른 경우와 완전히 동일합니다. 내보낸 각 함수 또는 클래스에는 이름이 있습니다. 왜냐하면 내보낸 함수와 클래스 선언에는 이름이 필요하기 때문입니다. default
키워드를 사용하지 않는 이상 이구문을 사용하여 익명 함수나 클래스를 내보낼 수 없습니다 (“Module의 Default값” 섹션 참조).
다음으로, 정의되었을 때 export
하지 않은 multiply()
함수를 생각해 보겠습니다. 이는 항상 선언에서 export
할 필요가 없기 때문에 효과적입니다. 참조를 export
할 수도 있습니다. 마지막으로 이 예제에서는 subtract()
함수를 export
하지 않습니다. 명시적으로 export
하지 않은 변수, 함수 또는 클래스는 Module에 비공개로 남아 있기 때문에 이 함수는 이 Module 외부에서 액세스할 수 없습니다.
import 기초
export
가 있는 Module이 있으면 import
키워드를 사용하여 다른 Module의 기능에 액세스할 수 있습니다. import
문의 두 부분은 가져올 식별자와 그 식별자를 가져올 Module입니다. 아래는 문장의 기본형태입니다.
|
|
import
뒤의 중괄호는 주어진 Module로부터 가져올 바인딩을 나타냅니다. 키워드 from
은 지정된 바인딩을 가져올 Module을 나타냅니다. Module은 경로를 나타내는 문자열로 지정됩니다 (Module 지정자라고 함). 브라우저는 <script>
요소에 전달할 동일한 경로 형식을 사용합니다. 즉, 파일 확장명을 포함해야합니다. 반대로, Node.js는 파일 시스템 접두사를 기반으로 로컬 파일과 패키지를 구별하는 전통적인 규칙을 따릅니다. 예를 들어 example
은 패키지이고 ./example.js
는 로컬 파일입니다.
import
할 바인딩 목록은 Destructured 객체와 유사하지만 보이지는 않습니다.
Module로부터 바인딩을 import
할 때 바인딩은 const
를 사용하여 정의된 것처럼 동작합니다. 즉, 동일한 이름의 다른 변수를 정의할 수 없으며 (같은 이름의 다른 바인딩을 가져 오는 것을 포함하여), import
문 앞에 식별자를 사용하거나 값을 변경할 수 없다는 뜻입니다.
단일 바인딩 import
“export 기초” 섹션의 첫 번째 예제가 파일 example.js
의 Module에 있다고 가정합니다. 여러 가지 방법으로 해당 Module에서 바인딩을 가져오고 사용할 수 있습니다. 예를 들어 하나의 식별자만 가져올 수 있습니다.
|
|
example.js
는 하나 이상의 함수를 export
하지만, 이 예제는 sum()
함수만 import
합니다. sum
에 새로운 값을 할당하려고하면, 가져온 바인딩을 재할당할 수 없기 때문에 결과는 오류입니다.
브라우저와 Node.js간에 최상의 호환성을 위해
import
하는 파일의 시작 부분에/
,./
또는../
을 포함시킵니다.
여러 바인딩 import
예제 Module에서 여러 바인딩을 import
하기위해 다음과 같이 명시적으로 나열할 수 있습니다.
|
|
여기 예제 Module에서 sum
, multiply
, magicNumber
의 세 가지 바인딩을 import
합니다. 그런 다음 로컬에 정의된 것처럼 사용합니다.
Module에서 모두 import
또한 Module의 전체를 단일 객체로 import
할 수있는 특별한 경우가 있습니다. 그런 다음 해당 객체에서 export
한 모든 것을 프로퍼티로로 사용할 수 있습니다.
|
|
이 코드에서,example.js
에 있는 export
한 모든 바인딩은 example
이라는 객체에 로드됩니다. 그러면 이름이 지정된 export
(sum()
함수, multiple()
함수 및 magicNumber
)는 example
의 프로퍼티로 액세스할 수 있습니다. 이 import
형식을 namespace import라고합니다. 왜냐하면 example
객체는 example.js
파일안에 존재하지 않고 example.js
의 export
한 모든 멤버들에 대한 네임 스페이스 객체로 사용되기 위해 생성되기 때문입니다.
그러나 import
문에서 Module을 몇번이나 사용하더라도 Module은 한번만 실행됩니다. Module을 import
한 코드가 실행된 후에, 인스턴스화된 Module은 메모리에 유지되고 다른 import
문이 그것을 참조할 때마다 재사용됩니다. 다음을 살펴보겠습니다.
|
|
이 Module에는 import
문이 세개 있지만 example.js
는 한번만 실행됩니다. 동일한 애플리케이션의 다른 Module이 example.js
에서 바인딩을 import
하는 경우 이 Module은 이 코드에서 사용하는 것과 동일한 Module 인스턴스를 사용합니다.
Module 구문 제한 사항
export
와 import
의 중요한 제한 사항은 구문과 함수 밖에서 사용해야한다는 것입니다. 예를 들어 이 코드는 구문 오류를 발생시킵니다.
|
|
export
문장이 if
문 안에 있습니다. 이것은 허용되지 않습니다. export
는 어떤 방식으로든 조건부 이거나 동적일 수 없습니다. Module 구문이 존재하는 한가지 이유는 JavaScript 엔진이 export
하는 것을 정적으로 결정하게 하기 위해서입니다. 따라서 Module의 최상위 레벨에서만 export
를 사용할 수 있습니다.
마찬가지로 명령문 내부에서는 import
를 사용할 수 없습니다. 최상위 레벨에서만 사용할 수 있습니다. 즉, 아래 코드는 구문 오류입니다.
|
|
동적으로 바인딩을 export
할 수 없는 것과 같은 이유로 동적으로 바인딩을 import
할 수 없습니다. export
와 import
키워드는 텍스트 편집기와 같은 도구가 Module에서 어떤 정보를 사용할 수 있는지 쉽게 알수있도록 정적으로 설계되었습니다.
import한 바인딩의 미묘한 특성
ECMAScript 6의 import
문은 일반 변수와 같이 원래 바인딩을 단순히 참조하는 것이 아니라 변수, 함수 및 클래스에 대한 읽기 전용 바인딩을 만듭니다. 바인딩을 import
하는 Module은 값을 변경할 수 없지만 해당 식별자를 export
하는 Module은 값을 변경할 수 있습니다. 예를 들어, 이 Module을 사용한다고 가정 해보겠습니다.
|
|
이 두 바인딩을 import
하면 setName()
함수는 name
의 값을 바꿀 수 있습니다.
|
|
setName("Greg")
에 대한 호출은 setName()
을 export
한 Module로 돌아가서 실행이 되고 name
을 "Greg"
로 설정합니다. 이 변경은 import
한 name
바인딩에 자동으로 반영됩니다. name
은 export
한 name
식별자의 로컬 이름이기 때문입니다. 위의 코드에서 사용된 name
과 import
한 Module에서 사용된 name
은 같지 않습니다.
이름을 변경하여 export와 import
때로는 Module에서 import
한 변수, 함수 또는 클래스의 원래 이름을 사용하지 않을 수도 있습니다. 다행히도 export
및 import
하는 동안 export
의 이름을 변경할 수 있습니다.
첫번째 경우, 다른 이름으로 export
하려는 함수가 있다고 가정합니다. as
키워드를 사용하여 함수가 Module 외부로 알려진 이름을 지정할 수 있습니다.
|
|
여기서 sum()
함수(sum
은 로컬 이름입니다)는 add()
(add
는 export
한 이름입니다)로 export
됩니다. 즉, 다른 Module이 이 함수를 import
하기 원할 때 sum
대신 add
라는 이름을 사용해야 합니다.
|
|
함수를 import
하는 Module이 다른 이름을 사용하고 싶다면, as
를 사용할 수도 있습니다.
|
|
이 코드는 import name
을 사용하여 add()
함수를 import
해서 sum()
(로컬 이름)으로 이름을 바꿉니다. 즉, 이 Module에는 add
라는 이름의 식별자가 없습니다.
Module의 Default 값
Module 구문은 실제로 Module에서 Default값을 export
하고 import
하는데 최적화되어 있습니다. 이 패턴은 CommonJS(브라우저 외부에서 JavaScript를 사용하기위한 또 다른 사양)와 같이 다른 Module 시스템에서 상당히 일반적이었습니다. Module의 Default값은 default
키워드로 지정된 단일 변수, 함수 또는 클래스이며 Module 당 하나의 Default export
만 설정할 수 있습니다. default
키워드를 다중 export
와 함께 사용하는 것은 구문 오류입니다.
Default 값 export
다음은default
키워드를 사용하는 간단한 예제입니다.
|
|
이 Module은 함수를 Default값으로 export
합니다. default
키워드는 이것이 Default export
임을 나타냅니다. 이 함수는 Module 자체가 함수를 나타내기 때문에 이름을 요구하지 않습니다.
다음과 같이 export default
다음에 식별자를 배치하여 식별자를 Default export
로 지정할 수도 있습니다.
|
|
여기에서 sum()
함수가 먼저 정의되고 나중에 Module의 Default값으로 export
됩니다. Default값으로 계산을 사용해야하는 경우 이방법을 선택할 수 있습니다.
식별자를 Default export
로 지정하는 세 번째 방법은 다음과 같이 이름 바꾸기 구문을 사용하는 것입니다.
|
|
식별자 default
는 이름 바꿔서 export
에서 특별한 의미를 가지며 값이 Module의 Default값이어야 함을 나타냅니다. default
는 JavaScript에서 키워드이기 때문에 변수, 함수 또는 클래스 이름으로 사용할 수 없습니다 (프로퍼티 이름으로 사용할 수 있음). 그래서 export
의 이름을 바꾸기 위해 default
를 사용하는 것은 Default값이 아닌 export
에 정의된 방법과 일관성을 유지하는 특별한 경우입니다. 이 구문은 단일 export
문을 사용하여 Default값을 포함한 여러 export
를 동시에 지정하려는 경우 유용합니다.
Default 값 import
다음 구문을 사용하여 Module에서 Default값을 import
할 수 있습니다.
|
|
이 import
명령문은 Module example.js
에서 Default값을 import
합니다. Default가 아닌 import
에서 볼 수있는 것과는 달리 중괄호는 사용되지 않습니다. 로컬 이름 sum
은 Module이 export
하는 Default 함수을 나타내는데 사용됩니다. 이 구문은 가장 깨끗하고, ECMAScript 6의 제작자는 이것이 웹상에서 가장 많이 사용되는 형식이기를 기대하며 이미 존재하는 객체를 사용할 수 있습니다.
Default 바인딩과 하나 이상의 Default가 아닌 바인딩을 모두 export
하는 Module의 경우 하나의 명령문으로 모든 export
바인딩을 가져올 수 있습니다. 예를 들어, 이 Module을 가지고 있다고 가정해 보겠습니다.
|
|
다음의 import
문을 사용하여color
와 Default 함수를 모두 import
할 수 있습니다.
|
|
쉼표는 Default 로컬 이름을 중괄호로 묶인 Default값이 아닌 값과 구분합니다. Default값은 import
문에서 Default가 아닌값 앞에 와야한다는 것을 명심하십시오.
Default값을 export
하는 것과 마찬가지로, 이름 바꾸기 구문을 이용하여 Default값을 import
할 수 있습니다.
|
|
이 코드에서 Default export
(default
)는 sum
으로 이름이 바뀌고 추가적으로 color
export
또한 가져옵니다. 이 예제는 앞의 예제와 동일합니다.
바인딩을 다시 export
Module이 import
한 내용을 다시 export
하고 싶을 때가 있을 수 있습니다 (예 : 여러 개의 작은 Module로 라이브러리를 만드는 경우). 이 장에서 이미 설명한 패턴을 사용하여 import
한 값을 다음과 같이 다시 export
할 수 있습니다.
|
|
이게 효과가 있지만, 하나의 문장으로 똑같은 일을 할 수 있습니다.
|
|
이 형태의 export
는 sum
의 선언을 위해 지정된 Module을 export
합니다. 물론 동일한 값에 대해 다른 이름을 export
하도록 선택할 수도 있습니다.
|
|
여기서 sum
은 "./example.js"
에서 import
한 후 add
라는 이름으로 export
합니다.
다른 Module의 모든 것을 export
하려면 *
패턴을 사용할 수 있습니다.
|
|
모든 것을 export
하면 이름이 있는 export
뿐만 아니라 Default값도 포함되기 때문에 Module에서 export
할 수있는 것에 영향을 줄 수 있습니다. 예를 들어, example.js
에 Default export
가 있는 경우 이 구문을 사용하면 새로운 Default export
를 정의할 수 없습니다.
바인딩없이 import
일부 Module은 아무것도 export
할 수 없으며 전역 범위의 객체만 수정하기도 합니다. Module 내의 최상위 변수, 함수 및 클래스가 전역 범위에서 자동으로 끝나지는 않지만 이것은 Module이 전역 범위에 액세스 할 수 없다는 것을 의미하지는 않습니다. Array
와 Object
와 같은 Built-in 객체의 공유된 정의는 Module 내에서 접근 가능하며, 그 객체에 대한 변경은 다른 Module에 반영됩니다.
예를 들어, 모든 Array에 pushAll()
메서드를 추가하려면 다음과 같이 Module을 정의할 수 있습니다.
|
|
이것은 export
또는 import
가 없더라도 유효한 Module입니다. 이 코드는 Module과 script로 모두 사용할 수 있습니다. 아무 것도 export
할 수 없기 때문에 import
를 사용하여 바인딩을 가져 오지 않고 Module 코드를 실행할 수 있습니다.
이 코드는 pushAll()
메서드가 포함된 Module을 import
해서 실행하므로 pushAll()
이 Array 프로토 타입에 추가됩니다. 즉, 이 Module 내부의 모든 Array에서 pushAll()
을 사용할 수 있습니다.
바인딩이없는
import
하는 대부분의 경우 polyfill과 shim을 만드는 데 사용됩니다.
Module 로딩
ECMAScript 6에서는 Module 구문을 정의하지만 로드하는 방법은 정의하지 않습니다. 모든 JavaScript 환경에서 작동할 수있는 단일 사양을 작성하는 대신 ECMAScript 6은 구문만 지정하고 로드 메커니즘을 HostResolveImportedModule
이라는 정의되지 않은 내부 작업으로 추상화합니다. 웹 브라우저와 Node.js는 각각의 환경에 적합한 방식으로 HostResolveImportedModule
을 구현하는 방법을 결정해야합니다.
웹 브라우저에서 Module 사용하기
ECMAScript 6 이전에도 웹 브라우저에는 웹 응용 프로그램에 JavaScript를 포함시키는 여러 가지 방법이 있었습니다. 이러한 script 로드 옵션은 다음과 같습니다.
- 코드를 로드 할 위치를 지정하는
src
Attribute와 함께<script>
요소를 사용하여 JavaScript 코드 파일로드. src
Attribute 없이<script>
요소를 사용하여 JavaScript 코드를 인라인으로 포함.- 웹 Worker 또는 서비스 Worker와 같이 Worker로 실행되는 JavaScript 코드 파일로드.
Module을 완벽하게 지원하려면 웹 브라우저가 이러한 각 메커니즘을 업데이트 해야했습니다. 이러한 세부 사항은 HTML 사양에 정의되어 있으며 이 섹션에서 설명합니다.