[데브코스/TIL] DAY17 - JavaScript(6) 생성자 함수와 프로토타입

Minha Ahn·2024년 10월 30일
0

데브코스

목록 보기
14/22
post-thumbnail

➕ 함수 내용 추가

1. 즉시 실행 함수 (IIFE)

🔎 즉시 실행 함수란?

함수 정의와 동시에 즉시 호출되는 함수로, 단 한 번만 호출되며 다시 호출할 수 없음

(function greet() {
  console.log("Hello");
})();

(function greet() {
  console.log("Hello2");
}());

두 가지 모두 즉시 실행 함수이다.
그러나 프리티어 덕분인지, 저장하면 위의 형식으로 통일된다.
함수의 정의를 감싸냐, 호출하는 것을 감싸냐의 차이다.
괄호의 위치가 크게 문제되지는 않는다고 한다.

모던 자바스크리브 딥 다이브 내용

  • 익명 함수를 사용하는 것이 일반적이나, 익명 함수나 기명 함수 모두 사용 가능 (화살표 함수 O, 함수 표현식 X)
    • 그룹 연산자 (...) 안에 있는 함수는 함수 리터럴로 동작
    • 따라서, 기명 함수로 해도 함수 몸체에서만 참조하는 식별자로 호출 불가
  • 반드시 그룹 연산자 (...)로 감싸야 함
    • 먼저 함수 리터럴을 평가해서 함수 객체를 생성하기 위함
    • 즉, 함수 객체를 생성할 수 있다면 그룹 연산자 이외의 연산자를 사용해도 좋음
    • 그룹 연산자 내에서는 피연산자가 값으로 평가되어야 하기 때문에 함수 리터럴로 평가되어 함수 객체 생성
  • 즉시 실행 함수도 값 반환 & 인자 전달 가능
  • 보통 라이브러리, 플러그인들이 즉시 실행 함수로 구현됨
    • 스코프 격리, 모듈화, 호출없이 즉시 실행, 클로저 활용 등의 이유로
  • 즉시 실행 함수는 자신만의 지역 스코프 생성
    • 즉시 실행 함수 내에 코드를 모아두면 변수나 함수 이름의 충돌 방지 가능

이렇게 사용하면 에러 발생해요

// 함수 이름 생략되어 함수 선언문 형식에 맞지 않아서 에러
function() {
  // ...
}();

// JS 엔진이 함수 선언문이 끝나는 블록 뒤에 세미콜론 추가
// 그룹 연산자에 피연산자가 없어서 에러
function foo() {
  // ...
}();

그룹 연산자 말고 이것도 좋아요

// 그룹 연산자
(function () {
  // ...
}());

(function () {
  // ...
})();

// 그 외 연산자
!function () {
  // ...
}();

+ function () {
  // ...
}();

2. 즉시 실행 함수 활용

다음은 즉시 실행 함수를 사용하여 모듈 패턴을 구현한 예시다.

const myModule = (function () {
  let privateVariable = "I am private";
  return {
    getPrivateValue() {
      return privateVariable;
    },
  };
})();

console.log(myModule.getPrivateValue()); // I am private

모듈 패턴이란
자바스크립트에서 특정 코드의 기능을 캡슐화하고, 내부 구현을 외부에서 숨기기 위해 사용하는 디자인 패턴이다.
이렇게 사용함으로써 전역 변수를 오염시키지 않고, 내부에서 필요한 변수를 관리할 수 있다.



✨ 생성자 함수

🔎 생성자 함수란?

자바스크립트에서 객체를 생성하는 데 사용되는 특별한 형태의 함수

  • 주로 객체의 초기 상태를 설정 & 해당 객체의 인스턴스를 생성하는 데 사용
  • 관례적으로 대문자로 시작하는 파스칼 케이스로 작성

객체 리터럴로 생성 & 생성자 함수로 생성

function Car() {
  this.name = "Benz";
  this.color = "white";
}
const car1 = new Car();

