[모던자바스크립트] 23. 오브젝트


이 글은 번역 및 정리 글입니다. 출처: javascript.info

객체 (오브젝트)

자바스크립트에는 7가지 데이터 형식이 있다. 그 중 6개는 값에 단일 항목만 포함되기 때문에 원시타입이라고 한다.

반면, 객체는 다양한 데이터 또는 더 복잡한 엔티티의 키순 모음을 저장하는 데 사용된다. 자바스크립트에서 객체는 언어의 거의 모든 측면을 관통한다. 따라서 우리는 다른 곳으로 깊이 들어가기 전에 먼저 이해해야한다.

중괄호를 사용하여 객체를 만들 수 있다. key: value쌍으로 key는 문자열, value는 무엇이든 가능하다.

객체는 두 구문 중 하나를 사용하여 빈 객체를 만들 수 있다.

let user = new Object(); // "object constructor" 구문
let user = {}; // "object literal" 구문

일반적으로 리터럴 방식이 사용된다.

리터럴 및 속성

생성시 키밸류를 쌍으로 넣을 수 있다.

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

// 이렇게 꺼내올 수 있다.
alert(user.name); // John
alert(user.age); // 30

값은 모든 유형이 될 수 있다.

user.isAdmin = true;

속성을 제거하려면 delete를 사용할 수 있다.

delete user.age;

여러단어로 된 속성을 사용할 수 있지만 따옴표로 감싸져야한다.

let user = {
  "likes birds": true
};

마지막 속성도 쉼표로 끝날 수 있다. 이로인해 모든 줄이 똑같기 때문에 쉽게 추가, 제거 할 수 있다.

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

대괄호

여러 단어로 된 속성같은 경우는 도트 접근이 작동하지 않는다.

// error
user.likes birds = true

대괄호 표기법을 사용하여 해결할 수 있다.

let user = {};

// set
user["likes birds"] = true;

// get
alert(user["likes birds"]); // true

// delete
delete user["likes birds"];

키를 변수로 사용할 수도 있다.

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

let key = prompt("What do you want to know about the user?", "name");

// 변수로 접근
alert(user[key]); // John ("name"을 입력했다면)

계산된 속성

객체 리터럴에 대괄호를 사용할 수 있다. 이를 계산된 속성이라고 한다.

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
  [fruit]: 5 // fruit 으로 받은 값
};

alert(bag.apple); // 5, fruit="apple" 이면

위 코드는 아래 코드와 같이 동작한다.

let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};

bag[fruit] = 5;

대괄호는 활용성이 높지만 도트 표기법에 비해 사용하기 번거롭다. 그리고 오타가 날 가능성도 높다. 일상적으로 도트 접근을 사용하지만 더 복잡한것이 요구된다면 대괄호로 바꾸면 된다.

속성 이름은 예약어가 사용가능하다

변수는 for, let, return과 같은 예약어를 가질 수 없지만 객체는 속성으로 가질 수 있다.

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

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

기본적으로 모든 이름이 허용되지만 __proto__의 경우 객체가 아닌 값으로 설정할 수 없다.

let obj = {};
obj.__proto__ = 5;
alert(obj.__proto__); // [object Object], 의도대로 동작하지 않음.

5의 할당은 무시 되었다.

약식 속성값

실제 코드에서는 기존 변수를 속성 이름의 값으로 사용하는 경우가 더러있다.

function makeUser(name, age) {
  return {
    name: name, // 이런식으로
    age: age // 이런식으로
    // ...다른 속성들
  };
}

let user = makeUser("John", 30);
alert(user.name); // John

이를 더 짧게 만들 수 있는데, 아래와 같이 하면 된다.

function makeUser(name, age) {
  return {
    name, // name: name 이랑 똑같음
    age // age: age 이랑 똑같음
    // ...
  };
}

유무 확인

존재하지 않는 속성을 접근하려 하면 undefined를 반환한다.

let user = {};

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

in을 사용하여 확인 할 수도있다.

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

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

for…in 루프

객체의 모든 키를 살펴보려면 루프를 돌릴 수 있다. 우리가 이미 알고 있던 for와는 사용법이 조금 다르다.

for (key in object) {
  // 객체의 각 속성을 돌아다니며 이 바디를 실행한다.
}

모든 속성을 출력하는 예제를 봐보자.

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

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

물론 key가 아닌 다른 변수를 사용해도 된다. for (let prop in obj)

객체의 순서화

객체를 반복하면 순서가 동일하게 가져오는가? 객체는 어떻게 순서화되는가? 대답은 특별한 방식으로 정렬 된다.

정수 속성이 정렬되고 다른 속성은 생성 순서로 나타난다.

let codes = {
  "49": "Germany",
  "41": "Switzerland",
  "44": "Great Britain",
  // ..,
  "1": "USA"
};

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": "Germany",
  "+41": "Switzerland",
  "+44": "Great Britain",
  // ..,
  "+1": "USA"
};

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

참조 복사

