[JavaScript] 객체(1) -객체와 참조에 의한 객체 복사

김서현·2023년 4월 21일

JavaScript 스터디

목록 보기
3/8
post-thumbnail

객체

객체형 <-> 원시형 (오직 하나의 데이터(문자열, 숫자 등)만 담을 수 있다)

  • 다양한 데이터를 담을 수 있다.
  • 중괄호 {...}를 이용해 만든다.
  • 키(key) (문자형) : 값(value) (모든 자료형) 쌍으로 구성된 프로퍼티를 여러 개 넣을 수 있다.

빈 객체 만들기

let user = new Object(); // '객체 생성자' 문법
let user = {};  // '객체 리터럴' 문법

리터럴과 프로퍼티

let user = {     // 객체
  name: "John",  // 키: "name",  값: "John"
  age: 30        // 키: "age", 값: 30
};

프로퍼티 값 얻기 => 점 표기법 사용하기

alert( user.name ); // John
alert( user.age ); // 30

프로퍼티 값 추가

user.isAdmin = true;

프로퍼티 값 삭제

delete user.age;

❗ 여러 단어를 조합해 프로퍼티 이름을 만든 경우엔 프로퍼티 이름을 따옴표로 묶어줘야 한다.

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // 복수의 단어는 따옴표로 묶기
};

trailing(길게 늘어지는) 쉼표 or hanging(매달리는) 쉼표
마지막 프로퍼티 끝 뒤에 붙는 쉼표
👉 모든 프로퍼티가 유사한 형태를 보이기 때문에 프로퍼티 추가, 삭제, 이동이 쉬워진다.

const로 선언된 객체는 상수이기에 수정이 불가할까?❗
❌ 아니다! const로 선언된 객체는 수정될 수 있다.

const user = {
  name: "John"
};

user.name = "Pete"; // (*)

alert(user.name); // Pete

constuser의 값을 고정하지만, 그 내용은 고정하지 않기 때문이다.
👉 user = ... 와 같이 전체적으로 설정하려고 할 때만 오류를 발생시킨다!


대괄호 표기법

let user = {
  "likes birds": true  // 복수의 단어는 따옴표로 묶기
};

alert(user.likes birds); // error

여러 단어를 조합해 프로퍼티 키를 만든 경우엔 점 표기법을 사용 불가 ❌

👉 대괄호 표기법을 사용하자!

alert(user["likes birds"]);

대괄호 표기법은 이렇게도 사용 가능! (점 표기법은 불가능함)

let key = "likes birds";

user[key] = true; // user["likes birds"] = true; 와 같다.

👉 코드를 유연하게 작성 가능


계산된 프로퍼티

객체를 만들 때 객체 리터럴 안의 프로퍼티 키가 대괄호로 둘러싸여 있는 경우

let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");

let bag = {
  [fruit]: 5, // 변수 fruit에서 프로퍼티 이름을 동적으로 받아옴.
};

// fruit에 "apple"이 할당 되었다면 {"apple":5} 이므로
alert( bag.apple ); // 5

🔽 이렇게도 사용 가능!

let fruit = 'apple';
let bag = {
  [fruit + 'Computers']: 5 // bag.appleComputers = 5
};

단축 프로퍼티

function makeUser(name, age) {
  return {
    name: name,
    age: age,
    // ...등등
  };
}

이렇게 프로퍼티 이름과 값이 변수의 이름과 동일한 경우
프로퍼티 값 단축 구문을 사용하면

function makeUser(name, age) {
  return {
    name, // name: name 과 같음
    age,  // age: age 와 같음
    // ...
  };
}

이렇게 깔끔하게 사용할 수 있다!


프로퍼티 이름의 제약사항

변수 : for, let, return과 같은 예약어 사용 ❌
객체는 이런 제약이 없다!

let obj = {
  for: 1,
  let: 2,
  return: 3
};

alert( obj.for + obj.let + obj.return );  // 6

키에 숫자가 들어가면 문자열로 자동 변환된다.

let obj = {
  0: "test" // "0": "test"와 동일
};

// 숫자 0은 문자열 "0"으로 변환되기 때문에 아래 두 예시는 같은 프로퍼티에 접근
alert( obj["0"] ); // test
alert( obj[0] ); // test

__proto__
그러나 __proto__는 예외로, 예상대로 작동하지 않는다!

let obj = {};
obj.__proto__ = 5; // 숫자를 할당합니다.
alert(obj.__proto__); // [object Object]

참고 : 프로토타입 상속


in 연산자로 프로퍼티 존재 여부 확인하기

자바스크립트 객체 : 다른 언어와 달리 존재하지 않는 프로퍼티에 접근해도 에러가 발생하지 않고 undefined를 반환
👉 이 성질을 이용해 프로퍼티 존재 여부 확인하기

let user = {};

alert( user.noSuchProperty === undefined ); // true

🔽 in 문법으로도 프로퍼티 존재 여부 확인이 가능하다!

"key" in object
let user = { name: "John", age: 30 };

alert( "age" in user ); // true
alert( "blabla" in user ); // false

이렇게 변수 값을 이용해서도 가능

let user = { age: 30 };

let key = "age";
alert( key in user ); // true, 변수 key에 저장된 값("age")을 사용해 프로퍼티 존재 여부를 확인

❓ 그런데 왜 undefined랑 비교해도 충분한데 in 연산자가 있는거지?
undefined와의 비교로 검사했을 때 실패하는 예쩨

let obj = {
  test: undefined
};