const car2 = {
  name: "Benz",
  color: "white",
};

console.log(car1);
console.log(car2);

출력하면 다음과 같이 나온다.

생성자 함수로 객체를 생성할 경우 생성자 함수 이름이 함께 출력된다.


1. 생성자 함수의 객체 정의

function User() {
  this.name = "철수";
  this.age = 30;
}

const user = new User();

this 키워드

  • 생성자 함수 내부의 this는 생성될 객체(= 인스턴스) 지칭
  • 참고로 보통 함수에서의 this는 함수를 호출한 대상을 지칭
    • 전역에 정의된 일반 함수 => window나 global 객체
    • 객체의 메서드 => 해당 메서드를 포함하는 객체

new 키워드

  • 생성자 함수 호출을 위해서 반드시 필요한 키워드
  • new 키워드를 사용하면
    • 새로운 객체 생성
    • 생성자 함수 내의 this가 새로 생성된 객체를 가르키도록 설정
    • 함수가 반환하는 객체가 있다면 해당 객체 반환. 그렇지 않다면 새로 생성된 객체 반환

2. 생성자 함수 사용의 장점

인스턴스 생성

🔎 인스턴스란?

생성자 함수로 만들어진 객체

  • 생성자 함수와 같은 구조를 가진 여러 개의 인스턴스 쉽게 생성 가능 (재사용성)
  • 각각의 인스턴스는 고유한 프로퍼티 소유
  • 인스턴스의 단점
    • 생성자 함수와 같은 구조의 프로퍼티와 메서드가 각각의 인스턴스에 동일 생성으로 인한 불필요한 메모리 낭비
    • 프로토타입으로 해결 가능

프로토타입을 통한 메서드 공유

🔎 프로토타입 (객체)란?

함수와 일대일로 배칭되는 프라이빗한 공간으로, 자바스크립트의 모든 함수는 프로토타입 소유

  • 생성자 함수의 프로토타입을 사용하는 경우, 모든 인스턴스가 동일한 메서드를 공유하여 사용 가능
  • 중복되는 메모리 감소 => 메모리 효율성 상승
  • 정리
    • 값이 변할 여지 있음 => 생성자 함수 내부
    • 값이 변하지 않고 공통적으로 사용됨 => 프로토타입 객체
  • 아래에서 더 자세히 설명

캡슐화 & 정보 은닉

  • 생성자 함수 내에 변수를 정의하고 메서드를 통해서만 접근하도록 설정 => 정보 은닉 구현
  • 객체 상태를 외부에서 직접 수정 불가

상속 구현

  • 생성자 함수의 프로토타입을 이용하여 상속 구현 가능
  • 부모 객체의 속성과 메서드를 자식 객체가 상속받음

추가

  • 인스턴스도 속성 동적 할당 삭제 가능



💰 프로토타입 객체

🔎 프로토타입이란?

자바스크립트의 모든 함수가 가지고 있는 각각의 고유한 속성이며, 자식 객체들이 자신을 참조할 수 있도록 만든 분신 객체

  • 모든 함수는 프로토타입 객체를 가지고 있다.
  • 자바스크립트는 프로토타입 기반의 언어로써, 원본 객체를 기반으로 복제하여 상속과 비슷한 기능을 구현할 수 있다.

생성자 함수의 프로토타입 객체

  • 생성자 함수의 프로토타입은 모든 인스턴스가 접근 가능
  • 공통으로 사용되는 메서드를 프로토타입에 옮기면 => 메모리 공간 절약
function Car() {
  this.name = "Benz";
  this.color = "white";
  this.getInfo = function () {
    return `${this.name}, ${this.color}`;
  };
}

const car = new Car();
console.log(car);

위와 같이 Car 생성자 함수로 객체를 생성하면
이런 구조로 인스턴스 무한 생성 가능

function Car() {
  this.name = "Benz";
  this.color = "white";
}

Car.prototype.getInfo = function () {
  return `${this.name}, ${this.color}`;
};

