[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 3일차(컨텍스트, 생성자 함수, 프로토타입)

김하은·2024년 7월 17일
0
post-thumbnail

오늘은 그동안 관심 있었던 주제들이 많아 수업이 더 흥미로웠다. 특히 기억하고 싶은 내용들을 정리하고자 한다. 수업을 들으며 급하게 필기했기 때문에 아래 내용에 틀린 부분이 있을 수도 있다. 그래도 오늘 배운 내용을 잊지 않기 위해 일단 기록한다. 나중에 모던 자바스크립트 딥다이브 책과 유튜브 영상을 통해 틀린 내용을 수정하고, 내용을 더 확고히 할 것이다.

08. 컨텍스트

출처: [https://haileychoi15.medium.com/실행-컨텍스트-execution-context-와-호이스팅-hoisting-3f407ad4820e](https://haileychoi15.medium.com/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-execution-context-%EC%99%80-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-hoisting-3f407ad4820e)

출처: https://haileychoi15.medium.com/실행-컨텍스트-execution-context-와-호이스팅-hoisting-3f407ad4820e

실행 컨텍스트

  • 자바스크립트 코드가 실행될 때 필요한 환경을 제공해주는 객체
  • 전역 실행 컨텍스트는 무조건 만들어짐
  • 함수가 호출될 때마다 새로운 실행 컨텍스트가 만들어짐
  • 내부적으로 레코드(Record)와 아우터(Outer)로 구성되어 있음
    • Record: 두 개의 단계를 거쳐 코드가 실행됨
      1. 생성 단계: 코드 실행에 영향을 줌
        • 코드 전체를 훑으며 변수 및 선언문을 기록함
        • 이 때, varlet, const의 경우 다름
          • var: 변수는 호이스팅되며 undefined로 초기화됨
          • letconst: 변수는 호이스팅되지만 초기화되지 않으며, 실제 선언 위치까지 "일시적 사각지대"(TDZ)에 놓임
        • 이 때, 함수 선언식으로 만든 함수와 함수 표현식, 화살표 함수의 경우 다름
          • 함수 선언식: 함수 선언 전체가 호이스팅됨
          • 함수 표현식과 화살표 함수: 변수는 호이스팅되지만 초기화되지 않음 (변수 호이스팅 규칙을 따름)
      2. 실행 단계: 코드가 실행되며 변수에 값이 할당되고 함수가 실행됨
    • Outer:
      • 스코프 체인을 통해 상위 스코프를 참조함
      • 전역 실행 컨텍스트는 상위 스코프가 없기 때문에 전역 객체(window)를 참조함

코드 예시로 흐름 따라가기

const num = 10;
function printNum() {
  const num = 30;
  console.log(num);
}
printNum()
  • 전역 실행컨텍스트 - Record 객체
    1. 생성 단계
      1. num 변수 선언 (TDZ)
      2. printNum 함수 선언 (호이스팅)
      3. 실행 단계로 넘어감
    2. 실행 단계
      1. num 변수 초기화 (10)
      2. printNum 함수는 선언만 되어있어 별다른 액션이 없음
      3. printNum()을 실행하면 새로운 실행 컨텍스트가 생성됨
  • printNum 실행컨텍스트 - Record 객체
    1. 생성 단계
      1. num 변수 선언 (TDZ)

        → 이때 전역 실행컨텍스트에서 const로 선언한 num을 여기서 또 선언할 수 있는 이유는 두 num 이 서로 다른 컨텍스트에 있기 때문임. 즉, 각 실행 컨텍스트는 독립적인 스코프를 가지며, 다른 컨텍스트에서 선언된 변수와 충돌하지 않음

    2. 실행 단계
      1. num 변수 초기화 (30)
      2. console.log(num) 실행: 30 출력
    3. 실행 완료 후 실행 컨텍스트에서 제거됨
    4. 전역 실행 컨텍스트로 돌아감
const num = 10;
function printNum() {
  console.log(num);
}
printNum();
  • 전역 실행컨텍스트 - Record 객체
    1. 생성 단계
      1. num 변수 선언 (TDZ)
      2. printNum 함수 선언 (호이스팅)
    2. 실행 단계
      1. num 변수 초기화 (10)
      2. printNum 함수는 선언된 상태로 존재
      3. printNum() 호출 시 새로운 실행 컨텍스트가 생성됨
  • printNum 실행컨텍스트 - Record 객체
    1. 생성 단계
      1. Outer 객체 설정: printNum의 Outer 객체는 전역 실행 컨텍스트를 참조함
    2. 실행 단계
      1. console.log(num) 실행: 전역 스코프에서 num을 찾아 10 출력
    3. 실행 완료 후 실행 컨텍스트에서 제거됨
    4. 전역 실행 컨텍스트로 돌아감
const num = 10;
function floor2() {
  const num = 20;
  function floor3() {
    const num = 30;
    console.log(num);
  }
  floor3();
}
floor2();
console.log(num);
  1. num 기록 초기화는 안함, floor2 함수 온전히 기록해 놔야지
  2. 실행 단계: num은 알고 보니 10이었고 floor2는 함수인 거 알았으니 업데이트 안 함
  3. floor2 함수 호출하고 실행하기 위한 실행 컨텍스트 만듦
  4. floor2 실행 컨텍스트 - Record 객체
    1. 생성 단계
      1. num 변수 선언 (TDZ)
      2. floor3 함수 선언 (호이스팅)
    2. 실행 단계
      1. num 변수 초기화 (20)
      2. floor3 함수는 선언만 되어있어 별다른 액션이 없음
      3. floor3()을 실행하면 새로운 실행 컨텍스트가 생성됨
  5. floor3 실행 컨텍스트 - Record 객체
    1. 생성 단계
      1. num 변수 선언 (TDZ)
    2. 실행 단계
      1. num 변수 초기화 (30)
      2. console.log(num) 실행: 30 출력
    3. 실행 완료 후 실행 컨텍스트에서 제거됨
    4. floor2 실행 컨텍스트로 돌아감
  6. floor2 실행 컨텍스트 완료 후 제거됨
  7. 전역 실행 컨텍스트에서 console.log(num) 실행: 10 출력

클로저

실행 컨텍스트가 정상적으로 제거(종료)되지 못하는 현상

  • 실행컨텍스트: 자기가 가지고 있는 코드의 실행이 끝나면 제거 돼야함(메모리에서 삭제 됨)
  • 실행 컨텍스트가 제대로 삭제되지 못하는 경우
    function outerFunc() {
      let count = 0;
      return function innerFunc() {
        count++;
        console.log(count);
      };
    }
    
    let counter = outerFunc();
    counter(); // 1
    counter(); // 2
    counter(); // 3
    
    counter = null;
    1. 전역 실행 컨텍스트가 만들어짐
    2. outerFunc 호출하는 순간 실행컨텍스트 만들어짐
    3. 생성단계에 count, innerFunc 기록
    4. 할당 및 함수 실행 → return;
    5. 실행컨텍스트 제거하려고 하지만 전역 실행컨텍스트에 넘겨준 함수가 count를 참조하고 있음
      1. 이를 처리하기 위해 실행컨텍스트를 콜스택에서 제거를 하긴하되 기록을 메모리상 어딘가에 보관함. 나를 참조하고 있는 애가 없어질 때까지⇒ 이런 상황을 클로져라고 함. 메모리 누수가 발생함
      2. 보관의 용도이므로 새롭게 호출해서 refresh 되지 않음
      3. 따라서 카운터를 여러번 호출했을때 숫자가 무한히 증가함
  • 메모리 누수를 방지하기 위해 클로져를 의도적으로 만들어 사용했다면 null 값으로 초기화 해줘야함

10. 생성자 함수

객체를 생성하는 함수

  • 객체의 속성이 같고, 값이 다른 경우에 객체를 생성할 수 있는 생성자 함수라는 문법을 제공함
  • 같은 속성을 가지고 있는 객체를 여러개 만들어야 할때 사용함
  • 생성자 함수로 만든 객체들의 속성을 한번에 변경할 수 있어서 유지 보수가 쉬움
  • 매개변수를 가질 수 있음

만드는 법 및 규칙

  • 함수 선언식으로 선언함 - 관례
  • 생성자 함수는 대문자로 시작해야함 - 관례
  • 생성자 함수에서 변수는 this 키워드로 생성함
  • 생성자 함수를 호출할 때는 new 키워드를 붙임
    • new 키워드를 붙이면 암묵적으로 this = {}return this 가 생략돼 있다고 생각해도 됨

function User(name, age, gender) {
	// this = {}
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.introduce = function () {
    console.log('이름: ' + this.introducename + ', 나이: ' + this.age + ', 성별: ' + this.gender);
  };
  // return this;
}

const user1 = new User('John', 30, 'male');
user1.introduce();

const user2 = new User('Ann', 12, 'female');
user2.introduce();

11. 프로토타입

객체의 상속과 재사용을 가능하게 하는 원리

무엇을 위한 속성 인가?

생성자 함수의 prototype 객체를 가리키기(참조) 위한 속성

프로토타입과 함수

  • 생성자 함수의 프로토타입 객체: 모든 함수는 그 함수와 연결돼 있는 프로토타입 속성(프로토타입 객체를 참조할 수 있음)이 있음
    • 모든 함수는 자신과 1:1로 매칭되는 프로토타입이라는 공간을 가짐
    • 서로 참조할 수 있는 속성이 존재함 → prototype, constructor

효율적으로 인스턴스 만들기

  • 공통적으로 사용할 수 있는 메서드나 속성값 같은 경우에는 prototype이라는 객체에다 넣어주자

    • 그래서 생성자 함수로 만들어지는 모든 객체들이 참조할 수 있게 하자
    • 그러면 만개의 객체를 찍어내더라도 불필요한 메모리 낭비를 줄일 수 있지 않을까?
      function User(name, age, gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
      }
      
      User.prototype.introduce = function () {
        console.log('이름: ' + this.introducename + ', 나이: ' + this.age + ', 성별: ' + this.gender);
      };
      
      const user1 = new User('John', 30, 'male');
      user1.introduce();
      
      const user2 = new User('Ann', 12, 'female');
      user2.introduce();
    • User의 prototype과 user1이 참조하고 있는 prototype은 같은 것인가?
      console.dir(user1.__proto__ === User.prototype); // true

프로토타입 체인

위 예시에서 프로토타입 객체 내부에 있는 introduceuser2.introduce(); 로 호출할 수 있는 이유는 뭘까?

  • 프로토타입 체인: 인스턴스 내부의 __proto__ 속성으로 자신을 생성한 생성자 함수의 프로토타입 객체를 참조하는 현상

  • 프로토타입 체이닝: 프로토타입 체인을 따라서 상위 프로토타입을 타고타고 올라가는 현상

  • 자바스크립트에서 최상위에 Object라는 생성자 함수가 있음

    • 자바스크립트의 모든 요소는 이 Object 로부터 파생됨

직접 비교해보기

프로토타입에 공통 메소드 입력

function User(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

User.prototype.introduce = function () {
  console.log('이름: ' + this.introducename + ', 나이: ' + this.age + ', 성별: ' + this.gender);
};

console.time();
const userList = [];
for (let i = 0; i < 9000000; i++) {
  userList.push(new User('John', 30, 'male'));
}

console.timeEnd();

객체 메소드로 공통 메소드 입력

function User(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.introduce = function () {
    console.log('이름: ' + this.introducename + ', 나이: ' + this.age + ', 성별: ' + this.gender);
  };
}

console.time();
const userList = [];
for (let i = 0; i < 9000000; i++) {
  userList.push(new User('John', 30, 'male'));
}

console.timeEnd();

따라서 성능 유지 차원에서 공통 메서드 및 속성들은 프로토 타입에 만들어 놓는다.

profile
아이디어와 구현을 좋아합니다!

0개의 댓글