일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 가상면접 사례로 배우는 대규모 시스템 설계
- 다섯수치요약
- 오버라이딩
- 데이터베이스
- 인덱스 순서
- 파티셔닝
- LRU
- partitioning
- 쿼리 실행계획
- 데이터베이스 인덱스
- 레디스
- Sharding
- f45
- axios
- knn분류기
- 글또
- Retry
- 통계학개론
- R Studio
- System Design
- 상자그림
- redis
- DB 파티셔닝
- 샤딩
- 머신러닝
- 인덱스 추가
- 데이터베이스 파티셔닝
- axis interceptor
- 복합인덱스
- k-Nearest Neighbors
- Today
- Total
haileyjpark
[JavaScript] JavaScript의 역사 본문
javascript의 역사와 변화된 과정에 대하여 이해하고
javascript에서 자주 사용하는 라이브러리나 기술, 용어들의 개념에 대해서 정리해 보았습니다.
1. JavaScript의 탄생: 🌟 동적인 웹 페이지의 필요성

1994년 출시된 Netscape는 페이지와 페이지에 링크를 걸어서 페이지 이동만 가능한 정적인 웹사이트만 가능했습니다.
그래서 넷스케이프의 설립자 Marc는 계속 “어떻게 하면 동적인 웹사이트를 만들 수 있을까?”를 고민했고, 이미 사용자에게 보여지는 웹사이트 안에서 DOM 요소들을 조작하면서 다이나믹한 요소들을 만들기 위해 새로운 Scripting 언어를 추가하기로 했습니다.
그 때 한창 뜨고 있던 JAVA언어는 웹사이트를 주로 개발하던 개발자들이 쓰기엔 무거웠고, 그래서 기존에 있던 Scheme언어의 문법을 좀 더 자바스럽게 변형해서 Mocha라는 새로운 언어를 탄생시켰습니다.
(시간이 없으니 10일 안에 만들어보자! 라고 했다는 것에서 충격을 받았습니다. 언어를… 10일 만에…. )
이후 언어의 이름을 liveScript로 바꾸었고, Netscape 브라우저 안에 liveScript를 이해하고 실행할 수 있는 엔진인 interpreter가 포함되어 출시되게 되었습니다.
이후 Java 언어의 인기에 올라타기 위해 JavaScript라고 이름을 바꿉니다.
2. ECMA Script의 탄생: 🌟 언어 표준화의 필요성
이후, 마이크로소프트가 Internet Explorer를 만들었고, Netscape Navigator를 reverse engineering해서 JavaScript를 그대로 복원한 후 Jscript라는 언어를 만들어 IE 3.0에 출시했습니다.
넷스케이프와 마이크로소프트는 각자 시장 점유율을 높이기 위해 자사 브라우저에서만 동작하는 기능을 추가합니다.
웹 개발자들 입장에서 다음과 같은 문제가 발생을 하였습니다.
- 웹 개발자들이 너무 다른 두 웹 브라우저에서 동작할 수 있는 웹사이트들을 만들어야 하는 사태가 발생
- 브라우저에 따라 웹페이지가 정상적으로 동작하지 않는 크로스 브라우징 이슈 발생
그래서 Netscape회사는 ECMA International에 JavaScript 표준화를 요청하고, ECMAScript1이 나옵니다.
ECMAScript를 통해, 각 브라우저 제조사는 일관성 있는 가이드라인을 따라 JavaScript 엔진을 구현할 수 있게 되었습니다.
3. Ajax의 등장: 🌟 HTTP의 불편함
기존의 HTTP 통신은 서버에 데이터를 전송할 때, HTML의 form태그로 전송 데이터를 감싸고, submit으로 서버에 전송합니다. 그럼 서버는 응답값으로 새로운 페이지를 알려주고, 클라이언트는 그 페이지를 새로 그립니다.
→ 실시간으로 변경사항이 반영되는 것을 반영하여 구현하려면, 매번 페이지를 새롭게 렌더링해야 했었습니다. (업데이트가 될 때까지 새로고침을 누른다.)
이러한 문제점을 개선하기 위해 1999년, JavaScript를 이용해 필요한 데이터만 비동기적으로 로드할 수 있는 통신 기능인 Ajax(Asyncronous javascript and XML)가 XMLHtppRequest라는 이름으로 등장했습니다.

