[JavaScript] 객체

thingzoo·2023년 4월 28일
0

JavaScript

목록 보기
7/7
post-thumbnail

1. 객체

  • 몇 가지 특수한 기능을 가진 연관 배열(associative array)
  • 프로퍼티(키-값 쌍)를 저장
  • 값은 어떤 자료형도 가능
  • 프로퍼티에 함수 할당하여 메서드 생성

객체 생성

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

프로퍼티

let user = {     // 객체
  name: "John",  // 키: "name",  값: "John"
  age: 30        // 키: "age", 값: 30
  "likes birds": true  // 복수의 단어는 따옴표로 묶어야 합니다.
};

프로퍼티 접근법

점 표기법

// 프로퍼티 값 얻기
alert( user.name ); // John
alert( user.age ); // 30

대괄호 표기법

여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없음

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

계산된 프로퍼티

프로퍼티 키가 대괄호로 둘러싸여 있는 프로퍼티

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

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

alert( bag.apple ); // fruit에 "apple"이 할당되었다면, 5가 출력됩니다.

단축 프로퍼티

변수를 사용해 프로퍼티를 만드는 경우, 프로퍼티로 변수만 써도 됨

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

프로퍼티 이름의 제약사항

프로퍼티 이름에 특별한 제약이 없음

  • 예약어도 사용 가능
  • 문자형이나 심볼형에 속하지 않은 값은 문자열로 자동변환됨
  • 예외: __proto__
let obj = {};
obj.__proto__ = 5; // 숫자를 할당합니다.
alert(obj.__proto__); // [object Object] - 숫자를 할당했지만 값은 객체가 되었습니다. 의도한대로 동작하지 않네요.

프로퍼티 존재여부 확인

객체 특징 이용

  • 자바스크립트 객체의 중요한 특징 중 하나는 다른 언어와는 달리, 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined를 반환함!
  • 이런 특징을 응용하면 프로퍼티 존재 여부를 쉽게 확인 가능
let user = {};

alert( user.noSuchProperty === undefined ); // true는 '프로퍼티가 존재하지 않음'을 의미합니다.

in 연산자 이용

  • 프로퍼티 존재 여부를 확인하는 연산자
let user = { name: "John", age: 30 };

alert( "age" in user ); // user.age가 존재하므로 true가 출력됩니다.
alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false가 출력됩니다.

객체 순회

객체 정렬 방식

객체의 프로퍼티는 특별한 방식으로 정렬됨

  • 정수 프로퍼티: 자동으로 정렬
  • 그 외의 프로퍼티: 객체에 추가한 순서 그대로 정렬

for...in 문

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

for (let key in user) {
  // 키
  alert( key );  // name, age, isAdmin
  // 키에 해당하는 값
  alert( user[key] ); // John, 30, true
}

2. 참조에 의한 객체 복사

  • 원시값(문자열, 숫자, 불린 값)은 ‘값 그대로’ 저장·할당되고 복사되는 반면에, 객체는 ‘참조에 의해(by reference)’ 저장되고 복사된다는 것
  • 변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소’인 객체에 대한 '참조 값’이 저장됨
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

객체 복사

객체 순회로 복사

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

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

// 빈 객체에 user 프로퍼티 전부를 복사해 넣습니다.
for (let key in user) {
  clone[key] = user[key];
}

// 이제 clone은 완전히 독립적인 복제본이 되었습니다.
clone.name = "Pete"; // clone의 데이터를 변경합니다.

alert( user.name ); // 기존 객체에는 여전히 John이 있습니다.

얕은복사: Object.assign 사용

Object.assign(dest, [src1, src2, src3...])
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 }

깊은 복사: _.cloneDeep(obj) 사용

자바스크립트 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용

3. 메서드와 this

메서드 생성

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

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

user.sayHi(); // 안녕하세요!
// 아래 두 객체는 동일하게 동작합니다.

user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 단축 구문을 사용하니 더 깔끔해 보이네요.
user = {
  sayHi() { // "sayHi: function()"과 동일합니다.
    alert("Hello");
  }
};

this 키워드

메서드 내부에서 this 키워드를 사용하면 객체에 접근 가능
this는 메서드를 호출할 때 사용된 객체를 나타냄

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

  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  }

};

