💙 들어가며
지난 시간 프로토타입을 배우면서
자바스크립트에서 객체지향을 구현하는 방식에 대해서 맛보았다.
그러나 이 방법으로도 완전한 객체지향을 구현하였다고 보기는 어려웠다.
그 이유는 무엇이었는지 오늘 수업을 통해서 확인해보자.
✏️ 학습내용 정리
#두 가지로 구분되는 자료형식(Wrapper Type│Reference Type)
자바스크립트에서는 자바와 달리 기본형이 없다고 배웠지만,
사실 엄밀히 말하면 기본형이 있기는 하다.
단 Wrapper Type으로 기본형이 존재한다는 것이 조금 특이하다.
기본형 Wrapper Type에는 Boolean, Null, Undefined, Number, BigInt, String, Symbol이 있고
그 이외의 것들은 모두 Reference이다.
다른 기본타입 중에 잘 보지 못했던 BigInt라는 녀석이 있다.
말 그대로 정말 큰 정수를 표현할 때 사용하는 타입이다.
자바스크립트에서 기본 정수형으로 표현될 수 있는 최대의 값은
Number.MAX_SAFE_INTEGER이라고 해서 2^53 - 1이다.
기본 정수형으로 표현되지 않는 큰 값을 BigInt라고 하며
이 경우 뒤에 n을 써주어야 한다.
실제로 콘솔에 한 번 찍어보자!
💡 다음을 출력하면?
console.log("=====BigInt=====");
console.log(Number.MAX_SAFE_INTEGER);
var big = Number.MAX_SAFE_INTEGER * 1000000;
console.log(big);
var big = 9007199254740991n * 1000000n;
console.log(big);
⭕ 출력결과:
BigInt라는 표시로 n을 붙여줘야지만 원하는 모양의 값이 나온다.
n표시를 해주지 않으면 지수형태로 출력되는 것을 볼 수 있다.

#객체타입 확인하기
기본형 변수도 객체이고, 참조형 변수도 객체이면 정확한 타입을 어떻게 구분할 수 있을까?
변수의 타입을 알기위해 사용하는 typeof 함수를 통해서 기본형을 정확히 확인할 수 있다.
기본형인 것들은 string, number등의 출력결과가 나오지만,
기본형이 아닌 것들은 다 object로 나온다.
그러면 기본형이 아닌 것들은 구체적인 type을 비교할 방법이 없을까?
기본형이 아닌 것들은 instanceof를 사용해서 확인한다.
instanceof로 확인할 때는 typeof와 정반대로
Wrapper Type 객체들은 맞는 타입과 비교되어도 false가 나온다.
오로지 객체형식만이 instanceof로 비교될 수 있다.
typeof때와는 다르게 instanceof 앞에는 객체명을 적어주어야 한다.
(객체명 instanceof를 사용하여 주로 Object나 Array와 비교한다.)
Array의 경우에는 다음과 같이 연산을 할 수도 있다.
💡 다음을 출력하면?
var nums = [];
console.log(nums instanceof Array);
console.log(Array.isArray(nums));
⭕ 출력결과:
instanceof와 마찬가지로 Array.isArray()라는 함수를 통해
현재 nums라는 객체가 배열 타입임을 확인할 수 있다.