이전에는 클라이언트와 서버 간에 HTML로 데이터를 주고 받았다면, AJAX는 데이터로 XML을 사용하면서, 서버와 필요한 데이터만을 교환할 수 있게 되었습니다.
- XML이란
XML은 EXtensible Markup Language의 약자로, 마크업 언어입니다.
HTML은 태그 기반이지만, XML은 텍스트 기반의 언어입니다.
HTML은 “데이터를 어떻게 누구나 보기쉽게 표현할 수 있을까?”에 중점이 맞춰진 언어입니다. 그렇기 때문에 서버와 통신 시에는 적합하지 않았습니다. XML은 텍스트 기반으로, 데이터를 저장하고 교환할 목적으로 만들어진 언어입니다.
Ajax는 JavaScript와 DOM, Fetch라는 핵심 기술로 구성됩니다.
→ JavaScript를 통해 DOM을 제어할 수 있고, Fetch를 통해 사용자가 웹페이지에서 작업하는 동안 서버와 통신할 수 있게 됨으로써, 새로운 웹페이지를 제공하는 것이 아닌 필요한 부분만 변경할 수 있습니다.
4. JQuery와 같은 라이브러리의 등장: 🌟 브라우저별 개발의 불편함
2000년 마이크로소프트에서 만든 IE의 점유율이 95%까지 높아졌고, 더이상 ECMAScript 표준안에 참가하지 않았습니다.
→ 2000년도부터는 표준안 진행이 더뎌졌습니다.
2004년 Mozila는 Firefox 브라우저를 출시했고, ECMA에 actionScript3이라는 언어와 이 언어를 해석하는 엔진인 Tamarin으로 다시 표준화를 검토해보자고 요청했습니다.
→ 하지만 ECMA에서는 이미 JavaScript와 그에 맞는 엔진이 있었기 때문에 다시 새롭게 표준안을 작성하기에 무리라고 판단하였습니다.
그래서 표준안을 두고 Mozila / Microsoft / Netscape 3사의 신경전이 있었고, 이로 인해 개발자들은 다양한 브라우저와 그에 따른 다양한 방식에 대해 힘들어했습니다.
이런 와중에 웹개발에 대한 수요가 많아지고 웹개발 시장이 커지면서 웹개발자들의 커뮤니티가 활성화되면서 개발자들이 더이상 다른 브라우저의 구현 사항을 신경쓰지 않게 하기 위해 JQuery, dojo, mootools와 같은 라이브러리들이 많이 나오게 됩니다.
이 라이브러리들이 제공하는 APIs를 쓰며 이 함수들만 호출하면, 다른 브라우저 상에서 동작할 수 있게 하는 것은 이 라이브러리들이 처리해줍니다.
좋은 프로그래밍의 철학은 이렇게 서비스레이어나, 프레젠테이션 레이어, 비지니스 레이어를 만들 때, 이렇게 APIs 들을 작 작성해서 나중에 구현사항이 변경되더라도 이 인터페이스들을 사용하고 있는 사용자의 코드는 수정하지 않도록 만드는 것이라고 합니다.
5. JS가 백엔드에? Node.js의 등장 : 🌟 Chrome 브라우저와 V8엔진 출시 / ES5, ES6
[1]. ES5와 ES6 표준안 제정
2000년도부터는 마이크로소프트가 ECMAScript에 참가하지 않으면서 표준안 제정이 더뎌졌습니다.
2008년, Google이 JavaScript를 실행하는 속도가 매우 빠른 강력한 V8엔진이 포함된 Chrome 브라우저를 출시하면서 다른 브라우저들이 좋은 자극을 받게 되고, 4사가 모여 표준화를 만들어 서로 윈윈의 관계를 형성해보자며 2009년 ES5, ES6가 제정됩니다.
모든 브라우저들이 이제는 ES의 표준화를 잘 따르고 있기 때문에 더이상 라이브러리의 도움 없이도 충분히 JavaScript와 웹 APIs에서 제공하는 APIs 만으로도 모든 브라우저에서 잘 동작할 수 있는 웹사이트나 웹어플리케이션을 만들 수 있게 됩니다.
[2] Node.js의 탄생
초기에 서버환경에서 JS를 사용하고자 LiveWire나 Jaxer등을 사용했지만, 느린 속도가 단점이었습니다.
- V8 JavaScript 엔진은 C++로 개발되었으며, 이전에 개발된 어떠한 JavaScript 엔진보다 속도가 빨랐습니다.
- 크롬 웹 브라우저 코드를 오픈소스화 하면서 V8 JavaScript 엔진의 코드가 공개되었습니다.
V8 엔진의 발표 이후, JS의 속도가 빨라지자 2009년 1월부터 JS를 웹브라우저가 아닌 곳에서 쓸 수 있도록 표준을 만들자는 의견이 많아졌고 현재 CommonJS 프로젝트로 알려진 ServerJS 프로젝트가 진행되었습니다.
CommonJS 표준 발표 이후, 라이언 달은 CommonJS 표준과 V8 JavaScript 엔진 기반으로 Node.js를 개발했습니다.
Node.js의 경쟁력은 어떻게 강해질 수 있었을까요?
웹 브라우저의 성능이 개선되면서 SPA가 떠오르기 시작했고, 스마트폰의 앱 생태계가 활성화되면서 서버는 웹 브라우저 외에 모바일 앱도 지원해야 했습니다. 이러한 흐름에 맞춰 서버는 JSON, XML 등의 형식으로 클라이언트에 데이터를 전달하는 API 서버의 역할을 할 필요가 더욱 커졌고, 이에 따라 UI와 관련한 많은 로직이 서버에서 클라이언트로 이전되었고, 서버는 더 단순한 작업을 더 많이 처리해야하는 상황이 되었습니다.
데이터 베이스 측면에서는, 서비스가 빠르게 변화할 때 Schema를 엄격하게 관리하지 않는 것이 서비스 변화에 더욱 기민하게 대처할 수 있다는 의견이 나오는 등 기존 관계형 데이터베이스의 한계에 대한 대안으로 NoSQL 데이터베이스가 등장했습니다.
이렇게 기존 서버 기술들이 가진 장점들 (RDBMS 데이터베이스와의 연동, HTML 생성) 의 중요성이 줄어든 환경은 Node.js에게 기회가 되었습니다. 기존 서버 기술들이 가진 기능이 없다는 것이 오히려 Node.js는 배우기 쉽다는 인식을 만들었고, 비동기 문법과 V8에서 비롯한 강력한 실행 성능은 Node.js의 특별한 장점이 되었습니다.
또한, JavaScript의 비동기적인 문법과 내부적으로 사용하는 비동기 네트워크 API 덕분에 성능이 좋은 실시간(real-time)서버를 작성하기에 유리하다는 것도 Node.js의 장점이 되었고, 실시간성이 강조되는 여러 SNS의 급속한 발달과 맞물려 영향력을 키울 수 있었습니다.
기존 네트워크 애플리케이션과 Node.js 기반 네트워크 애플리케이션의 차이
기존 웹서버
- 대부분 스레드를 기반으로 하는 동기 방식으로 네트워크 입출력 처리
- 기존 동기 방식은 일을 처리하는 대신 스레드를 여러 개 만들어 동시에 일을 처리
→ 일이 많아질수록 스레드를 더 많이 나누어야 하므로 메모리 사용량이 증가한다는 단점
Node.js
- 이벤트를 기반으로 하는 비동기 방식으로 네트워크 입출력 처리
- 이벤트 기반 비동기 방식은 이벤트가 발생 시 일을 처리하는 방식
- 스레드를 하나만 사용하고 이벤트를 사용하여 빠른 속도로 일을 처리할 수 있음
엄밀히 말하면 싱글 스레드로 동작하지는 않습니다.
노드를 실행하면 프로세스가 먼저 하나 생성되고, 그 프로세스에서 스레드들을 생성하는데, 이 때 내부적으로 스레드를 여러 개 생성합니다. 그 중에서 우리가 직접 제어할 수 있는 스레드는 하나 뿐이며, 그래서 흔히 노드가 싱글 스레드라고 여겨집니다. - 메모리 사용량과 같은 시스템 리소스 사용량에는 변화가 거의 없음
→ 따라서 대규모 네트워크 프로그램을 개발하기 적합 - I/O 작업을 처리할 때는 멀티 스레딩보다 멀티 프로세싱이 효율적이고 → 서버에는 기본적으로 I/O 요청이 많이 발생하므로, I/O 처리를 잘하는 노드를 서버로 사용하면 좋습니다.
- CPU 부하가 큰 작업에는 적합하지 않습니다.
CPU 연산을 많이 요구하면 스레드 하나가 혼자서 감당하기 어렵습니다.
Electron의 등장
Node.js의 성공으로, JavaScript에서 파일 시스템 등의 OS자원에 안정적으로 접근할 수 있는 경로가 생겼고, npm을 중심으로 JavaScript 생태계가 크게 활성화되었습니다.
이런 Node.js의 성공을 보며 Github의 직원들은 Node.js와 구글 크롬 브라우저의 오픈소스 버전인 Chromium을 섞어서 확장성이 뛰어난 텍스트 에디터를 만들어보면 어떨까라는 생각을 하게 되었습니다. Chromium은 이미 거의 모든 OS에서 웹페이지를 빠르고 유연하게 그릴 수 있는 최고 수준의 UI 기술을 탑재하고 있었고, Node.js를 이용하면 JavaScript를 이용해서 OS의 자원에 쉽게 접근하며 npm을 중심으로 구축된 JavaScript 생태계의 작업물들을 가져다 쓸 수 있었습니다.
이러한 생각을 바탕으로, Node.js + Chromium의 조합이 Electron이라는 이름으로 출시되었습니다.
데스크탑 앱 개발도구에는, OS마다 직접 제공하는(Native) 데스크탑 앱 개발도구와 여러 OS를 지원하는 크로스 플랫폼 앱 개발도구가 있었습니다.
OS마다 직접 제공하는 데스크탑 앱 개발도구는 Electron에 비해 성능은 좋았지만, 각 OS별로 따로 앱을 개발하고 관리해야 하는 것에 비해 Electron은 한 번만 개발을 하면 되었고, 웹사이트와 데스크탑 앱 간에 코드를 공유하면 하나의 코드로 웹사이트와 모든 OS에서 동작하는 데스크탑 앱을 만들 수 있었습니다. 이는 개발 비용 감소, 생산성 향상, 기술 파편화 방지를 통한 개발 안전성 향상 등의 가치를 누릴 수 있게 해주었습니다.
기존 크로스 플랫폼 앱 개발도구는 Electron처럼 웹사이트와 앱의 코드 공유를 가능하게 해주지 못했고, 개발 편의성과 기술 범용성 부문에서도 Electron에 비해 떨어졌습니다.
결국 Electron은 데스크탑 개발 시장에서 경쟁우위를 가지며 Microsoft, Facebook, Twitch, Slack, Discord, Skype, WhatsApp 등 큰 규모의 서비스들이 Electron을 기반으로 데스크탑 앱을 제작하게 되었습니다.
6. JS의 모바일 진출: React Native
모바일 앱을 개발하기 위해서는 안드로이드의 경우 Android(Java), 아이폰의 경우 IOS(Swift, Objective C)를 사용해야만 했습니다. 안드로이드, IOS 개발을 따로 하는 경우 Native 개발을 한다고 이야기하는데, 빠르게 최소 단위 MVP로 앱을 만들어 시장의 반응을 살펴야하는 스타트업 같은 곳에서는 두 가지를 동시에 개발하기에는 인력과 시간이 많이 소모되었습니다. 그래서 안드로이드와 IOS 앱을 동시에 개발할 수 있는 Ionic 등의 하이브리드 앱이 나오게 되었습니다. 그러나 하이브리드 앱의 경우 웹뷰를 네이티브에 씌우는 형태이기 때문에 속도가 느리고 큰 규모의 프로젝트에 적합하지 않았습니다.
그래서, 리액트의 접근 방식을 모바일로 확장한 페이스북의 오픈소스 프로젝트인 React Native가 등장하게 되었습니다.

