두 개의 파라미터 값과 값 타입을 비교하여 같으면 true 아니면 false 반환
⇒ one이 참조하는 메모리 주소와 two가 참조하는 메모리 주소는 다르다.const result = Object.is(10, "10"); console.log(result); const one = {}, two = {} console.log(Object.is(one, two)); // false // false
값과 타입까지 모두 비교하는 경우: ===
타입은 비교하지 않고 값만 비교: ==
console.log((undefined == null)); console.log((undefined === null)); console.log(Object.is(undefined, null)); // true // false // false
Object.is()와 === 비교 차이
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
활용한 형태
function check(data){ if(Object.is(typeof data, "object")){ console.log(data); } else { console.log("object 타입이 아님"); } }; check({value:10}); check(200); // {value: 10} // object 타입이 아님
⇒ Object.is(typeof data, "object")에서 typeof data 결과로 비교합니다.
두 번째 파라미터의 오브젝트 프로퍼티를 첫 번째 파라미터의 오브젝트에 복사 후 첫 번째를 반환합니다. own property만 복사합니다
const sports = { event: "축구", player: 11 }; let dup = {}; Object.assign(dup, sports); console.log(dup); // {event: 축구, player: 11}
첫 번째 파라미터 작성
첫 번째 파라미터는 필수 파라미터(essential)
Number, String, Symbol, Boolean값 작성
try { const obj = Object.assign(null, {}); } catch(e) { console.log("null 작성 불가"); }; // null 작성 불가
const obj = Object.assign(100); console.log(obj.valueOf()); // 100
⇒ 첫 번째 파라미터를 작성하지 않거나 null, undefined를 작성하면 TypeError
⇒ 첫 번째 파라미터에 Number를 작성하면 Number 인스턴스를 생성 후 파라미터 값 100을 [[PrimitiveValue]]에 설정 하여 인스턴스를 반환합니다. Boolean, String, Symbol도 동일합니다.
두 번째 파라미터 작성
두 번째 파라미터는 필수는 아닙니다(Optional)
열거 가능 오브젝트 작성 (1)
오브젝트 다수 작성 (2)
값을 작성(3)
값과 오브젝트를 작성(4)
/*(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); // {ten: 10} // {ten: 10, sports: 200}
/*(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); const one = {un: undefined, nu:null} Object.assign(obj, one); console.log(obj); // {ten: 10} // {ten:10, un:undefined, nu:null}
/*(4)*/ const obj = Object.assign(100, {book: 200}); console.log(obj.valueOf()); console.log(obj.book); // 100 // 200
(3) ⇒ 열거 가능한 오브젝트가 아닌 단순한 값이면 복사되지 않습니다.
(4) ⇒ 100은 Number 인스턴스를 생성 후 두 번째 파라미터의 Object를 Number 인스턴스에 복사합니다. 데이터 타입이 맞지 않지만, 오브젝트이므로 복사가 됩니다(정상적인 모습 X)
Object를 할당하면 프로퍼티 값이 연동됩니다.
const sports = { item: "축구" }; let copy = sports; sports.item = "농구"; console.log(copy.item); // 농구
⇒ 한 쪽 오브젝트의 프로퍼티 값을 바꾸면 다른 오브젝트의 프로퍼티 값도 바뀝니다.
assign()함수로 복사
const sports = { item: "축구" }; let copy = {}; Object.assign(copy, sports); sports.item = "농구"; console.log(copy.item); // 축구
⇒ 간단한 형태는 연동되지 않습니다.
assign()을 써도 연동되는 경우
const book= { item: {title: "자바스크립트"} }; let copy = {}; Object.assign(copy, book); copy.item.title = "책"; console.log(book.item.title); // 책
⇒ Object안에 Object가 있는 형태에서는 item.title값이 연동됩니다.
⇒ 프로퍼티를 복사하는 것이 아닌 Object 참조를 복사하기 때문입니다.
프로퍼티 단위로 복사하여 연동되지 않도록 합니다.
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); // 자바스크립트
⇒ 연동은 되지 않지만 단계의 깊이가 유동적이면 코드가 복잡해집니다.
⇒ 다단계 계층 구조에서 값이 연동되지 않도록 복사하는 것을 Deep copy라고 부릅니다.
JSON함수를 이용한 deep copy
const book = { item: {title: "자바스크립트"} }; const copy = JSON.parse(JSON.stringify(book)); book.item.title = "책"; console.log(copy.item.title); // 자바스크립트
⇒ JSON.stringify()로 문자열 변환 후 JSON.parse()로 파싱하면 연동되지 않습니다.
열거 가능한 오브젝트의 {key: value}를 [[key, value]]형태로 변환합니다.
const obj = {music: "음악", book:"책"}; const list = Object.entries(obj); for(let keyValue of list) { console.log(keyValue); }; // [music, 음악] // [book, 책]
작성한 순서가 바뀌는 경우
const obj = {10: "십", book:"책", 7:"칠"}; const list = Object.entries(obj); for (let keyValue of list) { console.log(keyValue); }; // [7, 칠] // [10, 십] // [book, 책]
⇒ key값이 숫자와 문자가 섞여있으면 숫자, 문자 순서로 분류
문자열은 문자 하나씩 분리
const list = Object.entries("ABC"); for(let keyValue of list) { console.log(keyValue); }; // [0, A] // [1, B] // [2, C]
⇒ 문자열은 문자 하나씩 분리하며 인덱스를 key 값으로 사용
열거 가능한 오브젝트의 {key: value}를 값만 [value1, value2]형태로 변환합니다.
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) { console.log(keyValue); }; // 칠 // 십 // 책
문자열은 문자 하나씩 분리
const list = Object.values("ABC"); for(let keyValue of list) { console.log(keyValue); }; // A // B // C
[[key, value]]형태를 {key: value}형태로 변환하는 함수
const list = [["one", 10], ["two", 20]]; const obj = Object.fromEntries(list); console.log(obj); // {one: 10, two: 20}
프로퍼티 키 값이 같을 경우 값을 대체합니다
const list2 = [["one", 10], ["one", 20]]; const obj2 = Object.fromEntries(list2); console.log(obj2); // {one:20}
Object의 프로퍼티 디스크립터를 반환합니다
상속받은 오브젝트는 반환하지 않습니다
데이터 디스크립터
const obj = {music: "음악"}; const des= Object.getOwnPropertyDescriptors(obj); for(let name in des.music) { console.log(name + ": "+ des.music[name]); }; // value: 음악 // writable: true // enumerable: true // configurable: true
엑세스 디스크립터
const obj2 = { get music(){} }; const des2 = Object.getOwnPropertyDescriptors(obj2); for(let name in des2.music) { console.log(name + ": "+ des2.music[name]); }; // get: get music(){} // set: undefined // enumerable: true // configurable: true
prototype과 __proto__에 연결된 메소드를 호출하는 방법이 다릅니다.
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)); // true // undefined // false // undefined
function Book() { this.point = 100; }; Book.prototype.getPoint = function() { return this.point; }; const obj = new Book(); console.log(obj.getPoint());
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); // 200 // undefined
__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); obj.__proto__.setPoint = function(param){ this.point = param; }; beforeObj.setPoint(700); console.log(beforeObj.getPoint()); // undefined // 700
지정된 객체의 프로토타입 (즉, 내부 [[Prototype]] 프로퍼티)을 다른 객체 또는 null 로 설정합니다.
첫 번째 파라미터 인스턴스의 __proto__에 두 번째 파라미터의 prototype에 연결된 프로퍼티를 설정합니다.
ES5에 getPrototypeOf()가 있습니다.
let obj = {0: 10, 1: 20, length: 2}; Object.setPrototypeOf(obj, Array.prototype); const callback = (element, index, all) => console.log(element); obj.forEach(callback); const check = Object.prototype; // 10 // 20
setPrototypeOf()를 사용할 때 prototype이 대체됨으로써 기존에 쓰던 메소드를 못쓰는 상황이 생깁니다.
Object.prototype을 가지고 있지만 Array.prototype의 프로퍼티들도 사용하고 싶은 경우, 하나하나 따로 연결을 해주는 건 좋은 방법은 아닙니다.
첫 번째 파라미터에 prototype을 작성하고, 두 번째 파라미터의 prototype에 연결된 프로퍼티를 설정합니다.
JS에서 프로퍼티 식별자 해결(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()); // 300
기존의 setPrototypeOf()는 프로토타입을 대체하는 함수였습니다. 하지만 위 코드처럼 __proto__를 이용해 계층적 구조를 구현함으로써 prototype을 확장할 수 있습니다. 1단계 2단계 구조를 만드는 것이죠. 상속과 비슷한 개념으로 생각할 수도 있습니다.
하지만, prototype의 확장에는 생성자 함수의 처리가 없습니다. 그렇기 때문에 생성자 처리가 필요할 때는 super등의 상속 처리 키워드를 제공하는 Class를 사용하는게 좋습니다.