Skip to content

Latest commit

 

History

History
520 lines (360 loc) · 19.2 KB

1주차.md

File metadata and controls

520 lines (360 loc) · 19.2 KB

객체: 기본

객체

자바스크립트엔 여덟 가지 자료형이 존재하며, 이 중 일곱 개는 오직 하나의 데이터(문자열, 숫자 등)만 담을 수 있어 '원시형(primitive type)'이라고 한다.

  1. 숫자
  2. BigInt
  3. 문자형
  4. boolean
  5. null
  6. undefined
  7. Symbol

하지만, 객체형은 원시형과 달리 다양한 데이터를 담을 수 있습니다. 키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장할 수 있다. 객체는 중괄호 {…}를 이용해 만들 수 있으며, 중괄호 안에는 ‘키(key): 값(value)’ 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있는데, 키엔 문자형, 값엔 모든 자료형이 허용됩니다. 프로퍼티 키는 ‘프로퍼티 이름’이라고 정의합니다.

객체 생성

  • 객체를 생성하는 방법은 2가지입니다.
let user = new Object(); // 1. '객체 생성자' 문법
let user = {}; // 2. '객체 리터럴' 문법
  • Key, Value 조건
let user = {
  // 객체
  name: "John", // 키: "name",  값: "John"
  age: 30, // 키: "age", 값: 30
  "likes birds": true,
  for: 1,
  let: 2,
  return: 3,
};
  • user에는 프로퍼티가 6개가 존재
    • 1번 프로퍼티 : "name"(이름)과 "John"(값)
    • 2번 프로퍼티 : "age"(이름)과 30(값)
    • 3번 프로퍼티 : "likes birds"(이름)과 true(값) = 단어 조합시 따옴표 추가
    • 4~6번 프로퍼티 : 변수 이름에 대한 예약어 특정 단어에 대한 ‘for’, ‘let’, ‘return’ 조건이 없다.

객체 읽기 방법

점 표기법

  • 점 표기법을 이용하면 프로퍼티 값을 읽는 것도 가능합니다.
// 프로퍼티 값 얻기
alert(user.name); // John
alert(user.age); // 30

대괄호 표기법

여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없기에, 대괄호 표기법을 사용합니다.

user["likes birds"] = true;

객체 제거 방법 : delete 연산

delete user.age;

상수 객체의 수정

const로 선언된 객체는 수정될 수 있습니다

const user = {
  name: "John",
};

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

alert(user.name); // Pete
  • constobject 를 할당할 경우, memory heapobject 를 위한 메모리를 만들고 해당 주소값을 const 식별자에 할당합니다. 즉, 해당 주소값은 변하진 않지만 참조하고 있는 object 는 값을 변화시킬 수 있습니다.

프로퍼티는 자유롭다.

  • 계산된 프로퍼티 가능

    • 변수로 프로퍼티 선언하거나 조합이 가능하다
    let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");
    let bag = {};
    
    // 변수 fruit을 사용해 프로퍼티 이름 생성
    bag[fruit] = 5;
    // 변수 fruit과 Count를 덧셈해 프로퍼티 이름 생성
    bag[fruit + "Count"] = 5;
  • 단축된 프로퍼티 가능

    function makeUser(name, age) {
      return {
        name: name, // name
        age: age, // age 으로 단축 가능
        // ...등등
      };
    }

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

  • 자바스크립트 객체의 중요한 특징 중 하나는 다른 언어와는 달리, 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined를 반환한다는 것입니다.

  • 이런 특징을 응용하면 프로퍼티 존재 여부를 쉽게 확인할 수 있습니다.

이를 in을 통해 판별이 가능하다.

let user = { age: 30 };

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

‘for…in’을 통한 객체 반복문

  • 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
}

프로퍼티에 순서가 있다고?

객체는 '특별한 방식으로 정렬’됩니다. 정수 프로퍼티(integer property)는 자동으로 정렬되고, 그 외의 프로퍼티는 객체에 추가한 순서 그대로 정렬됩니다.

let codes = {
  3: "스위스",
  2: "미국",
  1: "대한민국",
};

for (let code in codes) {
  alert(code); // 1, 2, 3
}

참조에 의한 객체 복사

