일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 머신러닝
- knn분류기
- LRU
- axios
- 데이터베이스 인덱스
- 샤딩
- R Studio
- 레디스
- 통계학개론
- DB 파티셔닝
- 가상면접 사례로 배우는 대규모 시스템 설계
- f45
- 인덱스 순서
- 상자그림
- k-Nearest Neighbors
- 파티셔닝
- Sharding
- 글또
- Retry
- 데이터베이스 파티셔닝
- redis
- 오버라이딩
- partitioning
- axis interceptor
- 복합인덱스
- 다섯수치요약
- System Design
- 쿼리 실행계획
- 데이터베이스
- 인덱스 추가
- Today
- Total
haileyjpark
[JavaScript] JavaScript 버전별 문법의 차이와 변환과정 및 치환방법 본문
버전별 문법의 차이
ES5(2009)
- Array helpers (forEach, map, filter, reduce, some, every와 같은 메서드) 지원
- Object에 대한 getter / setter 지원
- 자바스크립트 strict 모드 지원
- JSON 지원 (과거 XML 사용)
ES 2015 (ES6)
- let, const 키워드 추가
- iterator / generator 추가
- ES Modules (import / export) 추가 (node에서는 현재까지 미지원)
- Promise 지원
- Template strings: 백틱으로 처리된 문자열
- Fat arrow function: () => {} 형태의 함수 정의
- Enhanced object literals: 객체 멤버의 key/value가 동일하면 축약 가능
- Default function arguments: 함수 인자 기본값
- Rest, spread: 인자 전달이나 받을때 ...으로 배열 바인딩
- Destructuring: 변수나 함수인자 대입을 멀티로
- Class: 기본적으로 function 키워드와 동일하나 명시적으로 객체를 나타낼 수 있게. extends가 존재한다. getter, setter
ES 2016 (ES7)
- Exponentiation operator: 거듭제곱
- Array.prototype.includes
ES 2017 (ES8)
- async - await
- Shared memory and atomics
- Object.values, Object.entries
- Trailing commas in function parameter lists and calls
아직 최신 문법도 다 알지 못하는 상태에서는 버전별 문법의 차이만 하나씩 공부해도 정말 오랜 시간이 걸릴 것 같았다. (실제로 하나씩 공부하다가 정말 오랜 시간이 걸려서 중단했다...) 그래서 let, const이 var의 어떤 점을 보완했는지, JS에도 class가 생겼는데 class가 생성자 함수와 다른 점이 무엇이고 class를 쓴다면 정말 좋은점만 있는지에 대해 중점적으로 알아보았다.
var를 보완한 let, const
- var 키워드는 변수를 중복해서 선언할 수 있기 때문에 의도하지 않은 값을 반환할 수 있다는 문제점이 있다.
- let, const는 변수를 재선언 할 수 없다. 재할당 가능: let / 변하지 않는 상수 값: const
- (하지만 예외적으로 콘솔모드에서는 let 변수를 재선언 할 수 있다.)
- var 키워드는 함수 레벨 스코프를 따르기 때문에 함수 외부에서 선언한 변수는 모두 전역변수로 취급된다.
- let, const 키워드로 선언한 변수는 모두 코드 블록(함수, if, for, while, try/catch문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
- 예시
var myName = 'Hailey' if (true) { var myName = 'JungHyun' }
console.log(myName)
// output : Junghyun let myName = 'Hailey'
if (true) { let myName = 'JungHyun' }
console.log(myName)
//output: Hailey
- 전역 변수가 많아지게 되면 왜 좋지 않은 것일까?
- 전역 변수는 모든 코드가 전역 변수를 참조하고 변경할 수 있다. 유효 범위가 크면 코드의 가독성이 나빠지고 의도치 않게 상태가 변경되어 위험성이 높아진다.
- 변수의 생명 주기는 메모리 공간 확보 → 메모리 공간 해제 → 가용 메모리 풀 반환 으로 이루어진다. 함수 내부에서 선언된 지역 변수는 함수가 호출되면 생성되고 함수가 종료하면 소멸한다. 반면, 전역 변수의 생명주기는 애플리케이션의 생명 주기와 같다. 이 긴 생명 주기 때문에 메모리 리소스도 오랜 기간 소비하고, 상태 변경에 의한 오류가 발생할 확률이 높다.
- 파일이 분리되어 있다 해도 전역 스코프를 공유하기 때문에, 다른 파일 내에서 동일한 이름으로 전역 변수나 전역 함수를 재할당 할 경우 예상치 못한 결과를 가져올 수 있다.
- var 키워드는 호이스팅으로 인해 변수 선언문의 이전에 변수를 참조해도 에러를 띄우지 않고 undefined를 반환한다. 이는 코딩 시 정확한 에러를 발견할 수 없다는 문제점이 있다.
- const와 let은 선언 전에 변수를 참조하면 ReferenceError를 내서 잡아준다.
- const와 let은 호이스팅이 안되는 것인가?
- const와 let이 호이스팅이 되지 않는 것은 아니지만, var는 호이스팅 시에 선언, 초기화까지 된 상태라면 let, const는 변수의 선언은 인지되지만 값의 초기화, 할당은 되지 않은 상태이다.
생성자 함수를 보완한 Class
ES6에서 추가된 Class가 클래스 기반 객체지향 모델을 제공하는 것은 아니다. 클래스는 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕(syntactic sugar)이라고 볼 수도 있다.
- 클래스는 함수로 호출될 수 없다. 클래스를 new 연산자 없이 호출하면 에러가 발생한다.
- 생성자 함수는 new 연산자 없이 호출하면 일반 함수로 호출된다.
- 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다. (생성자 함수는 X)
- 클래스 선언은 let과 const처럼 블록 스코프에 선언되며, 호이스팅이 일어나지 않는 것처럼 보인다. (let과 const처럼 TDZ에 영향 받는다. → 클래스 표현식은 호이스팅이 일어나지 않음)
- 함수 선언문은 함수 호이스팅, 함수 표현식은 변수 호이스팅이 발생한다.
- 생성자 함수로 객체 생성하기
const User = function (name, email, address) {
this.name = name; this.email = email; this.address = address;
}
User.prototype.getBooks = function () {
console.log('책을 조회한다');
}
User.prototype.reserveBooks = function () {
console.log('책을 예약한다');
}
const User1 = new User('박정현', 'hailey@barogo.com', '서울시 영등포구');
- 클래스로 객체 생성하기
class User { constructor(name) {
this.name = name; this.email = email; this.address = address;
}
getBooks() { console.log('책을 조회한다'); }
reserveBook() { console.log('책을 예약한다'); } }
const user1 = new User('박정현', 'hailey@barogo.com', '서울시 영등포구');
//클래스 상속 class Admin extends User {
createBook() { console.log('책을 등록한다'); }
}
const admin1 = new Admin('강소라', 'sora@barogo.com', '경기도 화성시');
호이스팅으로 인해 코드가 꼬일 수 있는 단점을 해결해준다는 것과 자동으로 strict모드를 적용시킨다는 점 외에도, class는 객체 생성 시에 좀 더 코드를 읽기 쉽게 만들 수 있는 방법 중 하나인 것 같다. 또한 클래스 기반 언어에 익숙한 개발자가 보다 빠르게 학습할 수 있다.
python을 사용할 때는 db 스키마를 정의하거나, 데코레이터/각종 미들웨어를 만들 때, api를 구성할 때 등 거의 모든 부분에서 class로 객체를 만들고 상속하기도 하면서 기능을 구현했었다. 이어서 함수들로 객체를 생성하고 상속하며 구현하는 JavaScript의 방식을 접하다보니 class의 필요성때문에 JS의 새로운 버전에 class가 포함된 것이 아닌가? 라는 생각을 해보게 되었다. 그런데 새로운 JS의 class를 모든 JS개발자가 선호하지는 않는 것같다. 왜인지 궁금했다.
Using class inheritance in JavaScript is like driving your new Tesla Model S
to the dealer and trading it in for a rusted out 1973 Ford Pinto. - Eric Elliott
Eric Elliott라는 프로그래머는 “자바스크립트에서 클래스 상속을 사용하는 것은 새 테슬라 모델 S를 1973년형 포드 자동차와 교환하는 것과 같다"고 말했다.
- 인스턴스는 클래스의 정의들을 복사하지 않는다.
- 기존의 객체지향 언어에서 클래스를 사용해 인스턴스를 생성하면 클래스의 메서드를 복사해서 자신만의 메서드를 가지고 있는다. 하지만 자바스크립트는 프로토타입 기반이기 때문에 해당 클래스의 프로토타입 프로퍼티를 바꾸면 그로 생성된 인스턴스의 메서드도 바뀐다.
class User { getBooks() { console.log('책을 조회한다'); } } const user = new User(); user.getBooks(); // output '책을 조회한다' User.prototype.getBooks = function() { console.log('책을 예약한다'); }; user.getBooks(); // output '책을 예약한다'
- super는 정적 바인딩 된다.
- 객체의 프로토타입 링크가 다른 객체로 변경되어도 선언 당시의 상위 프로토타입 객체를 찾아간다.
class User { getBooks() { console.log('책을 조회한다'); } } class Admin extends User { getBooks() { super.getBooks() } } const admin = new Admin(); admin.getBooks(); // output '책을 조회한다' const specialUser = { getBooks: function() { console.log('책을 대출한다'); } }; const blackListUser = { getBooks: Admin.prototype.getBooks }; Object.setPrototypeOf(blackListUser, specialUser); blackListUser.getBooks(); // output '책을 조회한다'
메서드를 복사해서 가지고 있는게 아니라면, 초기에 생성된 부모 class의 메서드 정보가 바뀔 때마다 해당 class를 상속받은 다른 class들을 재정의 해주어야 한다. 그렇게 되면 코드 재사용성을 위해 class를 사용하는 의미가 없지 않나 하는 생각이 든다. 또한 super의 정적 바인딩 또한, 코드가 길어지고 복잡해짐에 따라 혼란을 야기할 수 있어 유지보수 측면에서 좋지 않다는 것을 알 수 있다.
버전 변환해보기
babel로 변환해보기
- 변환 전 도서관 프로젝트의 코드 - (createBook)
const createBook = async (ctx) => {
try {
if (!ctx.request.body) {
ctx.throw(400, 'please provide the information');
}
ctx.body = await bookService.createBook(ctx.request.body);
ctx.status = 201;
} catch (err) { ctx.throw(500, err);
}
};
const createBook = async (ctx) => {
try {
if (!ctx.request.body) {
ctx.throw(400, 'please provide the information');
}
ctx.body = await bookService.createBook(ctx.request.body);
ctx.status = 201;
} catch (err) { ctx.throw(500, err);
}
};
- 변환 후 코드 😱
"use strict";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg); var value = info.value;
}
catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
}
else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this, args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
var createBook = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(ctx) {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
if (!ctx.request.body) {
ctx["throw"](400, 'please provide the information');
}
_context.next = 4;
return bookService.createBook(ctx.request.body);
case 4:
ctx.body = _context.sent;
ctx.status = 201;
_context.next = 11;
break;
case 8:
_context.prev = 8;
_context.t0 = _context["catch"](0);
ctx["throw"](500, _context.t0);
case 11:
case "end":
return _context.stop();
}
}
}, _callee, null, [[0, 8]]);
}));
return function createBook(_x) {
return _ref.apply(this, arguments);
};
}();
babel로 변환해보니 async / await가 제너레이터와 프로미스로 구성되어 있다는 것을 확인할 수 있었다.
조금 더 뜯어보고 직접 만들어보고 싶었지만, 아직 프로미스와 제너레이터를 더 학습해야할 것 같다.
그렇지만 babel을 사용하지 않고 직접 변환해봐야하는 이유를 알게 되었다.
트랜스파일러로 변환해보는 것만으로는 언어의 구성방식과 동작원리를 깊게 이해할 수가 없다.
마치며
기존의 문법을 개선하기 위해 나온 새로운 문법이라고 해서 기존의 문법보다 항상 좋은 점만 있는 것은 아닌 것 같다.
Class와 상속의 개념을 사용하고자하는 사람들의 니즈에 의해 Class가 탄생했지만, 그 특성을 잘 알고 있는 사람들만이 잘 사용할 수 있고, 또 반대로 잘 알고 있기 때문에 개정된 문법을 사용하지 않을 수도 있다.
나는 아직 JS의 프로토타입과 객체지향의 개념이 명확하지 않은 상태이며 Class가 야기하는 혼란들을 잘 알고 사용할 자신이 없기 때문에 class 문법은 특별한 경우를 제외하고는 되도록 사용하지 않을 것 같다.
문법 공부만 하면서 일주일을 보낼 수는 없기 때문에 아쉽지만 진행하지 못하게 된 것들이 계속 늘어나고 있다. 탄탄한 기초의 중요성을 느끼고 있다. 이번 주제를 통해서는 제너레이터, 프로토타입과 호이스팅 등을 접하게 되었는데, 근시일 내에 더 깊게 공부해 볼 필요성을 느꼈다. 과제의 기능들을 빨리 구현해서 완성하는 것도 물론 중요하지만 내가 무엇을 모르는 지를 빨리 캐치하는 것과 이런 기본적인 부분에 대한 깊은 이해가 시급하다!
참고자료
'JavaScript' 카테고리의 다른 글
[JavaScript] 서버 간 axios 통신과 interceptor로 실패 요청 retry 하기 (0) | 2023.03.12 |
---|---|
[JavaScript] JavaScript의 역사 (0) | 2022.09.18 |
[JavaScript] JS의 비동기 처리 코드 작성 방식 (0) | 2022.07.24 |
[JavaScript] script언어와 Type언어란? (0) | 2022.05.15 |