객체와 원시 자료형의 기본적인 차이점 중 하나는 객체는 참조로 저장 및 복사된다는 것이다.

기본형은 값복사를 한다.

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

객체는 메모리의 주소, 즉 레퍼런스를 저장한다.

let user = { name: "John" };

let admin = user;

admin.name = "Pete"; //  "admin" 을 고쳤지만

alert(user.name); // 'Pete', "user" 도 바뀌었다.

즉 객체는 하나만 있고 그걸 참조하는 변수가 두개가 있는 것이다.

참조에 의한 비교

객체 비교도 마찬가지로 객체인 경우에만 동일할 경우에만 동일하다.

let a = {};
let b = a; // 레퍼런스 복사

alert(a == b); // true, 같은 레퍼런스
alert(a === b); // true

독립된 객체는 비어 있어도 동일하지 않다.

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

alert(a == b); // false

Const 객체

기본 자료형의 경우 const는 변경할 수 없지만, 객체인경우는 변경이 가능하다.

const user = {
  name: "John"
};

user.age = 25; // 오류는 나지 않는다.

alert(user.age); // 25

객체 변수는 참조를 저장한다고 하였다. 즉 참조를 변경하지 않으면 const를 해치지 않는것이기 때문에 내부를 수정할 수 있다.

참조를 변경하려 한다면 오류가 난다.

const user = {
  name: "John"
};

// Error (user를 재할당 할 수 없다.)
user = {
  name: "Pete"
};

복제와 병합, Object.assign

객체 변수를 복사하면 동일한 객체에 대한 참조가 하나 더 생성된다.

그렇다면 객체를 복제해야하는 경우는 어떻게 해야할까? 사실 대부분의 경우는 참조 복사가 더 좋지만 간혹 필요한 경우가 있다.

그러나 객체를 복제하려면 객체를 생성하고 그것의 속성을 반복해서 원시레벨 복사와 구조를 복사해야한다.

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

let clone = {}; // 빈 객체 생성

// 유저를 클론에 복사
for (let key in user) {
  clone[key] = user[key];
}

// 독립된 객체로 복제 되었다.
clone.name = "Pete"; // 클론의 데이터를 바꾸어도

alert(user.name); // 본 객체는 변경되지 않는다.

다른 방법으로는 Object.assign 메소드를 사용할 수 있다. 구문은 다음과 같다.

Object.assign(dest, [src1, src2, src3...])
  • destsrc1, ..., srcN의 객체이다.
  • 두번째 인자부터 모든 소스를 dest에 복사한다.

즉, 여러 객체를 하나로 병합하는 데 사용할 수 있다.

let user = { name: "John" };

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

// permissions1 과 permissions2 를 user에 복사
Object.assign(user, permissions1, permissions2);

// 이제 user = { name: "John", canView: true, canEdit: true } 가 되었다.

수신 객체에 이미 동일한 속성이 있는 경우는 덮어 쓴다.

let user = { name: "John" };

// name을 덮어쓰고, isAdmin 추가
Object.assign(user, { name: "Pete", isAdmin: true });

// now user = { name: "Pete", isAdmin: true }

클론을 하는데에 사용할 수도 있다.

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++; // user를 증가시키면
alert(clone.sizes.width); // 51, clone도 증가된다.

user[key]가 객체인 경우 해당 구조도 복제해야한다. 이를 딥클로닝이라고 한다.

숙제

오브젝트와 친해지기

  1. user 빈 객체를 만들자.
  2. name속성을 John으로 추가하자.
  3. surname속성을 Smith으로 추가하자.
  4. name속성을 Pete로 변경하자.
  5. name속성을 제거하자.

빈 객체 점검

isEmpth(obj)가 객체에 속성이 없으면 불리언을 반환하도록 작성하자.

아래처럼 동작하도록 해보자.

let schedule = {};

alert(isEmpty(schedule)); // true

schedule["8:30"] = "get up";

alert(isEmpty(schedule)); // false

Const 객체

아래 코드는 동작하는 코드인가?

const user = {
  name: "John"
};

// 동작하는가?
user.name = "Pete";

객체 속성을 합해보자

팀의 급여를 저장하는 객체가 있다. 모든 급여를 합산하고 sum변수에 저장하는 코드를 작성해보자. 답은 아래의 경우 390이다.

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
};

salaries가 비어있으면 결과는 0이 나와야한다.

숫자 속성에 2를 곱하기

multiplyNumeric(obj)로 모든 객체의 숫자 속성에 2를 곱하도록 만들어보자.

// 호출 전
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// 호출 이후
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};

multiplyNumeric은 아무것도 반환하지 않는다. 객체를 제자리에서 수정한다.

팁: typeof를 사용하여 숫자를 감별할 수 있다.


2023년 새해에는 성장하고 함께하고 싶다면?

Pre A 단계 이상의 스타트업 C 레벨들이 모여서 커뮤니티를 만들었습니다. 같이 스터디하고 친해질 일잘러를 찾습니다.




© 2017. by isme2n

Powered by aiden