생활코딩 — JavaScript 객체 지향 프로그래밍

hansol yang
19 min readDec 27, 2022

--

회사에서 연말 방학을 줘서 그간 보고 싶었던 강의, 책을 보고 있다.

그중에서 조금씩 듣고 있던 “JavaScript 객체 지향 프로그래밍” 강의가 끝으로 가고 있다.

강의를 들으며 정리한 내용들을 옮겨본다.

강의 전체의 내용을 아우르진 않고, 노트해뒀던 부분만 옮기니 필요한 내용이 있다면 강의 영상 참고.

JavaScript Object Oriented Programming

13.4_객체상속의_사용

먼저 kim 이라는 객체를 생성.
동작 확인. ok.
lee 라는 객체를 만들려고 보니 sum 을 또 만들기 싫음.
그러면 어떻게 하면 될까? => proto 를 이용.
일단 lee 에서 name, first, second 까지 정의
lee 의 **proto** 를 kim 으로 설정. (kim 을 상속 받음.)
lee.sum() => 20 출력. ok.
lee 에는 sum 이 없는데 어떻게?
=> lee.sum() 이 호출되면 lee 라는 객체에 sum 이 있는지 찾아봄.
=> 하지만 없음. 그러면 자바스크립트는 저 객체의 **proto** 의 프로퍼티로써 sum 이 있는지를 찾아봄.
=> 있음. 그러면 여기의 sum 이 실행됨. 이때 sum 내부의 this 는 kim 이 아닌 lee 의 객치임.
=> 근데 이 때 sum 이 화살표 함수라면 NaN 이 출력 됨. 화살표 함수에서 this 는 undefined.
*
lee 에서만 추가하고 싶은 기능이 있음. avg 같은 기능.
그런 경우에는 lee 에다가 구현하면 됨.
*
**proto** 를 이용해서 상속을 받는 등의 유연함은 편리함을 제공하지만 혼란스럽기도 함.
*
Object.create 로 바꿔보자.
// use Object.create
// kim 을 prototype object 로 하는 lee 라는 객체를 만들어줘.
const lee_object_create = Object.create(kim);

14.2._call

call
객체 안에 sum 이라는 메서드를 직접 만들지 않음. 근데 필요는 한 상황.
객체 바깥에 sum 을 만듦.
지금까지 sum 은 객체 안에 있는 this 의 first 와 second 를 더하는 역할을 해옴.
바깥에 있는 지금은 어떻게 쓰면 될까?
call 을 쓰면 됨. sum.call();
sum.call() 은 sum() 과 같음.
그러면 call 의 역할은?
모든 함수는 call 이라고 하는 메서드를 가지고 있음.
자바스크립트에서는 함수도 객체이기 때문.
call 이라는 메서드에 첫 번째 인자로 kim 을 주면,
sum 이 갖고 있는 this 는 kim 이 됨.
따라서 sum.call(kim) => 30, sum.call(lee) => 20
sum 이라는 함수는 kim 또는 lee 의 멤버가 아니었음.
근데 call 이라는 메서드를 통해서 sum 이 내부적으 사용할 this 를 kim 또는 lee 로 지정했더니
sum 이라는 함수가 각 객체의 멤버가 된 것.
같은 원리로
sum.call({first: 1, second: 1}) 는 2 가 됨.
sum 에 파라미터가 있다면 call 메서드의 두 번째 인자부터 넣으면 됨.
sum 을 function sum(prefix: string): string 와 같은 식으로 정의 한다면
다음과 같이 호출하면 됨. sum.call(kim, 'kim')
call 은 결국 context 를 바꾸는 명령임.

14.3.bind

bind
sum 을 호출할 때마다 this 를 넣어주지 않고
아예 내부적으로 딱 다른것으로 고정시켜 버리는게 bind.
bind 의 첫 번째 인자는 함수 내부적으로 this 를 뭘로 쓸꺼냐 하는 것.
sum.bind() 를 하면 새로운 함수가 만들어짐.
두 번째 자리부터는 함수가 호출될 때마다 사용 될 인자를 넣을 수 있음.
const kimSum = sum.bind(kim, "sum bind kim: ");
console.log(kimSum()); //=> sum bind kim: 30
sum 은 바뀌지 않고 sum 이 bind 를 한 취지에 맞게 바뀐 새로운 함수가 만들어져서 리턴 되는 것.
즉, 기존 sum 에는 영향을 주지 않음.
call 은 실행 할 때읜 context 즉, this 를 바꾼다.
bind 는 어떤 함수의 this 의 값을 영구적으로 바꾸는 새로운 함수를 만들어 낸다.