alert( obj.test === undefined ); // true --> 우리가 예상하기로는 프로퍼티가 있으니까 false여야 했는데 ㅠㅠ

alert( "test" in obj ); // true

값이 undefined여서, 값에 접근했을 때 undefined를 뱉어버린다.


for...in 반복문

모든 키 순회하기

for (key in object) {
  
}

ex)

let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for (let key in user) {
  alert( key );  // name, age, isAdmin
  alert( user[key] ); // John, 30, true
}

객체 정렬 방식

프로퍼티엔 순서가 있을까? 반복문은 프로퍼티를 추가한 순서대로 실행될까? 이 순서는 항상 동일할까?
👉 정수 프로퍼티는 자동으로 정렬되고, 그 외 프로퍼티는 객체에 추가한 순서 그대로 정렬된다.

정수 프로퍼티
변형 없이 정수에서 왔다 갔다 할 수 있는 문자열

// 함수 Math.trunc는 소수점 아래를 버리고 숫자의 정수부만 반환합니다.
alert( String(Math.trunc(Number("49"))) ); // '49'가 출력됩니다. 기존에 입력한 값과 같으므로 정수 프로퍼티입니다.
alert( String(Math.trunc(Number("+49"))) ); // '49'가 출력됩니다. 기존에 입력한 값(+49)과 다르므로 정수 프로퍼티가 아닙니다.
alert( String(Math.trunc(Number("1.2"))) ); // '1'이 출력됩니다. 기존에 입력한 값(1.2)과 다르므로 정수 프로퍼티가 아닙니다.

정수 프로퍼티 예제

let codes = {
  "49": "독일",
  "41": "스위스",
  "44": "영국",
  // ..,
  "1": "미국"
};

for (let code in codes) {
  alert(code); // 1, 41, 44, 49 --> 자동 정렬됨
}

그 외 프로퍼티 예제

let user = {
  name: "John",
  surname: "Smith"
};
user.age = 25; // 프로퍼티를 하나 추가

for (let prop in user) {
  alert( prop ); // name, surname, age
}

❓ 그럼 정수 키도 추가된 순서 대로 순회하고 싶으면?
이렇게 앞에 +를 붙여보자!

let codes = {
  "+49": "독일",
  "+41": "스위스",
  "+44": "영국",
  // ..,
  "+1": "미국"
};

for (let code in codes) {
  alert( +code ); // 49, 41, 44, 1
}

+code code 앞에 붙어있는 +는 어떤 문법이지?

➕ 참고
일반 객체 외 다양한 객체

  • Array – 정렬된 데이터 컬렉션을 저장할 때 쓰임
  • Date – 날짜와 시간 정보를 저장할 때 쓰임
  • Error – 에러 정보를 저장할 때 쓰임
  • 기타 등등


참조에 의한 객체 복사

객체는 ‘참조에 의해(by reference)’ 저장되고 복사된다.
<-> 원시값은 '값 그대로' 저장, 할당되고 복사된다.

원시값 ex)

let message = "Hello!";
let phrase = message;

두 개의 독립된 변수에 각각 문자열 "Hello!" 저장

객체 ex)

let user = {
  name: "John"
};

변수에 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소’객체에 대한 '참조 값’이 저장

let user = { name: "John" };

let admin = user; // 참조값을 복사

👉 그래서!! 객체에 접근하거나 조작할 때 user, admin 변수에서 모두 가능하다.

admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨

alert(user.name); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함

참조에 의한 비교

ex1) 같은 객체를 참조하기 때문에 비교시 참 반환

let a = {};
let b = a; // 참조에 의한 복사

alert( a == b ); // true
alert( a === b ); // true

ex2) 독립된 두 객체이기 때문에 비교시 거짓 반환

let a = {};
let b = {}; // 독립된 두 객체

alert( a == b ); // false

객체 복사, 병합과 Object.assign

그런데 기존에 있던 객체와 똑같으면서 독립적인 객체를 만들고 싶다면?
👉 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회해 복사

let user = {
  name: "John",
  age: 30
};

let clone = {}; // 새로운 빈 객체

// 순회해서 복사
for (let key in user) {
  clone[key] = user[key];
}

clone.name = "Pete"; // clone의 데이터를 변경

alert( user.name ); // "John" --> 그럼에도 기존 객체에는 여전히 John이 있음!

🔽 Object.assign을 사용해도 된다

Object.assign(dest, [src1, src2, src3...])
  • dest : 목표하는 객체
  • src1, ..., srcN : 복사하고자 하는 객체

src1, ..., srcN 의 프로퍼티를 dest에 복사.
dest 반환

❗ 목표 객체에 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값이 덮어씌워짐

ex1)

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// permissions1과 permissions2의 프로퍼티를 user로 복사
Object.assign(user, permissions1, permissions2);

// now user = { name: "John", canView: true, canEdit: true }

ex2)

let user = {
  name: "John",
  age: 30
};

let clone = Object.assign({}, user);

중첩 객체 복사

그런데 프로퍼티가 원시값이 아니라 다른 객체에 대한 참조 값이라면?
다른 객체를 참조하고 있는 그 프로퍼티는 같은 객체를 참조하게 된다! ( 완전히 독립된 객체로 복사할 수 없는 것 )

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true

// user와 clone는 sizes를 공유
user.sizes.width++;       // 프로퍼티를 변경합니다.
alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인 가능

❓ 그럼 이 문제를 어떻게 해결하지?
깊은 복사 : user[key]의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 한다!

➕ 참고
자바스크립트 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용하면 깊은 복사를 처리할 수 있다.

0개의 댓글