객체: 기본 | 객체, 참조, 메서드와 this

345·2023년 2월 3일

모던 JavaScript

목록 보기
4/23

✨ 객체

객체 자료형은 오직 하나의 데이터만 담을 수 있는 원시형(primitive type) 과 달리 다양한 자료형의 데이터를 담을 수 있습니다.

const obj = { 
  say: "hello",
  age: 10
}

중괄호 { } 내부에 key: value 쌍으로 구성된 프로퍼티 를 가집니다.

객체 선언하기

객체는 두 가지 방법으로 만들 수 있습니다.

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

객체를 선언할 때는 주로 중괄호 { } 를 이용합니다.

🔥 프로퍼티 다루기

key: value 의 쌍으로 구성된 프로퍼티는 중괄호 내부에 위치하며 그 객체의 멤버처럼 취급됩니다.

key 는 그 프로퍼티를 부르기 위해 사용되는 이름으로, 문자 자료형만을 허용합니다. 프로퍼티의 값에는 어떠한 자료형을 넣어도 괜찮습니다.

  • key : 문자형
  • value : 모든 자료형

읽기

프로퍼티를 읽어오는 방법에는 두 가지가 있습니다.

const obj = { 
  say: "hello",
  age: 10
}

alert(obj.say); // hello
alert(obj['age']); // 10
  1. obj.key
  2. obj["key"]

점과 대괄호를 이용하여 프로퍼티 값을 읽어옵니다.

추가

프로퍼티를 추가할 수도 있습니다.

const obj = { 
  say: "hello",
  age: 10
}

obj.name = "JJ";

console.log(obj); // { say: "hello", age: 10, name: "JJ"}

프로퍼티를 읽을 때의 방법을 사용하여, 값을 할당하는 부분을 추가하면 새로운 프로퍼티를 추가할 수 있습니다.

수정

프로퍼티를 추가할 때와 같은 방법을 사용합니다.
key 가 객체에 이미 존재한다면 그 프로퍼티 키의 값을 수정하게 되고,
존재하지 않는다면 새로운 key: value 쌍의 프로퍼티가 객체에 추가됩니다.

const obj = { 
  say: "hello",
  age: 10
}

obj.name = "JJ"; // 새로운 프로퍼티 추가
obj.age = 30; // 기존의 프로퍼티 수정

여기서 의문이 하나 생깁니다.

❓ const 인데 수정이 되나?

객체를 const 로 선언했어도 프로퍼티 각각은 수정 가능합니다.
그러나 객체 자체를 다른 값으로 수정하는 것은 불가능합니다.

삭제

delete 연산자로 프로퍼티를 삭제할 수 있습니다.

const obj = { 
  say: "hello",
  age: 10
}

delete obj.age;

변수와 프로퍼티

프로퍼티 이름을 변수에서 받아와 사용하는 것도 가능합니다.

let fruit = "apple";

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

alert( bag.apple ); // 5 출력

존재여부 확인

자바스크립트는 객체에 존재하지 않는 프로퍼티에 접근해도
오류를 내지 않고, undefined 를 반환합니다.

프로퍼티 값으로 undefined 를 할당하는 경우도 있기 때문에
값이 undefined 라는 걸로 존재 여부를 확인하는 것은 적절하지 않습니다.

in 연산자로 프로퍼티의 존재 여부를 확인할 수 있습니다.

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

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

따옴표 " " 를 생략하면, 해당 이름의 변수를 찾아가니 주의해야 합니다.


반복문

for...in 반복문을 이용하여 객체의 모든 키를 순회할 수 있습니다.

for (key in object) {
  // 각 프로퍼티 키(key)를 이용하여 본문(body)을 실행
}

예시는 다음과 같습니다.

let users = {
  user1: "John",
  user2: "Amy",
  user3: "Rose"
};

for (let user in users) {
  // user 이라는 변수에 프로퍼티 이름이 순차적으로 할당됨
  alert( user );  // user1, user2, user3
  alert( users[user] ); // John, Amy, Rose
}

반복을 위한 변수를 선언하여 사용합니다.


자동 정렬

보통 프로퍼티는 추가한 순서대로 정렬됩니다.

그러나 프로퍼티 이름이 "41" 처럼 숫자로만 구성되어 그대로 정수 변환이 가능한 경우, 프로퍼티는 오름차순으로 자동 정렬됩니다.

자동 정렬을 막기 위해서는 "+41" 처럼 기호를 덧붙여 그대로 정수 변환을 할 수 없도록 만들면 됩니다.


✅ 참조에 의한 복사

객체는 원시 타입과 달리 참조에 의한 (by reference) 저장과 복사를 수행합니다.

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

원시값을 복사한 경우 값 그대로 가져와 복사합니다.
각각의 독립된 변수 message 와 phrase 에 문자열 "Hello!" 가 저장됩니다.

따라서, phrase 변수의 값을 변경해도 message 변수의 값에 영향을 끼치진 않죠.
둘은 독립적으로 존재하기 때문입니다.

그러나 객체의 복사 방식은 위처럼 작동하지 않습니다.


메모리 주소 복사

객체의 복사는 객체 그대로를 가져와 저장하는 것이 아닙니다.
어딘가에 있는 객체의 메모리 주소 에 대한 참조 값 을 저장합니다.

let user = { name: "John" };

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

{ name: "John" } 이라는 객체가 메모리에 존재하고, user 변수는 이 객체의 주소를 참조합니다.
admin 변수가 user 를 복사하면, 객체에 대한 참조 값을 복사하게 됩니다.