15.prototype_vs_proto

prototype vs proto
물리학자가 아니면서, 양자역학 다큐멘터리를 보고 있다. 정도로 보면 됨.
자주 쓰이거나 꼭 알아야 하는 것은 아님.
너무 많은 힘을 들이지는 않아도 된다는 말.
이걸로 씨름할 시간에 앞에서 배웠던 걸 더 할 것.
앞에 있는 것일 수록 쓸모있는 것. 뒤에 있는 것일 수록 시험에 나오거나 유식한 척 하기 좋은 것.
근데 그게 뭐가 중요한가요. 그쵸? 일을 하는게 중요하죠.
자 그러니까 여러분들 너무 이해 안간다고 심란해 할 필요도 없고 너무 노력 안하셔도 괜찮은 파트입니다.
근데 또 노력을 해야지 이해할 수 있는 친구기도 해요 문제는.ㅎㅎ
함수란 무엇인가?
함수는 생긴것만 보면 statements 같음. 조건문이나 반복문 같은.
근데 사실 객체임.
그래서 var Person = new Function(); 과 같이 표현할 수 있음.
여기서, 자바스크립트의 함수들은 객체이기 때문에 **프로퍼티**를 가질 수 있음.
function Person(name: string, first: string, second: string) {
this.name = name;
this.first = first;
this.second = second;
}
이렇게 함수를 정의하면 객체이기 때문에 저 함수에 해당되는 Person 이라는 새로운 객체가 생성됨.
그런데 객체가 하나 더 생성됨. Person 의 prototype 객체임.
즉 함수 하나를 정의하면 객체가 2개 생김.
Person 이라는 객체에는 prototype 이라고 하는 프로퍼티가 생기고 그 프로퍼티는 Person 의 prototype 객체를 가리킴.
따라서, Person.prototype 이라고 하면 그건 Person 의 prototype 객체를 가리키는 것.
Person 의 prototype 객체도 자신이 Person 의 소속이라는 것을 표시하기 위해서 어딘가에 기록해둬야 함.
그러기 위해서 constructor 라고 하는 프로퍼티를 만들고
그 프로퍼티는 Person 을 가리키게 됨.
서로간의 상호 참조를 하고 있는 것임.
Person 은 prototype 프로퍼티를 통해서, Person's prototype 은 constructor 라고 하는 프로퍼티를 통해서.
Person.prototype.sum = function () {}; 와 같이 정의를 하면
이건 어떤 의미를 갖냐면
Person 의 prototype 을 찾아가고, 그건 Person's prototype 객체인데,
거기에 sum 이 없으니까 거기에 함수 정의를 하게 됨.
그러면 이제 객체를 찍어내는 공장인 Person 이라고 하는 constructor function 을 만든 것.
그러면 이 상태에서 객체 생성. var kim = new Person('kim', 10, 20)
그러면 kim 이라는 객체가 생길 것이고 그 김은 다음과 같이 구성 됨.
kim {
__proto__
name
first
second
}
이 때 __proto__ 가 생성되고, __proto__ 는 Person's prototype 을 가리킴.그러면 Person.prototype 을 통해서도, kim.__proto__ 를 통해서도 Person's prototype 에 접근하게 되는 것.
비슷한 형태로 lee 라는 객체를 생성해도 마찬가지.
이런 상태에서 kim.name 을 출력하려고 하면,
자바스크립트는 kim 이라고 하는 객체의 name 이라는 프로퍼티가 있는지를 찾아봄.
있음. 그러면 name 이라는 프로퍼티에 저장된 값을 출력함.
혹사니 name 이라고 하는 값이 없다면 __proto__ 가 가리키는 객체에 name 이 있는지를 다시 찾아봄.
kim.sum() 을 한다면?
kim 이라는 객체에는 sum 이라는 메서드가 없음.
그러면 자바스크립트는 __proto__ 를 통해서 __proto__가 가리키고 있는 Person's prototype 에 sum 이 있는지를 찾아봄.
있음. 그러면 kim 의 __proto__ 를 겨쳐 Person's prototype 의 sum 을 사용함.
만약에, Person's prototype 에도 없다면 Person's prototype 의 __proto__ 를 쫓아서 탐색을 계속 함.
생성자(생성자 함수) 에는 prototype 이, 생성된 쪽 에는 __proto__ 가 생기는 건가?
만약 본인이 언어 설계자 였다면 함수의 prototype 이라는 이름은 __prototypeObject__ 로,
그리고 각각의 객체, 인스턴스에 생성되는 __proto__ 는 __protoLink__ 로 했을 것이라고 하심.
이렇게 이해나는게 좀 더 이해가 된다.
함수에 있는 prototype 객체,
그리고 객체, 인스턴스에 있는 prototype link, chanin 을 제공하는 __proto__.
prototype 은 원류.
__proto__ 는 그 원류로 부터 흐르고, 그 원류를 쫓아 가는 링크.