객체와 원시 타입의 근본적인 차이 중 하나는 객체는 ‘참조에 의해(by reference)’ 저장되고 복사된다는 것입니다

  • 원시 값의 경우
    • 두 개의 독립된 변수에 각각 문자열 "Hello!"가 저장된다
let message = "Hello!";
let phrase = message;
  • 객체의 경우
    • 변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소’인 객체에 대한 '참조 값’이 저장됩니다.
let user = {
  name: "John",
};

객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 '참조’할 수 있는 값이 저장됩니다.

따라서 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.

얕은 복사

  • 위에 내용과 같이 객체가 저장되어있는 '메모리 주소’인 객체에 대한 '참조 값’이 저장하면서 발생하는 문제가 얕은 복사입니다.
let user = { name: "John" };

let admin = user;

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

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

객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있습니다. 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두 개인데, 그중 하나(admin)를 사용해 서랍장을 열어 정돈한 후, 또 다른 열쇠로 서랍장을 열면 정돈된 내용을 볼 수 있습니다.

참조에 의한 비교

  • 객체 비교 시 동등 연산자 ==와 일치 연산자 ===는 동일하게 동작합니다.
let a = {};
let b = a; // 참조에 의한 복사
let c = {};
alert(a == b); // true, 두 변수는 같은 객체를 참조합니다.
alert(a === b); // true
alert(a == c); //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을 사용하면 반복문 없이도 간단하게 객체를 복사할 수 있습니다.
    let user = {
      name: "John",
      age: 30,
    };
    
    let clone = Object.assign({}, user);
  • 객체는 참조에 의해 할당되고 복사됩니다. 변수엔 ‘객체’ 자체가 아닌 메모리상의 주소인 '참조’가 저장됩니다. 따라서 객체가 할당된 변수를 복사하거나 함수의 인자로 넘길 땐 객체가 아닌 객체의 참조가 복사됩니다.

  • 복사된 참조를 이용한 모든 작업(프로퍼티 추가·삭제 등)은 동일한 객체를 대상으로 이뤄집니다.

  • 객체의 '진짜 복사본’을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 Object.assign이나 '깊은 복사’를 가능하게 해주는 lodash 라이브러리에서 제공하는 _.cloneDeep(obj)를 사용하면 됩니다. 이때 얕은 복사본은 중첩 객체를 처리하지 못한다는 점을 기억해 두시기 바랍니다.

가비지 컬렉션

자바스크립트는 도달 가능성(reachability) 이라는 개념을 사용해 메모리 관리를 수행합니다

도달 가능성이란?

쉽게 말해 어떻게든 접근하거나 사용할 수 있는 값을 의미합니다. 도달 가능한 값은 메모리에서 삭제되지 않습니다.

root 라는 태생부터 도달 가능하기 때문에, 명백한 이유 없이는 삭제되지 않습니다.

  • root 값 종류
    • 현재 함수의 지역 변수와 매개변수
    • 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
    • 전역 변수

위의 있는 루트 값들에 대해 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 됩니다.

즉, 위와 같은 말은 가비지 컬렉션에 수집되는 값들은 더 이상 참조되지 않는 값, 사용되지 않는 값을 말합니다.

  • 가비지 컬렉션은 엔진이 자동으로 수행하므로 개발자는 이를 억지로 실행하거나 막을 수 없습니다.
  • 객체는 도달 가능한 상태일 때 메모리에 남습니다.
  • 객체가 참조되지 않는, 도달 불가능한 상태일 경우 가비지 컬렉션에 수집됩니다.
  • 참조된다고 해서 도달 가능한 것은 아닙니다. 서로 연결된 객체들도 도달 불가능할 수 있습니다.

가비지 컬렉션 내부 알고리즘 Mark-and-Sweep

내부적으로 Mark-and-Sweep을 활용합니다.

  • 가비지 컬렉션의 단계
    • 루트(Root)에 접근하여 Mark한다.
    • 루트 참조하는 객체에 접근하여 Mark한다.
    • 더이상 참조하는 객체가 없을 때까지 객체가 참조하는 객체에 접근하여 Mark한다.
    • 더이상 접근할 객체가 없으면, Mark되지 않은 객체를 메모리에서 삭제(Sweep)한다.

메서드와 this

메서드

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

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

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