const car = new Car();
console.log(car);

위와 같이 Car 생성자 함수의 프로토타입 객체로 공통되는 메서드를 옮겨두면
모든 인스턴스는 Car 생성자의 프로토타입을 통해 공통 메서드에 접근 가능

콘솔창에 출력하면 다음과 같음

getInfo 메서드가 다른 프로퍼티와 같이 객체에 바로 위치getInfo 메서드가 [[Prototype]]이라는 속성을 통해 getInfo 접근 가능

🔎 [[Prototype]]이란?

자바스크립트 객체의 프로토타입을 나타내는 내부 슬롯

  • 객체가 어떤 프로토타입을 상속받고 있는지 확인 가능
  • 즉, 객체가 자신의 프로퍼티와 메서드 외에 [[Prototype]] 속성을 통해 다른 객체의 프로퍼티와 메서드 상속 가능
  • 화살표 함수에는 프로토타입 없음

프로토타입에 있는 메서드 실행

const car = new Car();
console.log(car.getInfo());
  • 별다른 처리없이 마치 객체의 메서드를 사용하듯 호출
  • Car 인스턴스에는 getInfo가 없는데 어떻게? => 프로토타입 체인 덕분

프로토타입 체인

🔎 프로토타입 체인이란?

인스턴스에서 자신을 생성한 생성자 함수의 프로토타입에 접근할 수 있는 방법

  • 모든 인스턴스가 가지고 있는 프로퍼티 [[Prototype]]
    • 자신이 속하는 객체의 생성자 함수 프로토타입으로 연결
    • 개발자가 프로토타입에 접근하기 위해서는 __proto__ 사용해야 함
car.[[Prototype]] // 불가능
car[[[Prototype]]] // 불가능

car.__proto__ // 가능
  • car.getInfo()는 실제로는 car.__proto__.getInfo()
    • 자바스크립트가 __proto__ 생략 가능하도록 해줌
    • 직접 car.__proto__.getInfo() 접근은 불가
    • 함수의 this는 자신을 호출한 객체를 의미하므로, 위와 같이 호출되면 this는 __proto__가 되어버림
  • 프로토타입 체인을 통해 필요한 프로퍼티나 메서드가 없으면 상위 프로토타입으로 범위를 넓혀 찾는 것 (스코프 체인과 유사)
  • 프로토타입 체인을 통해 상위로 올라가면 항상 최상위는 Object 객체

프로토타입 체이닝

🔎 프로토타입 체이닝이란?

인스턴스가 프로토타입 체인으로 프로퍼티나 메서드를 탐색해 나가는 과정

  • 인스턴스는 자신의 속성에 필요한 게 있는지 찾음
  • 없으면 __proto__를 따라 프로토타입으로 넓혀서 찾음
  • 따라서 공통된 속성은 프로토타입에 넣는 것이 좋음



🎁 래퍼 객체

🔎 래퍼 객체란?

기본 자료형인 원시 값들이 객체처럼 동작할 수 있도록 임시로 생성되는 객체

  • 자바스크립트는 원시 값에도 객체의 메서드와 프로퍼티 제공을 위해 필요 시, 자동으로 래처 객체 생성
  • 자바스크립트 엔진이 관련있는 생성자 함수의 인스턴스 객체로 감싸줌

1. 동작 방식

  • 원시 값이 객체처럼 프로퍼티나 메서드 호출 시, 원시 값의 래퍼 객체를 자동으로 생성
  • 프로퍼티나 메서드에 접근한 뒤에 바로 소멸

2. 예시

const PI = 3.1415926535897932384626433832;
console.log(PI.toFixed(2));
  • PI는 숫자형인 원시값
  • toFixed라는 Number 객체의 메서드 접근을 위해 래퍼 객체 생성 => PI는 Number 객체의 인스턴스
  • PI를 담은 래퍼 객체(Number 객체)의 __proto__를 통해 Number 객체의 프로토타입으로 연결되며, toFixed 메서드 접근
    • 추가로... Number 객체의 __proto__는 Object 객체의 프로토타입