위 그림과 같이 JS로 코딩한 React의 컴포넌트는 React Native 플랫폼을 거쳐 IOS, Android Native로 각각 변환되어, JS 하나만으로도 앱 제작이 가능해지게 되었습니다.
- React Native의 장점
- 플랫폼 별 언어를 몰라도 JS만 알고 있으면 앱을 만들 수 있습니다.
- 개발 속도가 빠릅니다. (Android, IOS는 코드를 변경할 때마다 빌드를 새로 해줘야 하지만, React Native는 개발하면서 바로 결과를 확인할 수 있도록 Live Reloading을 제공해줍니다.
- 하이브리드 앱보다 성능이 좋습니다.
- 짧은 역사임에도 높은 퍼포먼스와 생산성으로 인해 생태계가 활발한 편이어서 리소스가 풍부합니다. - React Native의 단점
- React Native에 없는 기능이 필요하게 되면 직접 Native로 제작해야 할 수도 있습니다. 그런 경우 Android와 IOS를 모두 할 줄 알아야 합니다.
- 아직까지 완전하게 Native를 대체할 수 있는 성능은 아닙니다. 비즈니스 로직이 많이 복잡하거나 뷰 스택이 높게 쌓이게 되면 속도가 느려집니다.
7. yarn의 등장 : npm? yarn? 🌟 Facebook은 yarn을 왜 만들었나
npm이 없었을 때는 필요로 하는 기능을 추가하기 위해서 직접 작성하거나 github를 통해 다운로드하여 사용해야 했습니다. 이러한 불편을 해소하기 위해 npm이 등장했습니다.
npm에 어떤 불편함이 있었길래 yarn이 개발되었을까요?
- 속도
npm은 필수 단계를 순차적으로 수행하는 경향이 있어서 다음 패키지로 넘어가기 전에 각 패키지를 완전히 설치해야 합니다.
- 그래서 동시에 여러 패키지들을 설치하여 속도를 향상시키기 위해 yarn이 나왔습니다.
- yarn은 다운받은 패키지 데이터를 캐시에 저장하여 중복된 데이터는 다운로드하지 않고 캐시에 저장된 파일을 활용합니다.
( 그런데 npm V6.10.1과 yarn V1.17.3으로 install 속도 실험을 했는데 아주 근소한 차이였다고 합니다.) - 보안
- npm은 디펜던시를 가지는 다른 패키지들이 자동으로 포함되도록 합니다. 이런 부분은 편리하기는 하지만 보안 문제에 있어 여러 취약점을 불러올 수 있습니다.
- yarn은 yarn.lock이나 package.json 파일에 있는 것만 설치를 하도록 설계되었습니다.
- yarn.lock은 모든 디바이스에 같은 패키지를 설치하는 것을 보장하기 때문에 버전의 차이로 인해 생기는 버그를 방지해줄 수 있습니다.
(최근 npm의 업데이트에서 npm의 보안 업데이트도 크게 향상되었다고 합니다.)
8. JavaScript 모듈시스템의 도입
JS에 모듈 시스템이 없을 때는, 클라이언트 사이드를 개발하는 경우에 필요한 파일을 만들어서 같이 배포한 다음, <script src=”….”>와 같이 필요한 파일을 추가해서 불러오는 방식을 주로 사용했습니다.
→ 이런 방식은 추가하는 파일이 많아지면 관리가 어려워지는 문제가 있습니다. 특히, 추가한 외부의 코드에서 같은 변수를 사용하는 경우에 문제가 될 수 있습니다.
그래서 외부의 라이브러리를 추가해서 사용할 때 필요한 코드들만 사용하거나, 변수의 충돌과 같은 문제를 방지하고 관리의 용이성을 위해 JS의 모듈 시스템이 고안되었습니다. (라이브러리는 외부자원, 모듈은 내부 부품)
JavaScript는 CommonJS, AMD, UMD, ESM의 모듈 시스템
- ESM
- ES6에서부터 표준화된 모듈 시스템 (주로 클라이언트 사이드에서 사용됨) - CommonJS
- 새로운 JavaScript 표준을 만들기 위해서 2009년부터 시작된 프로젝트
- 동기적인 특징 때문에 서버사이드에 사용하기 용이 - AMD(Asynchronous Module Definition)
- 모듈과 종속성 파일들을 비동기적으로 로드할 수 있도록 모듈을 정의하는 매커니즘 (CommonJs와 ESM 모두 동기식 로딩 방식을 채택)
- 다른 모듈 시스템과의 차이점은 동적 로딩 - UMD
- 모듈 시스템이 AMD와 CommonJS를 쓰는 두 그룹으로 나뉘면서 서로 호환이 되지 않는 문제를 해결하기 위해 제안된 방식 → 클라이언트 사이드에서 많이 사용되는 AMD나 서버사이드에서 많이 사용되는 CommonJS를 모두 사용할 수 있음
JavaScript는 CommonJS, AMD, UMD, ESM의 모듈 시스템을 사용 방법
[1]. ESM
export 방법
- named export
- 선언된 변수명을 그대로 export. 모듈 내에 여러 개의 export가 존재할 수 있습니다.
- 변수를 선언함과 동시에 내보내기를 할 수 있습니다.
- 먼저 정의된 변수들을 모아서 내보내거나, 먼저 정의된 함수를 별칭으로 바꿔서 내보낼 수 있습니다.
export let name1;
export const name2;
export var name3;
export function name4 () {/*...*/}
export class MyClass {/*...*/}//정의된 변수 모아서 내보내기
const var1;
let var2;
var var3;
export { var1, var2, var3 }//먼저 정의된 함수 별칭으로 내보내기
let var4;
export { var4 as var5 }
- default export
- 모듈에서 하나만 존재할 수 있습니다.
- named export와 같이 변수를 직접 내보낼 수 없음 → 에러 발생
- export default를 두 번 사용하는 경우에도 에러 발생
→ default라는 식별자를 export문이 내부적으로 사용하기 때문
export default expression;
export default function () { /*...*/ } // 익명함수
export default function myFunction() { /*...*/ } // 기명함수
export default class { /*...*/ } // 클래스
export default class MyClass { /*...*/ } // 커스텀 클래스
export default function* () { /*...*/ } // 제너레이터// named export 처럼 묶어서 내보내기
const myModule = { /*...*/ }
const var1 = () => {}
export { myModule as default, var1 } // as를 이용해 별칭으로 default export// 아래와 같이 변수를 직접 내보내면 에러 발생
export default const test = /*...*/ // Uncaught SyntaxError: Unexpected token const// ------------------------------ 두번 사용하는 경우
export default function () { /*...*/ }
export default MyClass () { /*...*/ }
Uncaught SyntaxError: Identifier '*default*' has already been declared
- export-from
- import와 export를 한번에 처리할 수 있음
- 패키지의 index에서 모듈을 한 번 정리해 준 다음 하나의 모듈로 내보냄
- 클라이언트 사이드의 환경에서는 from을 사용할 때 .js 확장자를 꼭 적어줘야 함 → 404 에러 발생
// math 모듈에서 일부만 import 한 뒤 다시 export
export { add, subtract } from './math';
// config 모듈의 (export가 가능한)모든 변수를 export
export * from './config';
// logger 모듈의 default export를 Logger라는 이름으로 export
export { default as Logger } from './logger';// 디렉토리를 import하면 기본적으로 index.js 파일을 탐색함
import * as utils from './utils';
// utils라는 별칭으로 import한 메소드, 변수 사용
utils.add(1, 2) // 3
utils.DB_HOST // 'localhost'
utils.Logger.print('TEST') // 'TEST'// math 모듈에서 일부만 import 한 뒤 export
export { add, subtract } from './math';
// export했지만 현재 파일에서 그냥 add 함수를 사용하면 스코프내에 존재하지 않아 에러 발생
add(1, 2); // Uncaught ReferenceError: add is not defined
import
- as 명령어를 이용해서 별칭으로 불러오거나, *를 사용해서 named export의 모든 변수와 메서드를 불러올 수 있습니다.
import name from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as alias from "module-name";
import defaultMember from "module-name";
import "module-name";
Module Type
- script 태그에 type=”module”을 선언하면 자바스크립트 파일이 모듈로 동작하게 됨. → 이 때 모듈이라는 것을 명확하게 표현하기 위해 확장자는 mjs라고 붙여주는 것을 권장
- 파일마다 독립적인 스코프를 갖게 되고, 각각의 mjs 파일에 있는 window 객체는 서로 공유되지 않습니다.
- 브라우저에 따라서는 아직 import와 export를 지원하지 않는 경우도 있어서 webpack과 같은 번들러를 사용해야 함
[2]. CommonJS
module.exports
- module이라는 키워드는 현재의 모듈을 나타내는 자유변수(예약어)
→ 현재 모듈에 대한 정보를 담고있는 일종의 객체
const PI = 3.14;
module.exports.PI = PI;const utils = require('./utils');
console.log(utils.PI); // 3.14# module 객체에 담긴 데이터
Module {
...
exports: { PI: 3.14 }, // exports 라는 객체 안에 PI라는 프로퍼티가 생성됨
parent: Module {/*...*/},
filename: '...',
loaded: false,
children: [],
paths: [ /*...*/ ]
}
아래와 같이 module.exports = 3.14로 직접 내보낸다면
module.exports = 3.14;
console.log(module);# module 객체에 담긴 데이터
Module {
...
exports: 3.14, // exports에 객체가 아닌 데이터가 추가됨
parent: Module {/*...*/},
filename: '...',
loaded: false,
children: [],
paths: [ /*...*/ ]
}
exports
exports.PI = 3.14;
console.log(module.exports === exports); // true
console.log(exports); // { PI: 3.14 }
- 모듈이 평가되기 전에 module.exports의 값을 할당 받음
- module.exports와 exports는 완전히 동일한 동작을 수행하지만, 실제 exports는 module.export의 데이터를 참조하고 있음
- exports와 module.export 두 가지를 함께 사용하지 않는 것이 좋음.
module.exports = 3.14;
exports.PI = 3.14;
console.log(module.exports); // 3.14
exports가 참조하는 건 module.exports라는 빈 오브젝트인데, 이걸 3.14라는 데이터로 직접 덮어씌워 버리면 exports가 참조하던 객체는 사라지고, module.exports의 값을 참조하게 됨
require
- file modules
- 확장자는 생략하고 모듈의 이름만 적어줍니다.
- 상대경로에 있는 파일 중 확장자가 .js / .json / .node 인 파일을 모듈로 불러올 수 있음
const data = require('./data');
const utils = require('./utils');
console.log(data); // { data: "Hello World!" }
console.log(utils.PI); // 3.14
- folders as Modules
// utils 폴더 아래에 index.js 파일 생성
module.exports.PI = 3.14;const utils = require('./utils'); // 자동으로 ./utils 경로에 있는 index.js 파일을 탐색
console.log(utils.PI); // 3.14
Node.js는 상대경로나 절대경로로 모듈을 호출하면 다음과 같은 순서로 파일 탐색합니다.
1. package.json 파일에 정의된 name과 main의 값 → 없다면 2번으로
2. 해당 폴더에 index.js 또는 index.node 파일이 있는지 확인
3. 모두 실패하면 Cannot find module
- node_modules
경로없이 require(moduleName)을 호출할 경우, 현재 모듈의 최상위 디렉토리에서부터 /node_modules 폴더 탐색.
탐색 순서 (module.path에 정의되어 있는 프로퍼티 값과 같습니다)
1. /home/{win_user}/project/node_modules/{module_name}.js
2. /home/{win_user}/node_modules/{module_name}.js
3. /home/node_modules/{module_name}.js
4. /node_modules/{module_name}.js - global_directories
위의 방법들로 모듈을 찾지 못하면 OS의 글로벌 파일 경로인 global_directories를 탐색
1. $HOME/.node_modules
2. $HOME/.node_libraries
3. $PREFIX/lib/node
[3]. AMD
AMD의 비동기 모듈을 구현한 가장 유명한 스크립트는 RequireJS가 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<!-- require.js 로딩 -->
<!-- 스크립트 파일 로딩 -->
<script src="./app.js"></script>
</body>
</html>// require.js는 define의 callback 스타일로 모듈을 정의하여 내보냄
define(function() {
return {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};
});// require 함수의 첫번째 인자는 불러올 모듈, 두번째 인자는 함수로 인자는 불러온 모듈의 이름임
require(["./utils"], function(utils) {
const result = utils.add(1, 2);
console.log(result); // 3
});
[4]. UMD
모듈 로더를 확인하는 즉시 실행 함수와 모듈을 생성하는 익명 함수로 구성되어 있습니다.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports', 'b'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
}(this, function (exports, b) {
// 모듈 로더를 확인하는 즉시 실행하는 즉시 실행 함수
exports.action = function () {};
}));
9. TypeScript의 등장
모든 프로그래밍 언어에 장단점이 있듯이 JavaScript도 언어가 잘 정제되기 이전에 서둘러 출시된 문제와 과거 웹페이지의 보조적인 기능을 수행하기 위해 한정적인 용도로 만들어진 태생적 한계로 좋은 점도, 나쁜 점도 많습니다.
JavaScript는 C나 Java와 같은 C-family 언어와는 구별되는 다음과 같은 특성이 있습니다.
- 프로토타입기반 언어
- 함수단위 스코프를 갖는 scope, 호출방식에 따라 다른 this
- 동적타입언어
이러한 특성들은 코드가 복잡해질 수 있고 디버그와 테스트 공수가 증가하는 등의 문제를 일으킬 수가 있습니다.
또한, 시간이 지나며 Javascript가 브라우저 밖에서도 움직일 수 있도록 확장되어 백엔드 서버 구축에도 사용되는 등 활용성이 점차 증대되었고, Javascript를 이용하여 대규모 프로젝트를 수행하는 경우도 빈번하게 발생하게 되었습니다.
이렇게 활용성이 증대되며 사용자가 많아지게 되자, 기존 클래스 기반 언어를 사용하던 개발자들은 이러한 Javascript의 특성들로 인해서 혼란스러워하게 되었습니다.
변수명이나 속성명을 입력했는데 예상치 못한 결과값이 반환된다거나, 어딘가에서 자료형이 바뀌어버린 경우, 규모가 큰 프로그램이라면 여러 사람들이 협업하며 어디서 발생했는지 알 수 없어 모두가 고생하는 일이 발생할 수도 있습니다. 그래서 이를 대체하고자 CoffeeScript, Dart, Haxe 와 같은 AltJS(JavaScript의 대체 언어)가 나오게 되었습니다.
TypeScript 또한 JavaScript 대체 언어의 하나로, JavaScript(ES5)의 Superset(상위확장)입니다. C#의 창시자인 덴마크 출신 소프트웨어 엔지니어 Anders Hejlsberg(아네르스 하일스베르)가 개발을 주도한 TypeScript는 Microsoft에서 2012년 발표한 오픈소스로, 정적 타이핑을 지원하며 ES6(ECMAScript 2015)의 클래스, 모듈 등과 ES7의 Decorator 등을 지원합니다.
10. 호환성 문제? Babel의 등장
JavaScript는 시간이 지나며 많은 발전을 거듭하게 되고, 정적 타이핑을 지원하는 Typescript의 등장과 문법의 큰 변화가 있었던 ES5 등 다양한 방향으로 진화되었습니다. 그래서 개발자들은 이러한 변화에 적응하여 모던 JavaScript 코드로 개발을 하게 되었는데, 기존의 브라우저들은 이 모던 JavaScript와 호환되지 않았고, 이미 작성된 코드들을 전부 ES5 이후의 문법으로 다시 작성하는 일은 굉장히 힘든 일이었습니다. 특히, 프론트엔드 파트에서 구형 웹 브라우저인 IE는 대부분의 ES5 문법 적용이 되지 않아 프로그램을 브라우저에 맞춰 여러 번 작성해야 하는 번거로움이 발생했습니다.
이러한 크로스 브라우징 이슈를 해결하기 위해서 구글에서 개발한 “Babel(바벨)”이라는 트랜스파일러 라이브러리가 등장하게 되었습니다.
바벨은 ECMAScript2015 이상으로 작성된 코드들을 모든 브라우저에서 일관되게 동작하도록 호환성을 지켜주고, Typescript, JSX 문법처럼 다른 언어로 분류되는 것도 브라우저에서 동작하도록 만들어 줍니다.
그래서 이제 개발자들은 개발할 때 최신 JS로 개발하고, 배포할 때만 Babel을 이용하여 편리하게 변환할 수 있게 되었습니다.
11. SPA와 프론트엔드 프레임워크
[1]. SPA의 등장
과거에는 웹에서 제공되는 정보가 그렇게 많지 않아서 문서 하나에 전달되는 파일의 용량이 적었습니다. 많다고 해도 페이지가 많이 나뉘어져 있었기 때문에, 데이터를 쪼개서 보여줄 수 있었습니다. 하지만 모니터가 커지고, JavaScript의 기술도 발전하고, 컴퓨터 성능도 좋아지면서 웹 사이트가 고도화됨에 따라 하나의 페이지에서 더 많은 정보를 한 번에 보여줄 수 있게 되었습니다.
그래서 페이지를 이동할 때마다 매번 새로운 정보를 보여주어야 하는 서버 측에서 매번 용량이 큰 새로운 페이지를 전달하는 것이 버거워졌습니다. 게다가 사용자가 늘어남에 따라 모든 사용자가 각 페이지를 서버에 요청하니 서버에서는 과부하가 걸리는 상황들이 발생했습니다. 또한, 과거 MPA(Multi Page Application)는 페이지 전환마다 HTML 파일을 계속해서 요청해기 때문에, 사용자의 인터페이스에서 사용하고 있던 상태를 유지하기가 번거로워지고, 바뀌지 않는 부분까지 새롭게 불러와야 하기 때문에 불필요한 로딩도 발생하게 되었습니다.
이러한 문제를 해결하기 위해 등장한 것이 현재의 SPA(Single Page Application)입니다. SPA는 어떤 웹사이트의 전체 페이지를 하나의 페이지에 담아 동적으로 화면을 바꿔가며 표현합니다. 클릭이나 스크롤 이벤트가 발생하면, 상호작용을 위한 최소한의 요소만 변경이 일어납니다. 페이지 변경이 일어난다고 보여지는 것도 최초 로드된 JavaScript를 통해 미리 브라우저에 올라간 템플릿만 교체되는 것입니다.
SPA는 HTML5의 history API를 이용해서 서버로 요청을 전달하지 않고 JavaScript영역에서 현재 페이지 내에서 화면 이동이 일어난 것처럼 작동하게 됩니다. 이 history API를 이용해서 주소를 인위적으로 바꾸고, history.state에 담아둔 정보로 ajax 요청을 보내 화면을 갱신합니다.
[2]. 프론트엔드 프레임워크의 등장
이렇게 SPA가 등장하면서 서버가 할 일을 대신하는 브라우저의 역할이 커지고 파일이 많아지면서 관리가 어려워졌고, 이를 관리하는 프론트엔드 프레임워크가 등장하게 되었습니다.
SPA 프레임워크로 유명한 것들은 Angular, React, Vue가 있는데, 이들은 모두 SPA를 쉽고 확장성있게 구현하는 것을 목표로 하고 있습니다. SPA의 문제점 중 하나는 JavaScript로 인한 DOM 조작이 빈번하게 일어나 브라우저의 성능을 저하시킨다는 것인데, 대표적으로 React는 Virtual DOM이라는 개념을 사용해 SPA를 구현합니다.
실제 DOM 트리를 흉내낸 가상의 객체 트리로 html 정보를 저장하고 있다가, 데이터 변경이 발생하면 모든 변화를 모아 단 한번 브라우저를 호출해 화면을 갱신하는 방법을 사용합니다. (전체 UI를 그렸다가 이전의 Virtual DOM과 비교해 달라진 내용을 실제 DOM에 적용해 기존 DOM을 이용하는 방식보다 빠름) 이 방법은 서버와 브라우저 간 불필요한 상호작용을 최소화하면서 인터렉티브한 웹사이트를 만드는 것을 가능하게 해줍니다.
Angular가 가장 먼저 발표되고, 이후 React, Vue.js 순서로 개발되었습니다.
각각의 특징은 다음과 같습니다.
- Angular
- 구글에서 개발하고 운용중인 프레임워크
- 양방향 바인딩 방식 제공 (단방향 데이터는 화면 생성시 watcher가 데이터를 인식해 화면에 출력하고, 이후 사용자가 데이터를 업데이트하면 임의의 이벤트를 발생시켜 데이터의 값을 업데이트합니다.
반면, 양방향 바인딩은 추가적인 watcher가 존재해서 사용자가 값을 입력해 변화가 생기면 watcher가 변화를 감지하고 새로운 값을 브라우저 데이터에 반영합니다.)
- AngularJS는 JavaScript 기반으로 출시되었으나 2014년 발표된 Angular는 TypeScript 기반
- 대규모 프로젝트에 적합한 TypeScript 언어를 Vue.js / React보다 효과적으로 지원함 - React
- 페이스북에서 만든 JavaScript 기반 UI 라이브러리
- Virtual DOM 사용
- 라이브러리이기 때문에 다른 프레임워크와 혼용 가능 - Vue.js
- Angular의 양방향 바인딩, React의 Virtual DOM 렌더링 방식을 모두 가지고 있습니다.
- 템플릿을 사용해 뷰와 모델을 연결
- 후발주자이기에 Angular와 React에 비해 생태계 규모가 작습니다.
- Angular는 의존성 주입과 문법이 난이도를 높이고, React는 Redux등의 상태 관리 라이브러리가 필수적으로 여겨져 난이도가 높은 반면, Vue.js는 JavaScript와 HTML 문법만 알면 러닝 커브가 상대적으로 낮다는 장점이 있습니다.
12. JavaScript의 미래

