[TIL / DAY 12] 객체

miseullang·2024년 10월 29일

✅ 강의 내용 정리

📍 객체


키와 값으로 구성된 속성의 집합이며, 중괄호로 선언한다.

객체는 참조 자료형이다.

객체는 동적으로 추가, 또는 삭제를 할 수 있다.
⇒ 때문에 객체 내부에 없는 속성(프로퍼티)에 접근해도 undefined를 리턴 받는다.

추가 : 객체의 속성에 접근해서 값을 할당

삭제 : delete 키워드를 사용해서 특정 속성을 삭제

const userObj = {
    name: "철수",
    age: 20
};

// 객체 속성에 접근하는 방법
console.log(userObj.name);

// 객체 속성을 수정하는 방법
userObj.name = "영희";

대괄호 표기법으로도 접근할 수 있지만, 배열과 구분하기 위해 점 표기법을 주로 사용한다.

[ 🧀  / add ] 리터럴 표기법

데이터를 정의하는 데 있어서 간편하게 데이터를 정의할 수 있는 방법이다.

// 리터럴
const userObj = {
    name: "철수"
};

// 반 : 생성자 함수
const userObj = new Object();
userObj2.name = "철수";

기타 리터럴 표현:

// 배열 리터럴
const userArr = [];

// 문자 리터럴
const userName = "철수";

// 불리언 리터럴
const isUser = false;

// 숫자 리터럴
const num = 10;

// null 리터럴
const inUser = null;

hasOwnProperty()

객체 속성 값이 존재하는지 여부를 불린 값으로 알려주는 메서드다.

console.log(userObj.hasOwnProperty("name")); //true or false

객체 내부의 값을(자기 자신을) 호출하려면:

const userObj = {
   name: "철수",
   age: 20,
   getName: function() {
       console.log(userObj.name); // 또는 console.log(this.name);
   }
};

userObj.getName();

📍 This


this는 예약어로, 보통 함수 내부에서 많이 쓰인다.

this는 항상 함수를 호출한 객체를 가리킨다. 누가 자기를 호출했는가를 가리킨다.

const userObj = {
  name: "철수",
  getName: function () {
    console.log(this);
    console.log(userObj.name); // 철수
    console.log(this.name); // 철수
  }
}

userObj.getName(); // getName: ƒ ()name: "철수"[[Prototype]]: Object

객체의 키와 값이 같으면 생략할 수 있다 (단축 문법):

// 함수를 외부에 정의하고
function getName() {
   console.log(this);
}

// 객체로 불러올 수도 있음
const userObj = {
   name: "철수",
   getName,
};

userObj.getName();

단축 함수:

const userObj = {
   name: "철수",
   // 단축 함수 (실무에서 많이 쓰이는 방식)
   getName() {
      console.log(this);
   }
};

userObj.getName();

화살표 함수는 예외적으로 동작한다. 화살표 함수로 실행한 this는 그 함수가 선언된 스코프(어휘적 스코프)를 가리킨다.

const userObj = {
   name: "철수",
   getName: () => {
      console.log(this);
   }
};

userObj.getName(); // window를 가리킨다

객체를 반복(순회)할 수 있는 방법

1. for...in

const userObj = {
  name: "철수",
  age: 20,
};

// for-in으로 순회
for (let key in userObj) {
  console.log(key, userObj[key]);
}

2. Object.keys()

const userObj = {
  name: "철수",
  age: 20,
};

// 매개변수로 넣은 객체의 키 값을 배열 형태로 리턴
console.log(Object.keys(userObj));

const keysArr = Object.keys(userObj);
keysArr.forEach((key) => {
  console.log(key, userObj[key]);
});

3. Object.values()

const userObj = {
  name: "철수",
  age: 20,
};

// 키는 필요 없고 값에 바로 접근하고 싶을 때 사용
console.log(Object.values(userObj)); // ['철수', 20]

4. Object.entries()

const userObj = {
  name: "철수",
  age: 20,
};

Object.entries(userObj).forEach(([key, value]) => {
  console.log(key, value);
});
// name 철수
// age 20

console.log(Object.entries(userObj)); // [ [ 'name', '철수' ], [ 'age', 20 ] ]

객체의 병합과 복사

// 1. 얕은 복사 -> 객체, 배열 (참조 자료형)
const user = {
  name: "철수",
};

const copyUser = { ...user };
user.name = "영희";

console.log(user, copyUser); // { name: '영희' } { name: '철수' }