#주의해야 할 연산
그러나 주의해야 할 연산이 있다. 바로 null과 관련된 연산들이다.
null은 기본형인 Wrapper Type이지만 특이한 연산결과가 등장한다.
💡 ※주의※주의※주의※
1.
기본형이 아닌 나머지들을 typeof 했을 경우에 object가 나오는데,
기본형인 null의 타입을 확인하면 object가 나온다.2.
특히 null과 undefined를 값으로 비교(==)했을 때 true가 나온다는 점을 잘 기억하자.
(다른 falsy와 값비교하면 false나옴, 예를 들면 false같은..)
3.
그러나 주소를 비교(===)하면 false가 나오니 두 개가 같은것이 아니라는 점은 확실하다.출처: 뉴렉처(https://www.youtube.com/@newlec1)
#객체 생성과 제거
여태까지는 객체를 생성하는 방법에 대해서만 다뤄봤는데,
만약 내가 생성한 객체를 제거하고 싶을 때는 어떻게 해야 할까?
객체에 null을 대입해주면 된다.
단 여기서 null을 대입하게 되면 객체 자체가 사라진다.
콘솔에 찍어보면 다음과 같다.
💡 다음을 출력하면?
console.log("=====객체 생성과 제거=====");
var exam = { kor: 10, eng: 20, math: 30 };
var array = [1, 2, 3];
var add = new Function("x,y", "return x+y");
console.log(add(1, 2));
console.log(array);
// add = null;
array = null;
// console.log(add(1, 2));
console.log(array);
⭕ 출력결과:
add라는 함수에 null을 대입하고 난 뒤 add(1 ,2)을 출력하려고 하면
오류가 나서 (not defined) 주석처리 하였다.
array는 null을 대입하였더니 더이상 지정한 값이 나오지 않고 null이 나온다.

#객체 속성 생성과 제거
그렇다면 속성만 골라서 제거하고 싶을 때는 어떻게 할 수 있을까?
이때에는 따로 사용하는 키워드가 있다. 바로 delete이다.
delete를 이용하면 해당되는 속성과 그 값을 삭제할 수 있다.
콘솔에 직접 찍어보자.
💡 다음을 출력하면?
console.log("=====객체 속성 추가와 제거=====");
var exam = {};
exam.kor = 10;
exam.eng = 20;
console.log(exam.kor + exam.eng);
console.log(exam);
delete exam.kor;
console.log(exam.kor);
console.log(exam);
⭕ 출력결과:
exam에서 kor라는 속성을 delete 하였더니 속성과 값이 함께 사라졌다.
exam.kor을 출력하면 undefined가 나오고
exam을 출력하면 객체 안에 eng만 남아있음을 확인할 수 있다.

※ 만약 exam.kor에 null을 대입하면 어떻게 나올까? exam.kor이 삭제될까?
💡 다음을 출력하면?
console.log("=====객체생성과 제거=====");
var exam = {};
exam.kor = 10;
exam.eng = 10;
console.log(exam.kor + exam.eng);
console.log(exam);
exam.kor = null;
console.log(exam.kor);
console.log(exam);
위에서 delete를 했을 때는 속성까지 모두 제거가 되었지만
null을 대입하게 되면 속성은 그대로 남아 있음을 알 수 있다.

#기본형은 확장되지 않는다.
그런데 자바스크립트의 모든 변수의 자료형이 객체형이라고 해서
아무 자료형이나 무한대로 늘어날 수 있는 것은 아니다.
기본타입(Wrapper Type)은 리터럴(직접 초기화) 해줄 경우에는
더 이상 확장되지 않는다.
주의점이 있는데, new String()처럼 생성자로 만들면
이 경우에는 Reference Type이 되기 때문에 같은 String형이어도 확장된다.
다음의 예시를 확인해보자.
💡 다음을 출력하면?
console.log("=====기본형은 확장되지 않는다.=====");
var hi = new String("hello");
hi.American = "hi";
console.log(hi);
var happy = "I'm happy";
happy.expressions = "excited";
console.log(happy);
⭕ 출력결과:
같은 String 타입의 객체이지만
첫번째의 경우 new를 해서 생성한
Refernce Type의 객체이기 때문에 확장이 된다.
그러나 두번째의 경우에는 리터럴로 초기화하였기 때문에
기본형 Wrapper Type으로 인식이 되었고 확장되지 않음을 볼 수 있다.

#Reference 객체 접근방법
참조타입의 객체를 다루다보면 신기한 것이
객체 안에 함수가 구성으로 들어갈 수 있다는 것이다.
자바스크립트에서는 함수도 데이터이다. 따라서 구성원으로 들어갈 수 있다. (신기!)
그럼 이때 함수는 어떻게 접근해야 할까?
똑같이 속성의 개념으로 접근할 수 있다.
Reference타입의 객체들은
모두 점(. )이나 대괄호[]를 통해서 속성을 지칭해서 접근하면 된다.
#프로토타입은 공유
본격적으로 객체지향적 개념으로 접근해보려고 한다.
먼저, 프로토타입이다.
이전 시간에 프로토타입은 형식이 아니라
하나의 객체를 공유하는 것이라는 것을 배웠다.
그렇다면 이것을 상속이라고 할 수 있을까?
형식이 실체화 된 다음에 물려받는 형태인데 말이다.
prototype은 실체화된 객체를 공유하는 방식으로 이해하는 것이 좋겠다.
(물론 상속도 공유의 개념이지만..! 뒤에서 좀 더 자세히 다뤄보도록 하자.)
#프로토타입을 찾아보자
그러면 객체의 프로토타입은 어떻게 알아낼 수 있을까?
개체를 통해서 프로토타입을 알아내거나
객체를 통해서 생성자를얻고 그 생성자를 통해서 알아낼 수 있다.
혹은 Object.getPrototypeOf()함수를 사용해서 알아낼 수 있다.
※ 언더스코어(_ )는 2개씩 해야 한다. ( _ _ proto _ _ 임)
그러나 현재 더이상 사용하지 않으니 설명에서 제외하도록 하겠다.
다른 객체를 통해 프로토타입을 얻는다는 것이
상속이라는 개념과는 멀어보이지만 과거에는 이것도 상속이라고 했다고 한다.
여기서 주의할 점은 프로토타입을 별도로 설정하면 생성자가 달라진다는 것이다.
프로토타입을 설정하기 전과 후의 생성자가 달라지는 문제가 있을 수 있겠다.
잘 인지해두자.
💡 다음을 출력하면?
function NewlecExam() { };
NewlecExam.prototype = exam5;
var newExam = new NewlecExam();
console.log(newExam.__proto__);
console.log(Object.getPrototypeOf(newExam));
console.log(NewlecExam.prototype);
console.log(newExam.constructor === NewlecExam);
console.log(exam5.constructor == Exam);
console.log(newExam.constructor);
⭕ 출력결과:
prototype을 설정하면 생성자가 다른 것으로 인식되어 false가 나오고
밑에 constructor로 검색하는 생성자도 NewlecExam이 아니라 Object가 나온다.

그런데 만약 프로토타입을 설정하지 않으면
💡 다음을 출력하면?
function NewlecExam() { };
// NewlecExam.prototype = exam5;
var newExam = new NewlecExam();
console.log(newExam.__proto__);
console.log(Object.getPrototypeOf(newExam));
console.log(NewlecExam.prototype);
console.log(newExam.constructor === NewlecExam);
console.log(exam5.constructor == Exam);
console.log(newExam.constructor);
⭕ 출력결과:
생성자와 NewlecExam을 비교했을 때 true가 나오고
constuctor만 출력했을 때도 정상적으로 NewlecExam이 나온다.

한 번만 더 예시를 들어보자.
프로토타입을 설정하기 전과 후의 차이를 느껴보자.
💡 다음을 출력하면?
function Omok() {
this.x = 0;
this.y = 0;
}
console.log(Omok.prototype);
console.log(Omok.prototype.constructor);
Omok.protytpe = {};
var omok1 = new Omok();
console.log(omok1);
console.log(omok1.constructor);
console.log("=======프로토타입 생성 후=======");
Omok.prototype = {};
var omok2 = new Omok();
console.log(omok2.constructor.prototype);
⭕ 출력결과:
omok2의 constructor는 분명 Omok인데도 프로토타입에 {} 빈 객체를 넣어주었더니,
Omok의 prototype과 omok2.constructor.prototype이 다른 것을 확인해볼 수 있다.

#생성자를 훼손하지 않으면서 프로토타입 만들기
그렇다면 생성자를 훼손하지 않으면서 프로토타입을 만들 수 있는 방법은
무엇일까?
Exam.prototype = 자체를 정의하지 않고
Exam.prototype.total =과 같은 식으로 추가할 수도 있지만
Exam.prototype= {안에 constructor를 넣어주면}
생성자를 사라지게 하지 않으면서 prototype을 만들 수 있다.
주석을 잘 읽어보자!
💡 코드로 구현하면?
function Exam(){
this.kor = 0;
this.eng = 0;
this.math = 0;
}
var exam1 = new Exam();
console.log(exam1.__proto__.constructor)
console.log(exam1.__proto__)
console.log(exam1.__proto__.__proto__)
console.log(exam1.__proto__.__proto__.__proto__)
console.log("----------------------")
//프로토타입 자체에 constructor를 넣어준다.
Exam.prototype = {
constructor : Exam,
total : function(){
return this.kor+eng+math;
}
}
//다음과 같이 일일이 추가할 수도 있다.
// Exam.prototype.total = function(){
// return this.kor+eng+math;
// }
var exam2 = new Exam();
console.log(exam2.__proto__.constructor)
console.log(exam2.__proto__)
console.log(exam2.__proto__.__proto__)
console.log(exam2.__proto__.__proto__.__proto__)
#Shadowing
shadowing이란 자바에서 overriding과 같은 개념이다.
부모 속성(base)을 끌어와서 사용하는데,
여기에 override를 해버리면 base의 값을 바꾸는 것이 아니라
exam1, exam2 각각에 속성을 추가하면서 base의 값을 가리게 된다.
아래의 예시를 확인해보자.
💡 다음을 출력하면?
var base = {
kor: 10,
eng: 20,
math: 30
};
var exam = {
com: 10,
__proto__: base
};
var exam2 = {
com: 20,
__proto__: base
};
exam.kor = 3; // Shadowing
exam2.kor = 4; // Shadowing
console.log(exam.kor, exam.eng, exam.math, exam.com);
console.log(exam2.kor, exam2.eng, exam2.math, exam2.com);
⭕ 출력결과:
분명 같은 base를 속성으로 가지고 있고,
그래서 com이라는 속성의 값을 출력할 수 있음에도 불구하고
kor이 각각 다른 값으로 출력되었다.
이를 통해 shadowing이 되는 것에 유의해야 함을 깨달을 수 있다.
(절대 부모 객체의 값을 수정해주지 않고, 자식 객체에 속성을 추가하는 식으로 수정함)

마치 이런것과 같은 식이다.
💡 Shadowing에 주의하자
var base = {
kor: 10,
eng: 20,
math: 30
};
var exam = {
kor: 3, //여기에 kor이 생기면서 3이 대입된다고 생각해야 함
com: 10,
__proto__: base
};
var exam2 = {
kor: 4, //여기에 kor이 생기면서 4가 대입된다고 생각해야 함
com: 20,
__proto__: base
};
exam.kor = 3; // Shadowing
exam2.kor = 4;
#Object.hasOwn(객체, "속성")
그러면 이런 shadowing에 대한 문제점을 확인는데, 어떻게 보완할 수 있을까?
어떻게 자식에서 수정해도 부모것이 수정될 수 있을까?
일단 결론부터 말하면 자식에서 수정할 수는 없다. 부모에서 수정해야 한다.
그런데 부모가 1개가 아니고 부모의 부모의 부모가 있다면? 어떻게 찾아가야 할까?
그럴 때는 Object.hasOwn()을 사용해서 부모의 객체를 찾아가야 한다.
hasOwn()을 썼을 때 false가 나오는 것이라면 부모가 아니기 때문에
분명 부모것을 수정한다고 생각하고 수정했어도 자식만 수정이 된다.
(base 수정안되고 exam에 shadowing되어버림)
💡 exam2은 eng를 가지고 있을까?
var base = {
kor: 10,
eng: 20,
math: 30
};
var exam2 = {
com: 20,
__proto__: base
};
console.log(Object.hasOwn(exam2, "eng"));
⭕ 출력결과:
exam2 객체는 eng를 가지고 있지 않다.
상위에 부모가 가지고 있는 속성인 것!
따라서 exam2에서 math값을 출력할 수는 있어도
근원적으로 수정할 수는 없다.
exam2에서 math의 값을 수정하면 shadowing이 되어서
exam2에 math라는 속성이 새로 추가되는 방식으로 수정이 일어난다.

💙 마치며
생성자가 가진 프로토타입을 덮어씌우게 되거나
멋대로 부모가 아닌 자식 클래스에 속성이 추가되는 등의 문제가 있었던 과거버전
앞서 언급했던 prototype과 shadowing과 같은 문제 때문에
아직까지 배운 개념으로는 완전한 객체지향이라고 보기가 어렵겠다.
헷갈리는 것이 너무 많은 자바스크립트ㅠㅠ...
선생님의 가르침대로 잘 따라가고 있기를 바라며..!