즉, user 변수와 admin 변수는 동일한 객체 { name: "John" } 에 대한 참조 값을 가지게 됩니다.
객체 { name: "John" } 가 복사과정에서 새로 하나 더 생기는 게 아닙니다❗


✨ 따라서, 객체를 조작할 때 다음과 같은 상황이 발생합니다.

let user = { name: 'John' };

let admin = user;

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

alert(user.name); // 'Pete'가 출력됨. 

admin 변수로 조작한 결과가 user 변수가 가리키는 객체에도 반영됩니다.
실질적으로 객체는 하나가 존재하며, 두 변수가 같은 객체를 참조하기 때문입니다.



참조에 의한 비교

객체 비교 시 동등 연산자 == 와 일치 연산자 === 는 동일하게 동작하며, 두 객체가 동일한 객체 (동일한 주소 값) 인 경우에 참을 반환합니다.

let a = {};
let b = a; // 참조에 의한 복사
let c = {}; // 새로운 객체 생성 | 참조 값이 다름

alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true

alert( a == c ); // false

객체를 복사하여 동일한 참조 값을 지니는 경우 같은 것으로 취급합니다.
반면, 값이 같더라도 새로운 객체로 생성된 경우에는 다른 것으로 취급합니다.


독립적인 복사

객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값을 복사합니다.

그러나, 동일한 객체 자체를 복제하고 싶다면❓
독립적인 객체로 만들고 싶다면❓

두 가지 방법이 존재합니다.

  1. 기존 객체의 프로퍼티를 순환하며 하나씩 값 추가
let user = {
  name: "John",
  age: 30
};

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

// 순회하며 하나씩 프로퍼티를 복사하기
// 원시 수준의 복사
for (let key in user) {
  clone[key] = user[key];
}

  1. Object.assign 메서드 사용하기

    Object.assign(dest, [src1, src2, src3...])

  • src1, src2, src3... 객체의 프로퍼티를 dest 에 복사
  • 결과로 dest 반환
let user = {
  name: "John",
  age: 30
};

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

중첩 객체

객체의 프로퍼티가 객체인 경우가 있습니다.

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

이런 경우, 자바스크립트 라이브러리 lodash 의 메서드인 _.cloneDeep(obj) 를 사용하면 깊은 복사를 수행할 수 있습니다.


🔔 메서드와 this

객체의 프로퍼티에는 객체나 원시값 뿐만 아니라 함수도 할당할 수 있습니다.
프로퍼티에 할당한 함수를 메서드 라고 합니다.
메서드 내부에서 this 라는 키워드를 사용하면 그 메서드의 객체에 접근할 수 있습니다.
메서드를 호출할 때 사용된 객체를 this 라고 합니다.

메서드 만들기

메서드를 만드는 방법에는 여러가지가 있습니다.

  1. 함수 표현식으로 할당
let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("안녕하세요!");
};

user.sayHi(); // 안녕하세요!

함수 표현식으로 프로퍼티에 함수를 할당합니다.

  1. 함수 선언식으로 할당
let user = {
  // ...
};

// 함수 선언
function sayHi() {
  alert("안녕하세요!");
};

// 선언된 함수를 메서드로 등록
user.sayHi = sayHi;

user.sayHi(); // 안녕하세요!

기존에 선언된 함수를 프로퍼티에 할당할 수도 있습니다.

  1. 객체 리터럴 { } 안에 함수 선언
// 일반적인 방법
user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 단축 구문 사용
user = {
  sayHi() { // "sayHi: function()"과 동일
    alert("Hello");
  }
};

일반적인 방법과 단축 구문 방법은 객체 상속 관련하여 약간의 차이가 존재합니다.

this 의 동작

this 는 다음과 같이 소속한 객체를 나타냅니다.

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

  sayHi() {
    // 'this'는 '현재 객체'를 의미
    alert(this.name);
  }

};

user.sayHi(); // John

자바스크립트의 this 는 객체 메서드가 아닌 일반 함수에서도 사용 가능합니다.
this 값은 런타임에 결정되며, 동일한 함수여도 호출한 객체에 따라 this 의 참조 대상이 달라집니다.

this 의 값은 상황에 따라 달라진다고 할 수 있습니다.

  1. 전역 공간 / 객체 없이 사용한 함수 내부의 this : 전역 객체 window / undefined (엄격 모드)
  2. 메서드 내부의 this : 해당 메서드를 호출한 객체
  3. 일반 함수 내부의 this : 정해지지 않음. 1이나 2의 경우로 나눠짐

화살표 함수에는 this 가 ❌

화살표 함수는 this 를 가지지 않습니다.
따라서, 화살표 함수에서 this 를 참조하면 외부 컨텍스트의 this 값을 이용합니다.

  • 화살표 함수에는 고유한 this 가 없다❗
let user = {
  firstName: "보라",
  sayHi: () => alert(this.firstName);
};

user.sayHi(); // undefined

따라서, 메서드를 만들 때 화살표 함수 문법을 사용하여 this 를 이용하면
의도한 대로 동작하지 않습니다.

화살표 함수에는 this 가 없어 전역 객체를 참조하고,
결과적으로 undefined 가 나옵니다.


  • 화살표 함수는 상위 컨텍스트의 this 를 얻어온다 ❗
let user = {
  firstName: "보라",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // 보라

sayHi 메서드의 thisuser 이 참조한 객체이죠?
sayHi 메서드 내부의 화살표 함수는 this 를 가지지 않으므로,
바로 상위 컨텍스트인 sayHi 메서드의 this 를 얻어옵니다.

따라서, 위 코드와 같이 동작하게 됩니다.

profile
기록용 블로그 + 오류가 있을 수 있습니다🔥

0개의 댓글