JavaScript는 약 10년마다 커다란 변화가 이루어져 왔으며, 이를 기반으로 2020년을 세 번째 전환점으로 보는 시각도 있습니다.
첫번째 주기인 1997~2007년에는 JavaScript의 첫 등장과 다양한 라이브러리들의 등장으로 동적인 웹 개발이 시작되었고, 언어적 표준화가 시작되며 JavaScript 언어적 측면의 완성을 위해 노력했던 시기입니다.
두번째 주기인 2009~2019년에는 ECMAScript 5의 발표와 함께, 오늘날 생태계의 핵심을 이루는 Node.js, NPM, 트랜스파일러, 다양한 프레임워크가 등장하며 사용자들이 JavaScript를 탐험하고 새로운 영역으로 확장시켜 한 번 더 큰 도약을 이루어낸 시기입니다.
2020년 이후 세 번째 주기에서는 레거시 영역의 정리와 여러 도구로 인해 형성된 레이어의 제거가 이루어질 것으로 예측하는 시각이 있습니다.
CommonJS/AMD(RequireJS)와 같은 비표준적 모듈 사용에 의존하는 수많은 생태계가 ECMAScript 모듈로 전환될 수 있습니다.
타입 사용을 통한 성능 향상은 무시하기 어려워졌고, 그 동안의 ‘JavaScript 도구는 JavaScript로 개발되어야 한다는 접근 방식’은 Typescript를 통해 틀렸음이 증명되었습니다. 2018년 Brandon Dail이라는 사람은 2023년까지 대부분의 유명한 JavaScript 도구들이 JavaScript로 작성되지 않을 것이라고 전망하기도 했습니다.
Deno나 Relay의 경우처럼, 코어 JavaScript 도구에 JavaScript가 아닌 새로운 언어인 Rust가 사용되고 있기도 합니다. Deno는 런타임 사용 시 당연시되던 다양한 공통 도구에 대한 레이어를 제거했습니다. 테스트, limiting, 포매팅과 번들링을 한 개의 바이너리에 모두 포함시켰고, TypeScript 기반이므로 관련된 도구들의 레이어 또한 불필요해졌습니다.
또한, 이제 TC39(Ecma 인터내셔널의 여러 기술 위원회(Technial Committee, 이하 TC) 중 ECMAScript 명세를 논의하고 표준을 제정하는 위원회)의 다양한 논의는 접근성이 떨어지는 기존의 방식에서 벗어나, 온라인 포럼 서비스를 사용해 누구나 새로운 명세를 제안하거나 기존 명세에 대한 질문, 토론 등에 참여할 수 있도록 개선되었습니다.
우리는 지금까지 동적인 웹을 만들기 위해 탄생한 JavaScript가 백엔드로 데스크탑 앱으로, 모바일로 확장되고 TypeScript 라는 슈퍼셋이 생기며 발전되어 가는 과정을 지켜보았습니다. 이 외에 블록체인 DAPP 개발 분야에서도 현재 JavaScript가 가장 보편화되어 있습니다.
소프트웨어 개발자이자 사상가이기도 한 Gary Bernhardt는 2014년 PyCon 콘퍼런스에서 “JavaScript 탄생과 죽음”(The Birth & Death of JavaScript)이라는 세션을 통해 2014년 시점까지의 상황을 기반으로 JavaScript의 미래를 예측했고, 이 발표에서 JavaScript의 영향력이 2035년까지일 것으로 설정하기도 했습니다. 이 예측이 사실이라고 가정한다면 JavaScript의 영향력은 마지막 주기에 들어섰다고 볼 수도 있습니다.
하지만 아직까지는 JavaScript가 갖지 못한 장점을 가진 파괴적 기술이 등장해서 로우 엔드나 신규 시장에서 고객을 만들면서 발전하지 않는 이상 JavaScript가 다른 언어에 의해 대체될 가능성은 없을 것이라고 보는 견해가 많습니다.
마치며
이번 고민 포인트를 통해 내가 사용한 기술/사용할 기술이 어떤 불편함을 해결하기 위해 나온 기술이고, 어떤 부분을 개선해줄 수 있는지를 알고 적용하는 것의 중요성에 대해 공감하게 되었습니다.
늘 느끼는 거지만 개발을 잘하기 위한 가장 좋은 방법은, 말을 처음 배우기 시작할 무렵의 아이처럼 수많은 “왜?”와 함께하는 것인데요.
“이게 왜 개발됐지?”, “여기서는 왜 이런 에러가 나는거지?”, “이건 왜 이런 방식으로 구성됐지?”라는 의문을 가지면서 발전하는 모습을 보이고 싶습니다.
참고
https://www.youtube.com/watch?v=wcsVjmHrUQg&list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&index=1
SPA(Single Page Application) 이란?
SPA(Single Page Application)
프론트엔드 프레임워크 트렌드(Angular / React / Vue.js)
The Third Age of JavaScript
NAVER D2
누가 자바스크립트를 파괴할 수 있을까?
React Native를 소개합니다
'JavaScript' 카테고리의 다른 글
[JavaScript] 서버 간 axios 통신과 interceptor로 실패 요청 retry 하기 (0) | 2023.03.12 |
---|---|
[JavaScript] JS의 비동기 처리 코드 작성 방식 (0) | 2022.07.24 |
[JavaScript] JavaScript 버전별 문법의 차이와 변환과정 및 치환방법 (0) | 2022.05.21 |
[JavaScript] script언어와 Type언어란? (0) | 2022.05.15 |