user.sayHi(); // John

자바스크립트에선 모든 함수에 this를 사용할 수 있음

  • this 값은 런타임에 결정됨
let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;

// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // Admin (점과 대괄호는 동일하게 동작함)

this가 없는 화살표 함수

화살표 함수는 일반 함수와는 달리 ‘고유한’ this를 가지지 않음
화살표 함수에서 this를 참조하면, 화살표 함수가 아닌 ‘평범한’ 외부 함수에서 this 값을 가져옴

  • 함수 arrow()의 this는 외부 함수 user.sayHi()의 this
let user = {
  firstName: "보라",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // 보라

4. 생성자 함수

생성자 함수

  1. 함수 이름의 첫 글자는 대문자로 시작
  2. 반드시 'new' 연산자를 붙여 실행
function User(name) {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;  (this가 암시적으로 반환됨)
}

new.target

자주 쓰이는 문법은 아님! 참고만

new.target 프로퍼티를 사용하면 함수가 new와 함께 호출되었는지 아닌지를 알 수 있음

return문

생성자 함수엔 보통 return 문이 없음
반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없음
그런데 만약 return 문이 있다면?

  • 객체를 return 한다면 this 대신 객체 반환
  • 원시형을 return 한다면 return문 무시

생성자내 메서드

생성자 함수로 메서드를 더해주는 것도 가능

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "제 이름은 " + this.name + "입니다." );
  };
}

let bora = new User("이보라");

bora.sayHi(); // 제 이름은 이보라입니다.

/*
bora = {
   name: "이보라",
   sayHi: function() { ... }
}
*/

5. 객체 순회와 변환

일반 객체 순회 관련 메서드

  • Object.keys(obj): 객체의 키만 담은 배열 반환
  • Object.values(obj): 객체의 값만 담은 배열 반환
  • Object.entries(obj): [키, 값] 쌍을 담은 배열 반환
    => Map, Set, Array 전용 메서드처럼 Iterable객체가 아닌 진짜 배열을 반환함

객체 변환

  • 객체엔 map, filter 같은 배열 전용 메서드를 사용할 수 없음
  • 대신 아래의 방법 사용
  1. Object.entries(obj)를 사용해 객체의 키-값 쌍이 요소인 배열을 얻습니다.
  2. 1.에서 만든 배열에 map 등의 배열 전용 메서드를 적용합니다.
  3. 2.에서 반환된 배열에 Object.fromEntries(array)를 적용해 배열을 다시 객체로 되돌립니다.
let prices = {
  banana: 1,
  orange: 2,
  meat: 4,
};

let doublePrices = Object.fromEntries(
  // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다.
  Object.entries(prices).map(([key, value]) => [key, value * 2])
);

alert(doublePrices.meat); // 8

6. 구조 분해 할당

  • 함수에 객체나 배열을 전달해야 하는 경우
  • 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우
  • 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우

배열 분해하기

// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]

// 구조 분해 할당을 이용해
// firstName엔 arr[0]을
// surname엔 arr[1]을 할당하였습니다.
let [firstName, surname] = arr;

alert(firstName); // Bora
alert(surname);  // Lee

객체 분해하기

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// { 객체 프로퍼티: 목표 변수 }
let {width: w, height: h, title = "default"} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

중첩 구조 분해

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true
};

// 코드를 여러 줄에 걸쳐 작성해 의도하는 바를 명확히 드러냄
let {
  size: { // size는 여기,
    width,
    height
  },
  items: [item1, item2], // items는 여기에 할당함
  title = "Menu" // 분해하려는 객체에 title 프로퍼티가 없으므로 기본값을 사용함
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

예: 매개변수 똑똑하게 넘겨주기

// 함수에 전달할 객체
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

// 똑똑한 함수는 전달받은 객체를 분해해 변수에 즉시 할당함
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – 객체 options에서 가져옴
  // width, height – 기본값
  alert( `${title} ${width} ${height}` ); // My Menu 200 100
  alert( items ); // Item1, Item2
}

showMenu(options);

Reference

🔗 모던 JavaScript 튜토리얼

profile
공부한 내용은 바로바로 기록하자!

0개의 댓글