🍳 생성자 함수의 활용

1. 생성자 함수를 프라이빗하게 사용

  • 생성자 함수로 만들어낸 객체의 단점
    • 인스턴스 객체의 프로퍼티에 직접 접근해서 조작하는 것이 가능함
    • 예기치 않은 동작을 구현할 수 있음
  • 외부에서 접근하지 못하는 변수를 만들어야 할 때도 있음
function Counter() {
  let count = 0; // 클로저
  this.increment = function () {
    count++;
  };

  this.decrement = function () {
    count--;
  };

  this.getCount = function () {
    return count;
  };
}
  • 생성자 함수의 내부 변수는 해당 함수의 프로토타입 객체로도 참조할 수 없음
  • 오로지 인스턴스의 메서드로만 참조 가능

2. 생성자 함수의 팩토리 패턴

  • 공장처럼 생성자 함수를 찍어내는 패턴
  • 생성자 함수를 하나로 모아두는 것
function createPerson(type, name) {
  function Employee(name) {
    this.name = name;
    this.type = "employee";
  }

  function Manager(name) {
    this.name = name;
    this.type = "manager";
  }

  switch (type) {
    case "employee":
      return new Employee(name);
    case "manager":
      return new Manager(name);
  }
}

const person = createPerson("employee", "John");

3. 생성자 함수로 상속 구현

  • 방식이 너무 복잡하고 힘듦..
function Person(name) {
  this.name = name;
}

// Person 생성자 함수의 프로토타입 객체에 introduce라는 메서드 생성
Person.prototype.introduce = function () {
  return `I an ${this.name}`;
};
// 상속처럼 보여지게 코드를 구현할 수 있음
function Developer(name, position) {
  Person.call(this, name);
  this.position = position;
}
  • Developer 생성자 함수에 Person 생성자 함수 호출
    • Developer 객체에 Person 객체의 프로퍼티를 받아올 수 있음
    • this는 Developer 인스턴스를 지칭
    • 즉, Developer 객체에도 name 프로퍼티 추가
Developer.prototype = Object.create(Person.prototype);
Developer.prototype.constructor = Developer;
  • Object.create로 Person.prototype을 상속받는 새로운 객체 생성
    • Object.create는 새로운 객체 생성 + 객체의 프로토타입을 특정 객체로 설정
  • Developer.prototype을 Person.prototype의 새로운 객체로 설정
    • Developer 인스턴스도 Person의 메서드와 프로퍼티에 접근 가능
  • Developer.prototype의 constructor를 Developer로 설정
    • 설정 전에는 constructor 프로퍼티가 없는 상태

Developer.prototype.skill = function () {
  return this.position;
};

const dev = new Developer("철수", "프론트개발자");



📃 정리

생성자 함수

  • Person 생성자 함수가 있을 때, Person 생성자 함수로 생성된 person 인스턴스
  • person 인스턴스의 __proto__는 Person 생성자 함수의 프로토타입 (Person.prototype)
  • Person 생성자 함수 프로토타입의 __proto__는 Object 생성자 함수의 프로토타입 (Object.prototype)

래퍼 객체

  • 숫자형인 원시값이 있고 Number 객체의 프로퍼티 접근 원하는 상태
  • 원시값을 래퍼 객체인 Number로 감싸 Number 생성자 함수의 인스턴스로 변경
  • Number 인스턴스의 __proto__는 Number 생성자 함수의 프로토타입
  • 이를 통해 프로퍼티 접근 가능
  • Number 생성자 함수 프로토타입의 __proto__는 Object 생성자 함수의 프로토타입

즉, 모든 함수의 프로토타입 객체는 Object 생성자 함수의 인스턴스





📌 출처

수코딩(https://www.sucoding.kr)

profile
프론트엔드를 공부하고 있는 학생입니다🐌

0개의 댓글