본문 바로가기
Front-End/Javascript

javascript 기초부시기 - 데이터 타입 - object(객체) 3

by kimik 2022. 5. 29.

1. 객체

 

1.1 객체는 참조타입

객체는 객체타입 혹은 참조타입이라고 한다. 참조타입은 원시타입과 달리 모든 연산이 실제값이 아닌 참조값으로 처리된다는 의미이다.

원시타입의 값은 한번 정해지면 변경할 수 없지만(이에 대해서는 변경 불가능 값 을 다시 읽어보세요.) 객체는 프로퍼티를 변경, 추가, 삭제가 가능하므로 변경이 가능한 값이다.

 

객체는 언제든지 변경/추가/삭제가 일어나기 때문에 어느정도의 메모리를 확보해야하는지 알 수 없기 때문에 런타임에 메모리 공간을 확보하고 메모리의 힙 영역에 저장된다.

 

객체타입과 원시타입의 특징은 아래의 코드와 같다.

//참조 타입
var obj = {
  val: 10
}

var foo = obj;

console.log(obj.val, foo.val) //10 10
console.log(obj === foo) //true

obj.val = 20;

console.log(obj.val, foo.val) //20 20
console.log(obj === foo) //true

//원시타입
var str = 'hello'

var hi = str;

console.log(str, hi) // hello hello
console.log(str === hi) // true

str = 'world'

console.log(str, hi) // world hello
console.log(str === hi) // false

 

객체타입은 obj에 객체를 할당하는데 객체 자체를 저장하는게 아니라 생성된 객체의 참조값(address)를 저장한다.

foo에 obj를 할당하면 obj가 수정/추가/삭제 될때 foo 역시 obj가 참조한 참조값을 참조하기 때문에 값이 같이 변화된다.

하지만 원시타입은 객체타입과 같이 할당하더라도, str을 변경해도 str을 할당한 hi는 갱신되지 않는다.

 

var obj = { 
  val: 10 
}
var foo = {
  val: 10
}

console.log(obj.val, foo.val); // 10 10
console.log(obj === foo);      // false

위의 코드처럼 완전히 동일한 객체를 객체 리터럴로 생성하고, 두 객체를 비교하면 참조값이 다르기 때문에 같은 객체라고 하더라도 비교시엔 false를 반환한다.

 

1.2 객체도 불변(immutable)해야한다.

 

모든상황에서 그런것은 아니지만 대부분의 프레임워크(React, vue 등)에서 객체로 상태관리를 하거나, 특정 객체를 다른곳에서 참조하여 수정/추가/삭제시 다른 코드에도 영향을 미칠때, 이 객체에 대해 잘 알지못하는 타 개발자가 해당 객체를 수정/추가/삭제하면 의도치않은 결과 일어 날 수 있다.

그렇기 때문에 객체(배열 포함)의 불변성을 지키기위한 몇가지 방법들에 대해 서술한다.

 

1.2.1 Object.assign

 

Object assign은 target에 sources로 들어간 객체의 프로퍼티를 복사하여 target의 프로퍼티에 덮어쓰기한다. 덮어씌워지기 때문에 이때 동일한 키값을 가진 프로퍼티의 값은 sources의 값이 우선시 된다.

// Syntax
Object.assign(target, ...sources)
const obj = { a: 1 }
const source = { a: 2 } 

const clone = Object.assign({}, obj, source) 
//객체를 참조하는것이 아니라 새로운 객체를 만들기 위해서는 target부분에 빈 객체를 넣어야한다.

console.log(clone, obj, source ) //{a: 2} {a: 1} {a: 2} 같은 키값을 가졌기때문에 source의 값이 덮어씌워졌다.
console.log(clone === source, clone === obj) // false false
//clone은 새로운 객체이므로 obj와 source를 참조하지않는다.

const obj2 = { a: 1 }
const source2 = { a: 2 }

const clone2 = Object.assign(obj2, source2) //빈객체를 넣지않으면 obj2를 참조한 객체가 생성된다.

console.log(clone2 === obj2, clone2 === source) //true false

단 Object.assign으로 기존 객체를 복사하여 새로운 객체를 생성한다해도 이 방식은  얕은 복사라고 합니다. 

 

const person = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

const clonePerson = Object.assing({}, person}

clonePerson.name = 'Kim';
console.log(person.name); // Lee
console.log(clonePerson.name); // Kim

// 객체 내부의 객체(Nested Object)는 Shallow copy(얕은 복사)된다.
console.log(person.address === clonePerson.address); // true

person.address.city = 'Busan';
console.log(person.address.city); // Busan
console.log(clonePerson.address.city); // Busan
//객체 내부의 객체는 deep copy되지 않고 같은 값을 참조한다.

 

1.2.2 JSON.parse, JSON.stringify 활용

 

위의 Object.assign의 얕은 복사문제를 해결하는 방법중에 하나로 JSON의 메소드를 활용하는 방법이 있다.

 

const person = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

const clonePerson = JSON.parse(JSON.stringify(person))

console.log(person.address === clonePerson.address) //false

person.address.city = 'Busan';
console.log(person.address.city); // Seoul
console.log(clonePerson.address.city); // Busan

const obj = { key: undefined }
const cloneObj = JSON.parse(JSON.stringify(obj))
console.log(cloneObj) // {} stringify과정에서 유효하지않는 값을 빈 객체로 변환된다.

단점으로는

1. 가장 많은 메모리를 차지하고 늦은 처리 속도를 보인다.

2. JSON.stringify 과정에서 데이터가 유실 될 수 있다.

 

1.2.3 Immutable.js, lodash.js 와 같은 라이브러리 활용

import { _ } from "lodash";
import { Map } from "immutable";

const map1 = Map({
  name: "Lee",
  address: {
    city: "Seoul"
  }
});
const map2 = map1.set("address", { city: "Busan" });

console.log(map1, map2); // false
console.log(map1.get("address"), map2.get("address")); // {city: 'Seoul'} {city: 'Busan'}

const person = {
  name: "Lee",
  address: {
    city: "Seoul"
  }
};
const clonePerson = _.cloneDeep(person);

console.log(person.address === clonePerson.address); //false

person.address.city = "Busan";
console.log(person.address.city); // Seoul
console.log(clonePerson.address.city); // Busan

const obj = { key: undefined };
const cloneObj = _.cloneDeep(obj);
console.log(cloneObj); // { key: undefined } 정상적으로 노출된다.

외부라이브러리를 사용했기 때문에 확인을 하고싶다면 여기를 참고!

 

객체는 원시타입을 제외한 모든 값(배열, 함수 등)이기 때문에 객체의 모든 부분을 설명하려면 너무 길어지기때문에 프로토타입, 함수 등으로 나눠서 천천히 포스팅 하겠습니다. 

댓글