16.1. 생성자 함수를 통한 상속

상속은 유용한데, 복잡함.
자바스크립트가 유연하다 보니 상속하는 방법도 여러가지.
상속 방법
- 객체와 객체가 직접 상속하는 방법
- 클래스 또는 컨스트럭터 펑션을 통해서 상속하는 방법
클래스 문법을 이용해서 상속하는게 쉽다. 문제가 생길 가능성도 보다 적다고 생각함.클래스 이전에 상속하던 방법을 먼저 살펴볼 것. 프로토타입 사용해서.
실제로는 클래스랑 같은 기능이기 때문에 클래스를 쓰면 됨.

16.2. 생성자 함수를 통한 상속 : 부모 생성자 실행

constructor inheritance
// constructor function
function Person(name: string, first: number, second: number) {
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function () {
return this.first + this.second;
};
function PersonPlus(
name: string,
first: number,
second: number,
third: number
) {
// Person(name, first, second) 로는 안됨. this가 바뀌기 때문에.
// Person 함수를 통해서 초기화 하고 사용하고 싶은 this를 넘겨줘야 함.
// call 을 통해서 PersonPlus 라고 하는 생성자가 new 호출이 될 때 만들어지는 객체인 this 를 인자로 줌.
// 그 외에는 PersonPlus 추가적으로 필요한 작업(third) 만 해주면 됨.
// 즉, 여기서 Person.call(this, name, first, second) 은 클래스를 사용할 때의 super(name, first, second) 와 같은 역할을 함.
// 결국 this 가 중요함. 문맥, 맥락을 결정함.
Person.call(this, name, first, second);
this.third = third;
}
PersonPlus.prototype.avg = function () {
return (this.first + this.second + this.third) / 3;
};
let kim = new PersonPlus("kim", 10, 20, 30);

16.3. 생성자 함수를 통한 상속 : 부모와 연결하기

call 을 통해서 super 와 비슷한 동작을 하더라도 부모와 실제로 연결된 것은 아님.
예를 들어 sum 같은 함수는 상속되지 않은 상태. //=> kim.sum is not a function
현재의 상태
Person 은 prototype 을 가지고 있고
Person's prototype 은 constructor, sum 을 가지고 있음.
PersonPlus 는 prototype 을 가지고 있고
PersonPlus's prototype 은 constructor, avg, __proto__ 를 가지고 있음.
여기서 PersonPlus 를 new 연산자를 통해 kim 이라는 객체를 만듦.
kim 은 __proto__, name, first, second, third 를 가짐.
kim 의 __proto__ 는 자신은 생성한 생성자의 prototype 이 가리키는 객체를 가리킴.
그 객체는 PersonPlus's prototype.
이 상태에서 kim.avg() 를 호출하면?
kim 에 avg 가 있나? 없음. => __proto__ 를 따라서 탐색. => PersonPlus's prototype 객체에 있는지 확인 => 있음. => 실행
kim.sum() 을 호출하면?
kim 에 없음. => __proto__ 를 따라서 탐색. => PersonPlus's prototype 확인 => 없음. => 없다고 판단. 에러 발생 시킴.
이러한 상태에서 PersonPlus's prototype 에서 sum 을 못찾았을 때, Person's prototype 에 있는 sum 으로 연결하고 싶다.
그러면 PersonPlus's prototype 의 __proto__ 가 Person's prototype 을 가리키면 됨.
PersonPlus.prototype.__proto__ = Person.prototype;
kim.sum() => 30 출력.
근데 __proto__ 는 표준이 아님. 그래서 ts 에서 안됨.
그래서 대신에 Object.create() 를 사용함.
PersonPlus.prototype = Object.create(Person.prototype);
이렇게 하면 PersonPlus.prototype.__proto__ = Person.prototype; 의 역할을 하긴 함.
근데 하자가 있음.
Object.create 는 새로 객체를 생성하기 때문에 이전에 정의한 prototype 이 바뀜.
예로, avg 를 먼저 prototype 에 정의하고, Object.create 를 하면 avg 가 사라짐.
또한, console.log("Kim.constructor: ", kim.constructor); 을 해보면
Kim.constructor: [Function: Person] 가 나옴.
이 말은 kim 이 Person 의 constructor function 이다 라는 뜻.
kim 의 constructor function 이 Person 입니다. 라고 알려주는 것.
근데 우리가 원한건 그게 아님. kim 의 constructor function 은 PersonPlus 이어야 함.
그걸 위해서 PersonPlus.prototype.constructor = PersonPlus; 를 해줘야 함.
=> Kim.constructor: [Function: PersonPlus]

여기부터 블로그 업로드 추가로 해야 함.

16.4. 생성자 함수를 통한 상속 : constructor 속성은 무엇인가?

Person 객체는 prototype 을 통해서 Person's prototype 을 참조함.
다시 역으로, Person's prototype 객체는 constructor 를 통해서 Person 객체를 참조함.
즉 서로가 prototype 과 constructor 를 통해서 상호참조 하고 있는 상태.
그리고 Person 을 통해서 new 를 사용해 새로운 객체를 만들면
그 새로운 객체는 __proto__ 를 통해서 constructor function(여기서는 Person) 의
prototype 객체, 즉 Person's prototype 을 가리킴.
그러면 여기서 kim.constructor 라고 하면 뭐가 될까?
Person 이 되는거 아닌가?
kim 에는 constructor 라고 하는 프로퍼티가 없음. => 그러면 __proto__ 를 타고 탐색
=> Person's prototype 에서 찾음. => Person's prototype 의 constructor 는 Person. => 정답은 Person.
즉, kim 이라는 객체의 constructor 는 생성자(constructor function) 를 가리킨다.
*
개발자도구에서,
let d = new Date();
시간을 나타내는 객체를 만들어 주는 생성자를 통해서 d 라는 객체를 얻음.
위의 내용을 토대로
Date.prototype.constructor === Date => true
라는 것을 알 수 있음.
그러면 d.constructor 는 뭐야?
d 는 자체적으로 constructor 정보를 가지고 있지는 않기 때문에
__proto__ 를 따라서 constructor function 의 prototype 객체로 가서
거기 있는 constructor 라고 하는 프로퍼티를 통해서 자기를 생성해준 생성자를 알려줌.
이런걸 통해서 예를 들어 d 라는 객체가 있을 때 d.constructor 를 통해서 얘를 누가 만들었는지 알 수 있음.
*
또한,
const d = new Date(); 와 const d2 = new d.constructor(); 는 같다.
d.constructor === d2.constructor
*
그래서, 자바스크립트에서 constructor 라고 하는 프로퍼티는 여러가지 의미로 쓰이는데
그 중의 하나는 어떠한 객체가 누구로부터 만들어졌는지를 알려주는,
주류 객체지향에서는 니 클래스는 뭐야? 라고 물어보는거랑 비슷함.
또한 new d.constructor() 와 같이 constructor function 이 뭔지 몰라도 만들어낼 수 있기도 함.

16.5. 생성자 함수를 통한 상속 : constructor 속성 바로잡기

PersonPlus.prototype = Object.create(Person.prototype); 를 하면PersonPlus.prototype.__proto__ = Person.prototype; 와는 다르게
새로운 객체로 PersonPlus 의 prototype 을 교체해버림.
그리고 PersonPlus 의 prototype 은 PersonPlus 를 가리키고 있었을 건데
걔를 리플레이스 시켜 버리기 때문에 더 이상 PersonPlus's prototype 은 PersonPlus 를 가리키지 않는다.
kim.constructor: [Function: Person] => Person 을 가리킴.
왜? Object.create(Person.prototype); 을 통해서 만들어진 객체는 Person's prototype 을 prototype 으로 하는 객체이기 때문이다.
우리가 원하는건 PersonPlus 가 나오길 바람.
그러면 이렇게 하면 됨. PersonPlus.prototype.constructor = PersonPlus;
=> kim.constructor: [Function: PersonPlus]
그런데 PersonPlus.prototype.avg 와 같이 prototype 에 추가한 함수를
PersonPlus.prototype.constructor = PersonPlus; 이전에 했었다면
리플레이스되기 때문에 사라짐.
근데 __proto__ 를 사용해서 PersonPlus.prototype.__proto__ = Person.prototype; 이렇게 하면
avg 는 정의한 순서에 상관없이 존재함.
왜? __proto__ 를 사용한 방식은 기존에 있었던 객체를 리플레이스 하지 않고,
__proto__ 만 바꾸기 때문. 즉 참조, 링크만 변경하는 것.
__proto__ 를 쓰면 간단히 해결되긴 하지만 비표준이기 때문에 Object.create 를 쓰는게 바람직 하다.
가치를 어디에 둘것이냐의 문제.
constructor 를 통한 상속을 알아봤는데
class 구현을 사용하는게 깔끔하긴 함.
class 를 사용해도 내부적으로는 지금까지 해왔던 구현을 내부적으로 하고 있음.
그렇기 때문에 class 를 쓰는걸 권장.
그런데 또 한편으로는 prototype 과 __proto__ 의 미묘한 관계.
그게 자바스크립트의 인프라라고해도 과언이 아님.
그래서 요걸 실험하면서 얘를 쓰겠다 보다는 쓸 때는 클래스를 쓰고,
대신에 이걸 이해했다는 것은 자바스크립트의 상당히 중요하고 어려운 부분을 이해하게 됐다는 뜻이긴 함.

17. 수업을 마치며

자바스크립트가 어려운 이유는 세가지 정도가 있다고 생각.1. 자바스크립트를 쉽다고 생각하기 때문.
실제로는 쉽지 않은데 그것을 쉽다고 생각하면 어렵다고 생각할 때보다 훨씬 더 어려워질 수 있음.
우리는 이 언어가 쉽기 때문에 공부하는 것이 아니라 가치있기 때문에 공부하는 것이라는 걸 한번 생각해보시면 좋을 것 같습니다.
2. 자바스크립트가 가지고 있는 아쉬운 특성들 때문.
대표적으로 `prototype` 과 `__proto__` 의 혼란스러움.
아쉬운 부분인 것은 사실이나 아쉬움을 비파기보다는 극복하는 방법. 아쉬움을 비난하는 것 보다는 경의를 표하는 방법이 우리에겐 더 이롭다고 생각.
3. 자바스크립트가 가지고 있는 극단적인 유연함 때문.
그 중심에 함수가 있음.
자바스크립트에서 함수는 정말 다양한 얼굴을 가지고 있는데, 혼자서 사용될 때는 유아독존 하는 아나키스트 같은 모습이고, 함수 앞에 new 를 붙이면 다른 객체를 만들고, 또 call 을 이용해서 호출하면 다른 객체를 위해서 일하는 용병이 되고, bind 를 실행하면 자신을 복제한 용병을 무한히 만들어낼 수 있는 분신술을 구사함.
이런 유연함은 자유롭지만 자유는 혼란스러움을 대동함.
혼란을 대하는 우리의 자세는 어때야 할까. 자유라는 가치와 혼란이라는 대가는 동전의 양면이라고 생각.
하나를 없애면 다른 하나도 없어짐. 혼란을 피할 수 없 다면 자유를 즐기십시오.
그래도 지금까지의 공부를 통해서 이제 객체지향 방식으로 만들어진 수 많은 부품들을 사용해서 소프트웨어를 빠르게 빠르게 만들어 갈 수 있는 만반의 준비가 됐다고 생각.
항상 공부할 때 공부는 우리를 괴롭히려는 그런것이 아니라 우리를 덜 불행하게, 가끔씩은 행복하게 해주기 위해서 존재하는 거여야 한다고 생각하거든요.
실제로 그럴 거구요.
그래서 공부하고 나서 자기 자신한테 축하하고, 기분 좋아하고. 그거 능력이고 실력입니다.
사실은 가장 중요한 실력일 수 있을 것 같아요. 그런 실력자가 되시길 바랍니다.

--

--

No responses yet