함수 표현식으로 함수를 만들고, 객체 프로퍼티 user.sayHi에 함수를 할당해 주었습니다. 이렇게 객체 프로퍼티에 할당된 함수를 메서드(method) 라고 부릅니다.

  • 객체 프로퍼티에 저장된 함수를 '메서드’라고 부릅니다.

메서드에서 this를 호출하는 방법

메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있습니다.

이때 '점 앞’의 this는 객체를 나타냅니다. 정확히는 메서드를 호출할 때 사용된 객체를 나타냅니다.

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

  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  },
  // sayHi() {
  //   alert(user.name); // 'this' 대신 'user'를 이용함
  // }
};

user.sayHi(); // John
  • 메서드는 this로 객체를 참조합니다.

자유로운 this

자바스크립트의 this는 다른 프로그래밍 언어의 this와 동작 방식이 다릅니다. 자바스크립트에선 모든 함수에 this를 사용할 수 있습니다.

아래와 같이 코드를 작성해도 문법 에러가 발생하지 않습니다.

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

this 값은 런타임에 결정됩니다.

  • 함수를 선언할 때 this를 사용할 수 있습니다. 다만, 함수가 호출되기 전까지 this엔 값이 할당되지 않습니다.
  • 함수를 복사해 객체 간 전달할 수 있습니다.
  • 함수를 객체 프로퍼티에 저장해 object.method()같이 ‘메서드’ 형태로 호출하면 this는 object를 참조합니다.

this가 없는 화살표 함수

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

아래 예시에서 함수 arrow()의 this는 외부 함수 user.sayHi()의 this가 됩니다

let user = {
  firstName: "보라",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  },
};

user.sayHi(); // 보라
  • 화살표 함수는 자신만의 this를 가지지 않는다는 점에서 독특합니다. 화살표 함수 안에서 this를 사용하면, 외부에서 this 값을 가져옵니다.

new 연산자와 생성자 함수

new 연산자와 생성자 함수를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있습니다.

생성자 함수

  • 생성자 함수(constructor function)와 일반 함수에 기술적인 차이는 없습니다. 다만 생성자 함수는 아래 두 관례를 따릅니다.
    • 함수 이름의 첫 글자는 대문자로 시작합니다.
    • 반드시 new 연산자를 붙여 실행합니다.
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");
  • new User("보라")이외에도 new User("호진"), new User("지민") 등을 이용하면 손쉽게 사용자 객체를 만들 수 있습니다.

생성자와 return

생성자 함수엔 보통 return 문이 없습니다. 반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없습니다.

  • 새로운 객체를 return 한다면 this 대신 return의 객체가 반환됩니다.
  • 원시형을 return 한다면 return문이 무시됩니다.
//1
function BigUser() {
  this.name = "원숭이";

  return { name: "고릴라" }; // <-- this가 아닌 새로운 객체를 반환함
}

alert(new BigUser().name);
//2
function SmallUser() {
  this.name = "원숭이";

  return; // <-- this를 반환함
}

alert(new SmallUser().name);

생성자 내 메서드

  • 생성자 함수 내에 메서드를 더해주는 것도 가능합니다.
    • 아래 예시에서 new User(name)는 프로퍼티 name과 메서드 sayHi를 가진 객체를 만들어줍니다.