const copyUser3 = user;
user.name = "미나";

console.log(user, copyUser3); // { name: '미나' } { name: '미나' }

// 2. 깊은 복사 -> 기본 자료형
const copyUser2 = user;

let a = 10;
let b = a;

a = 20;
console.log(b); // a의 값이 변경된 것에 대해 영향을 받지 않음 => 깊은 복사가 된 것이다

깊은 복사에서 얕은 복사로 바꾸는 것은 불가능하지만, 얕은 복사를 깊은 복사로 바꾸는 것은 가능하다.

스프레드 연산의 깊은 복사는 1단계에 한해서만 동작한다.

const user = {
    name: "철수",
    age: 20,
    today: { eat: "meat" }, // today: 0x002
};

const user2 = { ...user };

user.age = 30;
user.today.eat = "vegitable";

console.log(user, user2);

이 코드는 얕은 복사(Shallow Copy)의 특성을 보여주는 예제다

  1. user2는 스프레드 연산자로 user를 복사
  2. user의 age를 30으로 변경하면 user2의 age는 영향받지 않음 (primitive value)
  3. user.today.eat을 "vegitable"로 변경하면 user2.today.eat도 함께 변경됨 (reference value)

출력결과:

// user
{
    name: "철수",
    age: 30,
    today: { eat: "vegitable" }
}

// user2
{
    name: "철수",
    age: 20,
    today: { eat: "vegitable" }  // today 객체는 참조가 복사되어 같이 변경됨
}

이는 스프레드 연산자가 1단계 깊이만 복사하고, 중첩된 객체는 참조를 복사하기 때문이다.

⇒ 해결 방법

// JSON 문자열로 바꾸고 다시 파싱
const user2 = JSON.parse(JSON.stringify(user));
// 중첩된 객체까지 완벽하게 복사

혹은 lodash 라이브러리 사용 (cloneDeep())

[ 🧀 / add ] 고급 개념

데이터 속성 설명자

객체 데이터 속성을 설명하는 것이다.

value, writable, enumerable, configurable

따로 오류가 뜨진 않는다.

const userObj = {
  age: 20,
  gender: "male",
};
userObj.name = "철수";

Object.defineProperty(userObj, "name", {
  value: "철수",
  writable: true, // 수정 가능 여부
  enumerable: false, // 열거 가능한 속성인지 = 순회할 때 접근 가능한 속성인지 여부
  configurable: true, // 설정에 대한 변경 가능 및 삭제 가능 여부 // false => delete 키워드로도 삭제 안 됨
});

userObj.name = "영희"; //writable: true => 값이 바뀜 // false => 오류가 뜨진 않지만 값이 바뀌지 않음

for (let key in userObj) {
  console.log(key);
} // enumerable: true => age / gender / name // false => 안 뜸

접근 속성 설명자

get, set
클래스에서 많이 사용하는 문법으로, 객체에서 이렇게까지 조작하는 경우는 드물다.

const userObj = {
  firstName: "John",
  lastName: "Hash",

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  set fullName(name) {
    const splitNames = name.split(" ");
    this.firstName = splitNames[0];
    this.lastName = splitNames[1];
  }
}

userObj.fullName = "Jane Doe"; // 그대로 변경되지 않고, userObj 내부의 set이 실행됨
console.log(userObj);

추가적으로 사용할 수 있는 메서드: 불변성을 유지하는 메서드

// Object.seal(); 객체의 속성을 추가하거나 삭제하는 것을 막는다
const userObj = Object.seal({
  name: "철수",
})

userObj.age = 20;
delete userObj.name;

console.log(userObj); // { name: '철수' }

// Object.preventExtensions() : 속성 추가 불가
const userObj2 = Object.seal({
  name: "철수",
})

userObj2.name = "영희";
userObj2.age = 20;
delete userObj2.name;

console.log(userObj2); // { name: '영희' }

// Object.freeze() => seal + preventExtensions

메서드 체이닝: 객체의 메서드는 연결해서 사용할 수 있다.

메서드 체이닝을 구현하려면 자기 자신을 내보내야 한다 (= 반환하는 것이 this여야 한다).

const calculate = {
  value: 0,
  add: function (n1) {
    this.value += n1;
    return this;
  },
  subtract: function (n1) {
    this.value -= n1;
    return this;
  },
  getResult: function () {
    return this.value;
  }
}

const result = calculate.add(10).subtract(5).getResult();
console.log(result);
profile
괴발개발 💻

0개의 댓글