1. is(), JS값 비교 방법
is()
•
두 개의 파라미터 값과 값 타입을 비교하여 같으면 true 아니면 false 반환
•
오브젝트 비교 목적이 아님
◦
[ ]와 [ ]비교, { }와 { } 비교는 false
const result = Object.is(10, "10");
console.log(result);//false
const one = {}, two = {}
console.log(Object.is(one, two));
JavaScript
복사
⇒ one이 참조하는 메모리주소와 two가 참조하는 메모리주소는 다르다.
•
JS값 비교 방법
◦
값과 타입까지 모두 비교하는 경우: ===
◦
타입은 비교하지 않고 값만 비교: ==
console.log((undefined == null));//true
console.log((undefined === null));//false
console.log(Object.is(undefined, null));//false
JavaScript
복사
•
Object.is()와 === 비교 차이
◦
NaN 비교
◦
+0과 -0비교
console.log((NaN === NaN));//false
console.log(Object.is(NaN, NaN));//true
console.log((NaN === 0/0));//false
console.log(Object.is(NaN, 0/0));//true
console.log((0 === -0));//true
console.log(Object.is(0, -0));//false
JavaScript
복사
•
활용한 형태
1.
Object.is(typeof data, "object")에서 typeof data결과로 비교합니다.
function check(data){
if(Object.is(typeof data, "object")){
console.log(data);
} else {
console.log("object 타입이 아님");
}
};
check({value:10});//{value: 10}
check(200);//object 타입이 아님
JavaScript
복사
2. 오브젝트 복사: assign(), 첫 번째 파라미터 작성, 두 번째 파라미터 작성
assign()
•
두 번째 파라미터의 오브젝트 프로퍼티를 첫 번째 파라미터의 오브젝트에 복사 후 첫 번째를 반환합니다.
•
own property만 복사합니다.
const sports = {
event: "축구",
player: 11
};
let dup = {};
Object.assign(dup, sports);
console.log(dup);//{event: 축구, player: 11}
JavaScript
복사
•
첫 번째 파라미터 작성
◦
첫 번째 파라미터는 필수 파라미터(essential)
◦
Number, String, Symbol, Boolean값 작성
try{
const obj = Object.assign(null, {});
} catch(e){
console.log("null 작성 불가");
};
const obj = Object.assign(100);
console.log(obj.valueOf());//100
JavaScript
복사
⇒ 첫 번째 파라미터를 작성하지 않거나 null, undefined를 작성하면 TypeError
⇒ 첫 번째 파라미터에 Number를 작성하면 Number 인스턴스를 생성 후 파라미터 값 100을 [[PrimitiveValue]]에 설정 하여 인스턴스를 반환합니다. Boolean, String, Symbol도 동일합니다.
•
두 번째 파라미터
◦
두 번째 파라미터는 필수는 아닙니다(Optional)
◦
열거 가능 오브젝트 작성 (1)
◦
오브젝트 다수 작성 (2)
◦
값을 작성(3)
◦
값과 오브젝트를 작성(4)
⇒ 100은 Number인스턴스를 생성 후 두 번째 파라미터의 Object를 Number 인스턴스에 복사합니다.
/*(1)*/
let obj = {};
Object.assign(obj, {ten: 10});
console.log(obj);
const one = Object.create({}, {
book: {value:100, enumerable: false},
sports: {value:200, enumerable: true}
});
Object.assign(obj, one);
console.log(obj);
/*(2)*/
const book = {title: "책"};
const sports = {item: "축구"};
const obj = Object.assign({}, book, sports);
console.log(obj);//{title:책, item:축구}
/*(3)*/
let obj = {ten: 10};
Object.assign(obj, undefined, null, 200);
console.log(obj);//{ten: 10}
const one = {un: undefined, nu:null}
Object.assign(obj, one);
console.log(obj);//{ten:10, un:undefined, nu:null}
/*(4)*/
const obj = Object.assign(100, {book: 200});
console.log(obj.valueOf());//100
console.log(obj.book);//200
JavaScript
복사
3. deep copy
•
Object를 할당하면 프로퍼티 값이 연동됩니다.
◦
한 쪽 오브젝트의 프로퍼티 값을 바꾸면 다른 오브젝트의 프로퍼티 값도 바뀝니다.
const sports = {
item: "축구"
};
let copy = sports;
sports.item = "농구";
console.log(copy.item);//농구
JavaScript
복사
•
assign()함수로 복사
const sports = {
item: "축구"
};
let copy = {};
Object.assign(copy, sports);
sports.item = "농구";
console.log(copy.item);//축구
JavaScript
복사
•
assign()을 써도 연동되는 경우
⇒ Object안에 Object가 있는 형태에서는 item.title값이 연동됩니다.
프로퍼티를 복사하는 것이아닌 Object참조를 복사하기 때문입니다.
const book= {
item: {title: "자바스크립트"}
};
let copy = {};
Object.assign(copy, book);
copy.item.title = "책";
console.log(book.item.title);//책
JavaScript
복사
•
프로퍼티 단위로 복사하여 연동되지 않도록 합니다.
⇒ 프로퍼티 단위로 복사하면 연동되지 않지만 단계의 깊이가 유동적이면 코드가 복잡해집니다. 이러한 다단계 계층 구조에서 값이 연동되지 않도록 복사하는것을 deep copy라고 합니다.
const book= {
item: {title: "자바스크립트"}
};
let copy = {};
for(let key in book){
let value = book[key];
copy[key] = {};
for(let name in value){
copy[key][name] = value[name];
};
};
copy.item.title = "책";
console.log(book.item.title);//자바스크립트
JavaScript
복사
•
JSON함수를 이용한 deep copy
const book = {
item: {title: "자바스크립트"}
};
const copy = JSON.parse(JSON.stringify(book));
book.item.title = "책";
console.log(copy.item.title);
JavaScript
복사
4. Object 변환: entries(), values(), fromEntries(), getOwnPropertyDescriptors()
entries()
•
열거 가능한 오브젝트의 {key: value}를 [[key, value]]형태로 변환합니다.
[(1) 실행 결과]
[music, 음악]
[book, 책]
•
작성한 순서가 바뀌는 경우
key값이 숫자와 문자가 섞여있으면 숫자, 문자 순서로 분류
[(2) 실행 결과]
[7, 칠]
[10, 십]
[book, 책]
•
문자열은 문자 하나씩 분리
문자열은 문자 하나씩 분리하며 인덱스를 key 값으로 사용
[실행 결과]
[0, A]
[1, B]
[2, C]
const obj = {music: "음악", book:"책"};
const list = Object.entries(obj);
for(let keyValue of list){ //---(1)
console.log(keyValue);
};
const obj = {10: "십", book:"책", 7:"칠"};
const list = Object.entries(obj);
for (let keyValue of list){ //---(2)
console.log(keyValue);
};
const list = Object.entries("ABC");
for(let keyValue of list){//---(3)
console.log(keyValue);
};
JavaScript
복사
values()
•
열거 가능한 오브젝트의 {key: value}를 값만 [value1, value2]형태로 변환합니다.
⇒ [실행 결과]
음악
책
•
작성한 순서가 바뀌는 경우
⇒ [실행 결과]
칠
십
책
•
문자열은 문자 하나씩 분리
⇒ [실행 결과]
A
B
C
const obj = {music: "음악", book: "책"};
const list = Object.values(obj);
for (let value of list){
console.log(value);
};
const obj = {10: "십", book:"책", 7:"칠"};
const list = Object.values(obj);
for (let keyValue of list){ //---(2)
console.log(keyValue);
};
const list = Object.values("ABC");
for(let keyValue of list){//---(3)
console.log(keyValue);
};
JavaScript
복사
fromEntries()
•
[[key, value]]형태를 {key: value}형태로 변환하는 함수
⇒ {one: 10, two: 20}
•
프로퍼티 키 값이 같을 경우 값을 대체합니다.
⇒ {one:20}
const list = [["one", 10], ["two", 20]];
const obj = Object.fromEntries(list);
console.log(obj);
const list2 = [["one", 10], ["one", 20]];
const obj2 = Object.fromEntries(list2);
console.log(obj2);
JavaScript
복사
getOwnPropertyDescriptors()
•
Object의 프로퍼티 디스크립터를 반환합니다.
◦
데이터 디스크립터
⇒ value: 음악
writable: true
enumerable: true
configurable: true
◦
액세스 디스크립터
⇒get: get music(){}
set: undefined
enumerable: true
configurable: true
◦
상속받은 오브젝트는 반환하지 않습니다.
const obj = {music: "음악"};
const des= Object.getOwnPropertyDescriptors(obj);
for(let name in des.music){
console.log(name + ": "+ des.music[name]);
};
const obj2 = {
get music(){}
};
const des2= Object.getOwnPropertyDescriptors(obj2);
for(let name in des2.music){
console.log(name + ": "+ des2.music[name]);
};
JavaScript
복사
5. prototype과 __proto__, 메소드 호출 방법
메소드 호출 방법
•
prototype과 __proto__에 연결된 메소드를 호출하는 방법이 다릅니다.
1.
prototype에 연결된 메소드 호출
•
Array.prototype.slice()처럼 prototype을 작성하여 호출
2.
__propto__에 연결된 메소드 호출
•
인스턴스를 생성하여 호출
function Book(){
this.point = 100;
};
Book.prototype.getPoint = function(){
console.log(Object.is(this, Book.prototype));
return this.point;
};
console.log(Book.prototype.getPoint());
console.log(Book.prototype.getPoint.call(Book));
JavaScript
복사
1.
Book.prototype.getPoint()으로 prototype을 작성하여 호출하면 getPoint()에서 this가 Book.prototype을 참조합니다.
2.
Book.prototype.getPoint.call(Book)에서는 this로 Book을 참조합니다.
3.
둘 다 this.point를 참조할 수없는데 참조하기 위해서는 Book의 인스턴스를 생성후 인스턴스의 메소드를 호출해야 합니다.
•
new 연산자로 생성하찬 인스턴스 구조
function Book(){
this.point = 100;
};
Book.prototype.getPoint = function(){
return this.point;
};
const obj = new Book();
console.log(obj.getPoint());
JavaScript
복사
1.
const obj = new Book();
⇒ obj의 Scope를 보면 point에 100이 할당되어 있으며 인스턴스 프로퍼티입니다. 이는 생성자 함수(Book(){...})에서 this.point = 100으로 설정한 것입니다.
⇒ __proto__를 보면 prototype에 연결된 메소드가 표시되는데 여기서 getPoint는 Book.prototype.getPoint를 참조합니다. 여기서 주의할점은Book.prototype.getPoint를 참조하는 것이지 복사하여 __proto__에 설정하는것이 아닙니다.
2.
console.log(obj.getPoint());
⇒ 생성한 인스턴스의 이름을 사용하여 getPoint()를 호출하면 호출된 메소드에서 this로 해당 인스턴스를 참조합니다.
위 코드에서는 obj인스턴스를 참조하는데 obj는 생성자를 통해 생성할 때 this.point = 100을 할당해줬기 때문에
getPoint() 메소드 내에서 this.point를 참조할 수 있습니다.
6. 인스턴스에 함수로 추가
함수로 추가
new 연산자로 인스턴스를 생성한 뒤 인스턴스의 프로퍼티로 함수를 추가합니다. 이렇게 인스턴스에 프로퍼티로 함수를 설정할 경우 다른인스턴스에서는 해당 함수를 쓸 수 있을까요?
답은 아닙니다. 각각의 인스턴스는 생성자를 통해 인스턴스를 생성할 때 prototype에 연결된 메소드들의 참조를 __proto__에 담아 인스턴스에 설정하지만, 인스턴스에서 프로퍼티로 함수를 추가하는 그 함수에 한정되는 것이기 때문이죠.
코드를 통해 분석해봅시다.
function Book(){
this.point = 100;
};
Book.prototype.getPoint = function(){
return this.point;
};
const obj = new Book();
//인스턴스에 프로퍼티(함수)를 추가합니다.
obj.setPoint = function(param){
this.point = param;
};
obj.setPoint(200);
console.log(obj.getPoint());
const newObj = new Book();
console.log(newObj.setPoint);
JavaScript
복사
[실행 결과]
200
undefined
1.
obj.setPoint = function(param){...}
⇒ 인스턴스에 setPoint 프로퍼티를 설정해서 point값을 설정하는 로직을 추가합니다.
2.
obj.setPoint(200);
⇒위에서 설정한 setPoint함수를 호출해 파라미터(200)을 넘겨줘 point의 값을 200으로 설정합니다.
3.
console.log(newObj.setPoint);
⇒ 새로 생성항 Book()의 인스턴스인 newObj에서 setPoint프로퍼티를 호출하지만 해당 프로퍼티는 obj인스턴스에서 개별로 추가한 함수이기 때문에 newObj에는 설정되지 않았고 그래서 undefined가 출력됩니다.
7. __proto__에 메소드 추가
메소드 추가
__proto__에 function을 추가하면 prototype에 설정이 되며 메소드로 추가하는것과 같습니다.
여기까지만 보면 위에서 한 함수로 추가하는것과 동일해보이지만 가장 큰 차이점은 메소드의 '공유'에 있습니다.
인스턴스에 추가한 함수는 해당 인스턴스에서만 사용가능하지만, __proto__에 추가한 메소드는 공유되어 해당 객체의 인스턴스는 모두 사용할 수 있습니다. 심지어 메소드를 추가하기전에 생성된 인스턴스에서도 말이죠.
함수를 통해 살펴보겠습니다.
function Book(){
this.point = 100;
};
Book.prototype.getPoint = function(){
return this.point;
};
const obj = new Book();
const beforeObj = new Book();
console.log(beforeObj.setPoint);//undefined
obj.__proto__.setPoint = function(param){
this.point = param;
};
debugger
beforeObj.setPoint(700);
console.log(beforeObj.getPoint());//700
JavaScript
복사
1.
console.log(beforeObj.setPoint)
⇒ 아직 메소드를 추가하기전 beforeObj에는 setPoint 프로퍼티(함수)가 설정되어있지않아 undefined가 출력됩니다.
2.
obj.__proto__.setPoint = function(param){...}
⇒ 함수 추가와는 다르게 __proto__에 setPoint property를 등록합니다. 이렇게 메소드를 추가하게되면 Book() 객체의 prototype을 참조하는 인스턴스들 모두에 공유됩니다.
3.
beforeObj.setPoint(700);
⇒ obj인스턴스의 __proto__에 setPoint 프로퍼티를 추가했는데 beforeObj에서 setPoint()를 호출해도 정상동작하는 것을 확인할 수 있습니다. 이를 통해 추가한 메소드가 공유되는것을 확인할 수 있습니다.
8. setPrototypeOf(): 인스턴스 사용
2개의 파라미터를 통해 첫 번째 파라미터(오브젝트 or 인스턴스)에 두 번째 파라미터를 설정하는 함수로 두 번째 파라미터는 오브젝트의 prototype으로 연결할 내용 혹은 null이 들어갑니다. 코드를 통해 setPrototypeOf()실행 후에 인스턴스 구조가 어떻게 변하는지 확인해봅니다.
let obj = {0: 10, length: 1};//Array-Like 오브젝트
Object.setPrototypeOf(obj, Array.prototype);
const callback = (element, index, all) => console.log(element);
obj.forEach(callback);
const check = Object.prototype;
JavaScript
복사
1.
let obj = {0: 10, length: 1};
⇒ obj는 인스턴스입니다. 빌트인 오브젝트에 의해 인스턴스가 생성되기 때문입니다. 그렇기에 obj는 인스턴스인데 인스턴스에는 prototype이 없습니다. prototype을 참조하는 __proto__가 있기에 setPrototypeOf는 인스턴스의 __proto__에 설정하는것과 동일하다 할 수 있습니다.
2.
Object.setPrototypeOf(obj, Array.prototype);
⇒ obj의 __proto__에 Array.prototype에 연결된 메소드를 설정합니다. Scope를 살펴보면 Object.prototype에 연결된 메소드가 없어지고 Array.prototype에 연결된 메소드가 표시됩니다.
3.
obj.forEach(callback);
⇒ 기존 Object.prototype에는 없는 메소드인 forEach를 사용할 수 있게되었습니다.
4.
const check = Object.prototype;
⇒ Object.prototype을 꺼내 확인해보면 prototype이 변경되지 않은 것을 볼 수 있습니다.
setPropertyOf()메소드를 통해 프로퍼티를 변경한다면 그 변경은 해당 인스턴스로 한정됩니다. 그렇기에 객체의 prototype을 확인해도 변경되지않았고, 다른 인스턴스에서도 변경되지 않습니다.
9. setPrototypeOf(): prototype 사용
setPrototypeOf()를 사용할 때 prototype이 대체됨으로써 기존에 쓰던 메소드도 못쓰는 상황이 생깁니다. Object.prototype을 가지고있지만 Array.prototype의 프로퍼티들도 사용하고싶은경우에는 어떻게 해야할까요? 하나하나 따로 연결을 해주는것은 좋은 방법은 아닙니다. 우리는 javascript에서 프로퍼티 식별자 해결(Identity Resolution)을 할 때 어떤식으로 진행되는지를 상기할 필요가 있습니다. 엔진은 식별자를 찾기위해 __proto__순서로 검색합니다. 예를들어 A라는 인스턴스에서
getXXX()라는 프로퍼티 식별자 해결을 하기위해 인스턴스에 선언되니 프로퍼티를 검색 후 __proto__내부를 순회한 뒤 없을경우 __proto__.__proto__...순으로 검색합니다. 이에서 착안해 setPrototypeOf()가 메소드를 대체함으로써 기존의 메소드를 사용 못하게 한다면, __proto__안에 새로운 __proto__를 만들어 설정하면 기존 메소드들이 없어지지 않고 새로운 메소드들은 정상적으로 설정될 수 있습니다. 코드를 통해 알아 보겠습니다.
const Point = function(){
this.point = 100;
}
Point.prototype.getPoint = function(){return this.point}
const Book = function(){}
Book.prototype.setPoint = function(point){this.point = point}
Object.setPrototypeOf(Point.prototype, Book.prototype);
const obj = new Point();
obj.setPoint(300);
console.log(obj.getPoint());
JavaScript
복사
1.
Object.setPrototypeOf(Point.prototype, Book.prototype);
⇒ Book에 연결된 메소드들을 Point.prototype.__proto__에 설정합니다. 이로써 Point의 prototype도 없어지지않고 Book의 prototype들도 사용할 수 있게 됩니다.
2.
const obj = new Point();
⇒ Point의 인스턴스를 생성합니다.
3.
obj.setPoint(300);
⇒setPoint 식별자 해결을 위해 엔진에서 식별자를 찾기시작하면 obj.__proto__.__proto__.setPoint()를 찾을 수 있습니다.
4.
console.log(obj.getPoint());
⇒ 기존의 point는 생성당시 100으로 설정되었지만 setPoint를 통해 300으로 설정되어 300이 출력됩니다.
정리
기존의 setPrototypeOf()는 프로토타입을 대체하는 함수였습니다. 하지만 위 코드처럼 __proto__를 이용해 계층적 구조를 구현함으로써 prototype의 확장합니다. 1단계 2단계 구조를 만드는 것이죠.
상속과 비슷한 개념으로 생각할수도 있습니다. 여기서 소개한 prototype의 확장에는 생성자함수의 처리는 없습니다. 그렇기 때문에 생성자 처리가 필요할 때는 super등의 상속 처리 키워드를 제공하는 Class를 사용하는게 좋습니다.