function User(name) {
  this.name = name;

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

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

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

옵셔널 체이닝 '?.'

  • 옵셔널 체이닝(optional chaining) ?.을 사용하면 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있습니다.

옵셔널 체이닝이 필요한 이유

  • 사용자가 여러 명 있는데 그중 몇 명은 주소 정보를 가지고 있지 않다고 가정해봅시다. 이럴 때 에러가 발생할 수 있습니다.
    • user.address.street를 사용해 주소 정보에 접근했는데 없는 경우
    • querySelector(...) 호출 결과가 null인 경우 에러 발생
// 1.user.address.street를 사용해 주소 정보에 접근했는데 없는 경우
let user = {}; // 주소 정보가 없는 사용자

alert(user.address.street);

// 2. querySelector(...) 호출 결과가 null인 경우 에러 발생
let html = document.querySelector(".my-element").innerHTML;

?.은 ?.'앞’의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환합니다.

옵셔널 체이닝을 남발하지 말아야하는 이유

  • ?.는 존재하지 않아도 괜찮은 대상에만 사용해야 합니다.

  • 사용자 주소를 다루는 위 예시에서 논리상 user는 반드시 있어야 하는데 address는 필수값이 아닙니다. 그러니 user.address?.street를 사용하는 것이 바람직합니다.

  • 실수로 인해 user에 값을 할당하지 않았다면 바로 알아낼 수 있도록 해야 합니다. 그렇지 않으면 에러를 조기에 발견하지 못하고 디버깅이 어려워집니다.

  • 옵셔널 체이닝 문법 ?.은 세 가지 형태로 사용할 수 있습니다.

    • obj?.prop – obj가 존재하면 obj.prop을 반환하고, 그렇지 않으면 undefined를 반환함
    • obj?.[prop] – obj가 존재하면 obj[prop]을 반환하고, 그렇지 않으면 undefined를 반환함
    • obj?.method() – obj가 존재하면 obj.method()를 호출하고, 그렇지 않으면 undefined를 반환함

옵셔널 체이닝 문법은 꽤 직관적이고 사용하기도 쉽습니다. ?. 왼쪽 평가 대상이 null이나 undefined인지 확인하고 null이나 undefined가 아니라면 평가를 계속 진행합니다.

  • ?.를 계속 연결해서 체인을 만들면 중첩 프로퍼티들에 안전하게 접근할 수 있습니다.
  • ?.은 ?.왼쪽 평가대상이 없어도 괜찮은 경우에만 선택적으로 사용해야 합니다.
  • 꼭 있어야 하는 값인데 없는 경우에 ?.을 사용하면 프로그래밍 에러를 쉽게 찾을 수 없다는 단점이 있습니다.

심볼형

  • 자바스크립트는 객체 프로퍼티 키로 오직 문자형과 심볼형만을 허용합니다.
    • 심볼
      • '심볼(symbol)'은 유일한 식별자(unique identifier)를 만들고 싶을 때 사용합니다.
      • Symbol()을 사용하면 심볼값을 만들 수 있습니다.
      • 심볼은 이름이 같더라도 값이 항상 다릅니다. 이름이 같을 때 값도 같길 원한다면 전역 레지스트리를 사용해야 합니다.
  • 심볼은 문자형으로 자동 형 변환되지 않습니다.
    • 자바스크립트에선 문자형으로의 암시적 형 변환이 비교적 자유롭게 일어나는 편입니다.
    • alert 함수가 거의 모든 값을 인자로 받을 수 있는 이유가 이 때문이죠. 그러나 심볼은 예외입니다.
    • 심볼형 값은 다른 자료형으로 암시적 형 변환(자동 형 변환)되지 않습니다.

하지만 어림도 없는 심볼 은닉화

사실 심볼을 완전히 숨길 방법은 없습니다. 내장 메서드 Object.getOwnPropertySymbols(obj)를 사용하면 모든 심볼을 볼 수 있고, 메서드 Reflect.ownKeys(obj)는 심볼형 키를 포함한 객체의 모든 키를 반환해줍니다.

객체를 원시형으로 변환하기

  • 원시값을 기대하는 내장 함수나 연산자를 사용할 때 객체-원시형으로의 형 변환이 자동으로 일어납니다.

  • 객체-원시형으로의 형 변환은 hint를 기준으로 세 종류로 구분할 수 있습니다.

    • "string" (alert 같이 문자열을 필요로 하는 연산)
    • "number" (수학 연산)
    • "default" (드물게 발생함)
  • 연산자별로 어떤 hint가 적용되는지는 명세서에서 찾아볼 수 있습니다. 연산자가 기대하는 피연산자를 '확신할 수 없을 때’에는 hint가 "default"가 됩니다.

  • default인 경우는 아주 드물게 발생합니다. 내장 객체는 대개 hint가 "default"일 때와 "number"일 때를 동일하게 처리합니다. 따라서 실무에선 hint가 "default"인 경우와 "number"인 경우를 합쳐서 처리하는 경우가 많습니다.

  • 객체-원시형 변환엔 다음 알고리즘이 적용됩니다.

    • 객체에 obj[Symbol.toPrimitive](hint)메서드가 있는지 찾고, 있다면 호출합니다.
    • 1에 해당하지 않고 hint가 "string"이라면, obj.toString()이나 obj.valueOf()를 호출합니다.
    • 1과 2에 해당하지 않고, hint가 "number"나 "default"라면, obj.valueOf()obj.toString()을 호출합니다.