Blogging - Prototype Chain
Bare mininum requirements
- ES6
class
키워드 및super
키워드 이용 방법을 알아봅니다.Prototype
상속하는 방법을 알아봅니다.
이번 시간에는 JS의 상속 패턴에 관해서 알아보자!!
일단 이 글을 보기 전에 객체지향 프로그래밍 in Javascript와 Prototype in JS를 먼저 보는 것을 추천한다.
Prototype은 객체의 원형이다. class가 없는 자바스크립트는 객체의 원형인 프로토타입을 이용해 복사와 객체특성을 확장해 나가는 방식으로 새로운 객체를 생성한다.
다시 말해서, JS에서 class는 새로운 기능이 아니라, 이미 있는 기능에서 문법만 추가해놓은 양념같은 애이다. JS는 어디까지나 프로토타입 기반 언어이지, class
를 사용할 수있다고 해서 class
기반 언어가 된 것은 아니다. 결론은, JS class는 class
를 흉내낸 문법일뿐이다.
어찌됐든, JS에서 상속을 할 수 있는 방법은 3가지가 있다.
1. class를 이용하는 방법
2. 객체들끼리 직접 상속하는 방법
3. Prototype을 이용하는 방법
=> 사실 2번과 3번은 본질적으로 같지만, 일단 구분하겠다.
하지만, 상속에 대해서 본격적으로 들어가기 전에, 먼저 class
와 prototype
의 사용법부터 알아보자.
class
와 prototype
모두 단 한가지의 기능을 위해 구현됐다. 바로 객체를 찍어내기 위한 역할이다.
=> 객체의 재사용성을 높이기 위한 용도로써, 만들어졌다.
상속에 들어가기에 앞서, class
와 prototype
은 어떤 방식으로 객체를 만들어내는지 무슨 차이가 있는지부터 알아보자.
프로토타입의 강점인 자유롭게 객체의 구조와 동작방식을 바꿀 수 있다는 점에 대해서 알아보자!!
function Person(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
this.sum = function () {
return this.first + this.second;
};
}
let kim = new Person("kim", 10, 20);
let lee = new Person("lee", 30, 40);
위의 코드에서는 kim
과 lee
를 Person
이라는 생성자를 통해서 찍어냈다.
그런데, 여기서 this.sum
이 거슬린다.
sum
이라는 함수는 생성되는 객체마다 고유한 특징을 가지는 함수가 아니다. 그저, 모든 객체에게 코드 한 줄 다르지 않는 동일한 역할을 하는 함수이다.
문제는 sum()
은 객체가 생성될 때마다 새로 만들어지고 있다는 것이다. 그만큼, 새 객체가 생성될 때마다, sum()
을 생성하는 시간이 들 것이고, 메모리가 사용될 것이다.
=> 즉, 이런 접근법은 생산성이 많이 떨어진다.
만약, 우리가 모든 객체들이 동일하게 사용하는 함수를 단 한 번만 생성해서 모든 객체들이 사용하게 할 수있다면, 함수가 매번 생성되는 시간과 메모리를 절약할 수 있다.
어떻게 이것을 실행할 수 있을까?? => 이때, prototype이 등장한다.
function Person(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
}
let kim = new Person("kim", 10, 20);
let lee = new Person("lee", 30, 40);
Person.prototype.sum = function () {
return "프로토타입 " + (this.first + this.second);
};
생성자 함수 안에 sum()
을 정의하는 것이 들어가 있지 않기 때문에, 새로운 객체가 생성자를 통해서 만들어질 때마다, sum()
이 실행되지 않는다. 즉, 딱 한 번만 실행되기 때문에, 성능과 메모리를 절약할 수 있다.
여기서 추가적으로, 만약에
kim
이라는 객체의sum()
은 다르게 동작하게 하고 싶다면 어떻게 해야 할까??
kim.sum = function (params) {
return "kim만의 위해 변형된 " + this.first + this.second;
};
JS는 kim
라는 객체 자신이 sum()
을 가지고 있지 않으면, 이 객체 생성자인 Person
의 메소드에 정의되어 있는지를 찾아본다.
다시 말해서, JS 엔진은 kim이라는 객체에서 먼저 sum()
을 찾기 때문에, kim.sum
을 Person.prototype.sum
보다 먼저 찾게 되고, 실행시킨다.
하지만 lee
객체는 내부에 sum()
이 없기 때문에, Person
객체까지 거슬러올라가게 되고 거기서 Person.prototype.sum
을 찾아 실행시킨다.
이제 Person
이라는 객체의 원형을 class
방식으로 만들어보자!!
class Person {}
let kim = new Person();
// Person을 기반으로 한 객체가 생성됐다.
console.log("kim", kim);
하지만, 이런 방식은 완벽하게 똑같은 객체들만을 찍어낼 수 있을 뿐이지, 찍어내는 객체들마다 고유한 특성을 담기 위해서는 값을 넘겨줘야하는데, 어떻게 가능할까??
class는 객체를 만드는 공장이다.
생성자와는 다르게, class에서는 생성되는 객체를 어떻게 초기화 시키는가??
객체의 초기 상태 설정은 class는 어떻게 함??
객체가 만들어지기 전에, 자동으로 실행되는 함수가 있다. 그 함수의 이름은 construcotr(){ }
이다.
반드시 이 이름 그대로 써야한다.
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
}
여기에 sum()
이라는 함수를 메소드로 넣어보자!
Person.prototype.sum = function () {
return "프로토타입 " + (this.first + this.second);
};
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
sum() {
return "프로토타입 " + (this.first + this.second);
}
}
let kim = new Person("이것은 이름", "퍼스트", "세컨드");
console.log("kim", kim);
console.log("kim", kim.sum());
그런데, kim
이라는 객체만큼은 sum()
함수의 동작을 다르게 하고 싶다면??
kim.sum = function(){...}
이런 식으로 만들면 된다.
class
안에 정의된 변수들은 모두 field
라고 부른다. 그리고 함수들은 method
라고 부른다.
또한, field
만 들어있는 class
를 Data Class라고 부른다.
아마 혼자서 코딩을 한다면, 상속을 사용할 일이 없을 것이다. 어쩌면, 그런 이유로 상속이 중요하지 않다고 생각할 수있다. 하지만, 상속은 반드시 알아야할 개념 중에 하나이다. 왜 그렇게까지 알아야 하는 개념일까??
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
sum() {
return (this.first + this.second);
}
}
예를 들어 Person
에다가 avg()
라는 메소드를 추가하고 싶다고 해보자!!
가장 쉬운 방법은
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
sum() {
return (this.first + this.second);
}
avg() { // 추가된 코드
return (this.first + this.second) / 2;
} ///////////////
}
이렇게 Person
안에 직접적으로 추가하는 것이다.
그러나, 개발자가 항상 이런 일을 할 수 있는 것은 아니다.
Person
이라는 class
가 내가 만든 것이 아니라, 다른 개발자가 만든 것이거나, 라이브러리에서 받아온 것이라면, 직접적으로 수정하는 것은 좋은 방법이 아니다.
그리고 자신이 짠 코드라고 해도, 생성된 객체들에게 avg()
가 거의 필요없는 메소드 일 수도 있다.
그렇다면, avg()
를 Person
에 추가하는 것은 비효율적일 수 있다.
물론 상속을 사용하지 않고, avg()
가 포함된 class
를 하나 더 만들어서 문제를 해결할 수도 있다.
// avg()가 추가된 PersonPlus라는 class를 별도로 생성
class PersonPlus {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
sum() {
return (this.first + this.second);
}
avg() {
return (this.first + this.second) / 2;
}
}
let cho = new PersonPlus("kim", 10, 20);
console.log("cho.sum()", cho.sum());
하지만, 이러한 접근법은 뭔가 아쉽다.
프로그래머들이 가장 피하고 싶은 것이 하나 있다면, 코드의 중복일 것이다.
PersonPlus
는 avg()
를 제외한 모든 코드가 Person
과 동일하다. 즉, 너무 많은 코드가 중복된다.
이런 코드의 중복을 제거하기 위해서, 할 수 있는 방법이 상속이다.
class PersonPlus extends Person {
avg() {
return (this.first + this.second) / 2;
}
}
let cho = new PersonPlus("kim", 10, 20);
console.log("cho.sum()", cho.sum());
이렇게 상속을 통해서, 중복되는 코드의 양을 줄였다.
또한, 부모 클래스를 수정하면, 상속받는 자식들도 자동으로 부모의 속성이 변경되기 때문에, 유지보수 측면에서도 편리해진다.
=> 이것이 상속의 위력이다.
우리가 어떤 기능을 도입하게 되면, 제일 먼저 보이는 것은 그 기능의 장점이다. 그러나, 새로운 기능은 항상 눈에 보이지 않는 단점이 있다.
상속을 도입했을 때, 발생하는 문제는 무엇인가??
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
console.log("constructor");
}
sum() {
return this.first + this.second;
}
}
class PersonPlus extends Person {
avg() {
return (this.first + this.second) / 2;
}
}
// 만약 PersonPlus에 third 라는 4번째 인자를 받고 싶다면, 어떻게 해야 할까??
let cho = new PersonPlus("kim", 10, 20);
console.log("cho.sum()", cho.sum());
위의 코드에서, PersonPlus
에 third
라는 4번째 인자를 받고 싶다면, 어떻게 해야 할까??
가장 직관적인 방법은 다음과 같다.
// Person을 직접적으로 수정하는 게 아니라면,
// class 생성자에 Person을 직접적으로 넣어주고, 수정하는 방식이 있다.
class PersonPlus extends Person {
constructor(name, first, second, third) {
this.name = name;
this.first = first;
this.second = second;
this.third = third;
}
sum() {
return this.first + this.second + this.third;
}
avg() {
return (this.first + this.second + this.third) / 3;
}
}
그러나, 이렇게 되면, 상속을 쓰는 의의가 없어진다.
이런 경우를 위해서, 부모가 할 수 있는 일은 부모가 하고, 부모가 하지 못하는 일은 나만 하도록 하는 것 그것이 super()
이다.
super는 2가지의 사용법이 있는데,
1.super()
가 되면, 부모 클래스의 생성자 이다.
2.()
가 없으면,super
는 부모클래스 자체를 뜻한다.
만약 super
라는 기능이 없으면, 자식클래스에서 부모클래스의 속성과 기능에 추가적인 무언가를 넣어 활용때 다시 부모클래스의 코드를 사용해야하는 중복이 발생할 것이다.
class PersonPlus extends Person {
constructor(name, first, second, third) {
//
super(name, first, second);
this.third = third;
}
sum() {
// 부모 클래스의 함수를 먼저 실행 후,
// 그 결과가 return되서 third를 더해주는 추가적인 작업을 한다.
return super.sum() + this.third;
}
avg() {
return (this.first + this.second + this.third) / 3;
}
}
이렇게 super
와 extends
를 이용해서, 우리가 원하는 바를 더 짧은 코드로 구현할 수 있다.
JAVA와 같은 전통적인 클래스 기반 언어들은 상속을 부모 클래스 ( super class ) 와 자식 클래스 ( sub class )를 통해서 객체를 생성해낸다. 그렇기 때문에, 해당 객체가 어떠한 기능을 갖게될 것인지는 class
에서 결정된다.
Parent class = super class = 부모 클래스
child class = sub class = 자식 클래스
그런 이유로, 이미 만들어진 객체가 다른 객체의 상속을 받는다던지 하는 것은 불가능하다. 그냥 객체는 태어나면, 본인이 어떤 기능을 갖게 될지가 결정되는 것이다.
물론, JS에서도 class
라는 문법이 있고, extends
라는 키워드가 있지만, 그것은 그냥 장식에 불과하다. 결국, JS가 동작하는 내부 매커니즘이 바뀐 것은 없다. => 그냥 class
는 다른 언어를 썼던 개발자들이 편하게 쓰도록 도입한 문법일 뿐이다.
그런데, JS는 이보다 훨씬 더 자유롭다. 그러나, 그만큼 복잡하고 여러가지 혼란스러운 점들이 있다.
그렇다면 JS는 상속을 어떻게 구현하는가??
전통적인 클래스 기반 언어들은 클래스를 상속 받는데, JS에서는 객체가 직접 다른 객체의 상속을 받을 수 있다. 그리고 개발자가 얼마든지 그 상속관계를 바꿀 수 있다.
예를 들어, 현재 super object를 바꿔주고 싶을 때는 그냥 link만 바꿔주면 된다. 그 링크를 prototype link라고 한다. 이 prototype link가 가리키고 있는 객체를 prototype object라고도 부른다.
super object = prototype object = 부모 객체
sub object = 자식 객체
prototype link
와 prototype object
에 관해 더 자세히 알아보고 싶다면, 필자가 정리한 Prototype in JS를 보는 것을 추천한다.
자바스크립트에서 객체를 통해 직접 다른 객체로부터 상속받는 방법은 2가지가 있다.
다른 말로 하자면, prototype link를 바꿔줄 수 있는 방법이 2가지가 있다.
__proto__
let superObj = { superVal: "super" };
let subObj = { subVal: "sub" };
subObj.__proto__ = superObj;
console.log("subObj.subVal =>", subObj.subVal);
console.log("subObj.superVal =>", subObj.superVal);
// 비록 프로토타입 링크로 이어졌다고는 해도,
// 자식 객체에서 부모로부터 상속받은 속성을 변경시켜봤자
// 그 변화는 자식 객체 한정이라는 것을 알 수있다.
// 즉, 부모 객체에서는 자식 객체에서의 변화에 반응하지 않는다.
subObj.superVal = "sub";
console.log("superObj.superVal =>", superObj.superVal);
__proto__
을 통해 간단하게 link만 바꿔주면 되니, 다른 객체를 상속받을 수 있다. 즉, class
와 다르게, 상속 자체가 굉장히 유연해진다.
__proto__
상속 방식은 JS에서 표준으로 인정하지 않고 있다.
이제는 JS에서 권장하는 정통 상속 방식에 대해서 살펴보자!!
Object.create()
__proto__
를 직접적으로 사용하지 않기 위해서 나온 방식이다.
let superObj = { superVal: "super" };
// let subObj = { subVal: "sub" };
// subObj.__proto__ = superObj;
///
// 위에 주석 처리된 코드와 똑같이 동작하는 코드이다.
// subObj는 superObj를 부모로 하는 새로운 객체이다.
let subObj = Object.create(superObj);
///
console.log("subObj.subVal =>", subObj.subVal);
console.log("subObj.superVal =>", subObj.superVal);
subObj.superVal = "sub";
console.log("superObj.superVal =>", superObj.superVal);
__proto__
에서 봤던 예제 코드를 Object.create()
방식으로 바꿔봤다. 사용법에 큰 변화는 없지만, prototype link
를 지정해줄 때, __proto__
보다 Object.create()
방법을 더 권장한다.
예를 들어, kim
이라는 객체 안에서 sum()
이 있는데, lee
라는 객체에서는 코드 중복을 피하기 위해, kim
이 가지고 있는 메소드를 직접적으로 추가하고 싶지 않다면 어떻게 할 수 있을까??
=> 이때, 객체들끼리의 상속을 활용할 수 있다.
__proto__
방법let kim = {
name: "kim",
first: 10,
second: 20,
sum: function () {
return this.first + this.second;
},
};
그리고 lee에서만 사용하고 싶은 기능이 있다면,
예를 들어, 평균을 구하는 avg()를 추가해보자
let lee = {
name:'lee',
first:30 , second: 40,
avg:function () {
return( this.first+this.second )/2;
}
}
lee.__proto__ = kim;
console.log("lee.sum() : ", lee.sum()); // lee.sum() : 70
위의 코드와 같이, kim
의 sum()
을 상속을 통해 사용할 수있고, 동시에 kim
에는 없는 lee
만의 고유한 값들을 가질 수도 있다.
Object.create()
방법let kim = {
name: "kim",
first: 10,
second: 20,
sum: function () {
return this.first + this.second;
},
};
let lee = Object.create(kim);
lee.name = 'lee';
lee.first = 30;
lee.second = 40;
lee.avg = function () {
return (this.first+this.second)/2;
}
console.log("lee.sum() : ", lee.sum()); // lee.sum() : 70
console.log("lee.avg() : ", lee.avg()); // lee.avg() : 35
마지막으로, class
출시 이전에 사용했던 Prototype
을 이용해서 상속하는 방법에 대해서 알아보자!! 이 방법을 생성자를 이용하는 방법이라고 부르기도 한다.
( 본질적으로 둘은 같은 의미지만, 내용이 산으로 갈 수 있어서, 이렇게만 언급하겠다 )
class
로 상속하는게 훨씬 쉽고, 잠재적인 에러가 생길 가능성도 적어서, class
상속을 쓰는 게 훨씬 좋다.
하지만, 아직도 어딘가에서는 쓰이고 있는 코드들을 이해하기 위해서, 그리고 실질적으로 JS의 상속이 작동하는 원리를 이해하기 위해서 알아보자!!
좀 더 쉬운 이해를 위해, 동일하게 작동하는 코드를 class
와 prototype
방식으로 작성해보자!
class Person {
constructor(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
}
sum() {
return this.first + this.second;
}
}
let lee = new Person("lee", 10, 20);
console.log("lee.sum() is ", lee.sum()); // lee.sum() is 30
class PersonPlus extends Person {
constructor(name, first, second, third) {
super(name, first, second);
this.third = third;
}
sum() {
return super.sum() + this.third;
}
avg() {
return (this.first + this.second + this.third) / 3;
}
}
let kim = new PersonPlus("kim", 10, 20, 30);
console.log("kim.sum() is ", kim.sum()); // kim.sum() is 60
console.log("kim.avg() is ", kim.avg()); // kim.avg() is 20
// 부모가 될 Person 생성자
function Person(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
}
let lee = new Person("lee", 10, 20);
console.log("lee.sum() is ", lee.sum());
Person.prototype.sum = function () {
return (this.first + this.second);
};
이제 자식 생성자가 될 Personplus를 작성해보자!
function PersonPlus(name, first, second, third) {
// 이렇게 해도 작동은 한다.
// 그러나, 이게 몇 작성해야되는 값이 몇 천개라면??
// 이 방법은 코드 중복 + 시간 낭비이다.
this.name = name;
this.first = first;
this.second = second;
this.third = third;
}
위와 같이 해도 작동은 한다. 그러나, 이게 몇 작성해야되는 값이 몇 천개라면??
이 방법은 코드 중복 + 시간 낭비이다.
그래서, 우리는 Person
이라는 함수를 상속받아서 이런 중복을 줄여주고 싶다.
그럼 그 때에 무엇에만 집중하면 되냐면, this
에만 집중하면 된다.
접근법 2
이렇게 하면 될까??
function PersonPlus(name, first, second, third) {
Person(name, first, second);
this.third = third;
}
안 된다!!
왜냐면 Person
에 name
값이 들어간다고 해도Person
에서의 this
는 우리가 만든 Personplus
에서 만들어질 객체의 this
아니다.
또한, Person
은 앞에 new
가 안 붙었기 때문에 생성자가 아니다.
그럼 대체 어쩌란 말인가??
PersonPlus()
가 new
를 통해서 만들어지는 객체 kim
의 this
값을 Person
에 보내줘야한다.
function PersonPlus(name, first, second, third) {
Person.call(this, name, first, second);
this.third = third;
}
call
메소드에 의해서 실행되는 Person
은 우리가 생성하려고 하는 Personplus
의 this
이기 때문에, 저 생성자를 통과시키면, 우리가 원하는 일을 할 수 있다.
그리고 우리가 추가적으로 필요한 third
값에 대한 작업만 해주면 된다.
결과적으로,
Person.call(this, name, first, second);
// class에서의 super 역할을 하고 있다.
// super( name, first, second );
둘은 같은 일을 하고 있다.
결국, JS OOP의 핵심은 this
값을 제대로 설정해주는 가이다.
하지만, 이게 끝이 아니다. 아직
Person
의 메소드인sum()
을 상속 받지 못했다.
// 방법 1.
PersonPlus.prototype.__proto__ = Person.prototype;
(자식 객체).prototype.
__proto__
= (부모 객체).prototype;
그러나, 이것은 JS 표준이 아니다.
// 방법 2.
PersonPlus.prototype = Object.create(Person.prototype);
// PersonPlus.prototype 이라는 새로운 객체를 만든다.
(자식 객체).prototype = Object.create( (부모 객체).prototype);
둘 중에 어떤 것을 사용해도 부모의 메소드들을 상속 받을 수 있게 된다.
하지만, 방법 2
는 방법 1
에서는 발생하지 않는 문제 발생한다.
kim.constructor
를 출력해보면, kim
의 생성자가 f Person()
로 찍힌다.
이상하지 않은가??
이에 대한 해답은 의외로 간단하다.
PersonPlus.prototype = Object.create(Person.prototype);
이 방법은 PersonPlus.prototype
이라는 새로운 객체를 만든다. 원래 PersonPlus.prototype
의 생성자는 PersonPlus
를 가리키고 있었을 텐데, 그것을 Person
으로 replace시켜버린다.
정리하자면, Object.create(Person.prototype)
를 통해서 만들어진 객체는 Person
프로퍼티를 prototype
으로 둔 객체이기 때문에, 생성자도 Person
을 가리킬 수 밖에 없다.
그렇기 떄문에, 개발자가 임의로 올바른 생성자를 지정해줘야 하는 아쉬운 점이 발생한다.
임의 지정은 다음과 같이 해주면 된다.
PersonPlus.prototype.constructor = PersonPlus;
(자식 객체).prototype.constructor = (자식 객체);
추가적으로,
우리는 (객체 이름).constructor
를 통해 해당 객체가 어디로부터 만들어 졌는지 알 수 있다.
또한, new
를 함께 쓰면 해당 객체의 출신 공장을 몰라도 객체를 찍어낼 수 있다.
const d = new Date();
Date.prototype.constructor === Date; // true
d.constructor; // ƒ Date()
d2 = new d.constructor(); // 2022-01-05T19:55:30.543Z
function Person(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function () {
return this.first + this.second;
};
// let lee = new Person("lee", 10, 20);
// console.log("lee.sum() is ", lee.sum());
function PersonPlus(name, first, second, third) {
Person.call(this, name, first, second);
this.third = third;
}
// PersonPlus.prototype.__proto__ = Person.prototype;
// 그러나, 이것은 JS 표준이 아니다.
PersonPlus.prototype = Object.create(Person.prototype);
// Object.create(Person.prototype) 사용시,
// PersonPlus로 생성된 객체의 생성자가 Person으로 찍히게 때문에,
// 해줘야 하는 조치
PersonPlus.prototype.constructor = PersonPlus;
PersonPlus.prototype.avg = function () {
return (this.first + this.second + this.third) / 3;
};
let kim = new PersonPlus("kim", 10, 20, 30);
console.log("kim.sum() is ", kim.sum()); // kim.sum() is 30
console.log("kim.avg() is ", kim.avg()); // kim.avg() is 20
console.log("kim.constructor is ", kim.constructor);
// 'kim.constructor is ' ƒ PersonPlus()
" Can I use "를 통해 본인이 짠 코드가 특정 브라우져에서 지원되지 않는 문법이라면, 어떻게 할 것인가??
이때, 개발자들을 도와줄 수 있는 있는 구원자가 babel
이다.
최신 문법이 지원되지 않는 곳에서는 이전 방식으로 코드를 짜야하는데,
이런 상황에서 내가 짠 최신코드를 구형 버전으로 자동 변환 시켜주는 웹싸이트!!
https://babeljs.io/
물론, 구형 문법을 최신 문법으로도 변환시켜준다.
상속에 대한 정리를 끝내고, 생각해보니... class
를 사용하는 게 코딩에선 더 효율적이다.
다만, 자바스크립트의 동작원리와 깊은 개념을 이해하는 목적으로 constructor
를 탐구해보는 것은 의미가 있는 것같다.
JS가 공부하면 할 수록 복잡한 이유는 JAVA나 C++처럼 제대로된 설계도 위에서 만들어진 언어가 아니라!! 일단 만들어놓고, 불편하니 바꾸고, 다른 언어 사람들이 이해하기 쉽게 뭔가를 비슷하게 짚어넣고!! 이런 반복으로 지금은 왜 필요한지도 모르겠는 문법들까지 지저분하게 많아진 것같다.
JS 중급자들이라면, 최신 문법도 배워야하고, 어딘가에서 쓰이고 있을지 모를 "레거시 코드"를 이해하기 위해서, 구형 문법도 학습해야 되는 비효율적인 학습량의 증가가 일어나는 것같아서 씁쓸하다.