자바스크립트 Deep Dive - week 4

하머·2022년 11월 29일
0

알게 된 점

  • let, const 등은 호이스팅이 이루어지지 않는 것이 아닌 오류를 통해 이루어지지 않는 것 처럼 보이는 것이다. 실제로는 호이스팅이 이루어지지만, 변수가 선언되기 전에 사용되었기 때문에 오류가 발생한다.
  • 타입스크립트에서 class 등을 만들 때 get set을 사용한다면 접근자 프로퍼티를 사용하는 것이였다.
  • 타입스크립트의 const assertion과 Object.freeze의 차이는 뭘까? const assertion은 타입스크립트에서만 사용되는 것(컴파일 단계)이고, Object.freeze는 런타임에서도 사용되는 것이다.
    • Object.freeze의 경우 얇은 동결(shallow freeze)이다. 객체의 프로퍼티가 객체인 경우에는 동결이 되지 않는다. 그에 반해 const assertion은 깊은 동결(deep freeze)이다.
  • new 연산자를 사용하는 것이 생성자 함수를 뜻하는 것이고, 사용하지 않는 다면 일반 함수로 사용할 수 있는 것을 깨달았다. ex) String(), Number()
    • this를 사용하지 않는다면 일반 함수로 사용하는 것이 좋다.
    • this의 동작이 다르다. 생성자 함수에서는 this가 생성되는 객체를 가리키고, 일반 함수에서는 this가 window를 가리킨다.
  • 생성자 함수 내부에서 new.target을 사용하면 생성자 함수가 new 연산자와 함께 호출되었는지 확인할 수 있다.
  • 함수 의 객체 프로퍼티 중 argument 는 함수를 호출할 때 전달된 인수들을 배열 형태로 저장하는데, 인자로 넘어온 값이 매개 변수의 갯수보다 크더라도 인자로 넘어온 값만큼 배열에 저장된다.
function sum() {
  console.log(arguments);
  return arguments.reduce((acc, cur) => acc + cur, 0);
}

sum(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

퀴즈

  1. 다음 코드의 실행 결과는?
function foo() {
  console.log(this);
}

foo();
  1. 다음 코드의 실행 결과는?
function foo1() {
  console.log(this);
}

const myFunc = new foo1();
myFunc();

15장 - let, const 키워드와 블록 레벨 스코프

var 키워드 변수의 문제점

  • 변수 중복 선언 허용
  • 함수 레벨 스코프
    • 함수 외부에서 var 키워드로 선언한 변수는 코드 블록 내에서 선언해도 모두 전역 변수가 되어버린다.
    • ex) if, for 조건문, 반복문
  • 변수 호이스팅
    • 할당문 이전에 변수 참조하면 초기화 값인 undefined를 반환한다.

let 키워드

  • 변수 중복 선언 금지 => 에러
  • 블록 레벨 스코프
    • 오직 함수의 코드 블록만을 지역 스코프로 인정하는 var와는 다르게 함수, 조건문, 반복문, try/catch문 을 모두 지역 스코프로 인정하는 블록 레벨 스코프를 사용한다.
  • 변수 호이스팅이 발생 시 에러를 일으킨다.
    • 선언 단계와 초기화 단계가 분리되어 진행되어 var와는 다르게 초기화 단계가 변수 선언문에 도달했을 때 실행되고 초기화 이전 변수에 접근 시 참조 에러가 발생해 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간인 일시적 사각지대(TDZ)를 생성한다.
    • 다만 이것이 호이스팅 자체를 일으키지 않는 것은 아니다. 여전히 호이스팅은 일어나지만 참조 에러가 나오는 것이다.
  • var를 전역으로 선언하면 window.foo 와 같이 window 객체의 프로퍼티가 되지만 var와 다르게 실행 컨텍스트의 렉시컬 환경 중 선언적 환경 레코드에 저장되므로 window 객체의 프로퍼티가 되지 않고 접근할 수 없다.

const 키워드

  • const 키워드는 반드시 선언과 동시에 초기화가 진행되어야 한다.
const foo = 1;
const constantVariable; // SyntaxError;
  • let 키워드와 마찬가지로 블록 레벨 스코프를 가지고, 호이스팅이 발생되면 에러를 일으킨다.
  • 재할당이 금지된다.
  • 재할당이 금지되지만 참조값과 같은 변경 가능한 값들은 변경이 가능하다. => 불변을 의미하지는 않는다.

16장 - 프로퍼티 어트리뷰트

프로퍼티 어트리뷰트 와 프로퍼티 디스크립터 객체

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
    • 프로퍼티의 상태: value, writable, enumerable, configurable (데이터 프로퍼티)
  • 프로퍼티 어트리뷰트는 프로퍼티 디스크립터 객체로 추상화된다.
  • 프로퍼티 디스크립터 객체는 프로퍼티의 상태 정보를 담고 있는 객체로서 프로퍼티의 상태 정보를 나타내는 프로퍼티 어트리뷰트를 프로퍼티 디스크립터 객체의 프로퍼티로 구성한다.
  • 프로퍼티 디스크립터 객체는 Object.getOwnPropertyDescriptor 메서드를 통해 얻을 수 있다.

데이터 프로퍼티와 접근자 프로퍼티

  • 프로퍼티는 데이터 프로퍼티접근자 프로퍼티로 구분된다.
  • 데이터 프로퍼티는 키와 값으로 구성된 일반적인 프로퍼티이다.
  • 접근자 프로퍼티는 값을 읽을 때 사용하는 getter 함수와 저장할 때 사용되는 setter 함수로 구성된 프로퍼티이다.
  • 접근자 프로퍼티로 프로퍼티 값에 접근하게 되면 getter 함수가 호출되고, 프로퍼티 값을 할당하게 되면 setter 함수가 호출된다.
const person = {
  firstName: "Ungmo",
  lastName: "Lee",
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }, // getter 함수
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
  }, // setter 함수
};
// firstName, lastName = 데이터 프로퍼티
// fullName = 접근자 프로퍼티

프로퍼티 정의

  • 프로퍼티 정의: 객체에 프로퍼티를 추가하거나 기존 프로퍼티의 상태를 변경하는 것
  • 프로퍼티 정의는 Object.defineProperty 메서드를 사용한다.
  • Object.defineProperty 메서드는 첫 번째 인수로 전달받은 객체에 두 번째 인수로 전달받은 프로퍼티 키와 세 번째 인수로 전달받은 프로퍼티 디스크립터 객체의 프로퍼티를 사용하여 프로퍼티를 정의한다.
  • Object.defineProperty 메서드는 프로퍼티 정의에 성공하면 첫 번째 인수로 전달받은 객체를 반환하고, 실패하면 TypeError를 발생시킨다.
const person = { name: "Lee" };

// 프로퍼티 정의
Object.defineProperty(person, "age", {
  value: 20,
  writable: true,
  enumerable: true,
  configurable: true,
});

console.log(person); // {name: "Lee", age: 20}

객체 변경 방지

  • 객체 변경 방지: 객체의 확장을 금지하거나 객체의 프로퍼티를 재정의할 수 없도록 하는 것
  • 객체의 확장을 금지하면 객체에 새로운 프로퍼티를 추가할 수 없다.
  • 객체의 프로퍼티를 재정의할 수 없도록 하면 이미 존재하는 프로퍼티의 상태를 변경할 수 없다.
  • 객체의 확장을 금지하려면 Object.preventExtensions 메서드를 사용한다.
  • Object.preventExtensions 메서드는 첫 번째 인수로 전달받은 객체의 확장을 금지하고, 첫 번째 인수로 전달받은 객체를 반환한다.
const person = { name: "Lee" };

// 객체의 확장을 금지한다.
Object.preventExtensions(person);

// 프로퍼티 추가
person.age = 20; // 무시된다.

console.log(person); // {name: "Lee"}
  • 객체 확장 금지 외에 밀봉, 동결 등의 메소드도 있다.
  • 밀봉: 객체의 확장을 금지하고, 이미 존재하는 프로퍼티의 [[Configurable]]을 false로 설정하여 프로퍼티 재정의를 금지한다.
  • 동결: 객체의 확장을 금지하고, 이미 존재하는 프로퍼티의 [[Configurable]]과 [[Writable]]을 false로 설정하여 프로퍼티 재정의와 프로퍼티 값 갱신을 금지한다. => readonly
  • 내 생각: TypeScript에서 상수 선언으로 자주 쓰이는 as const가 객체를 동결하는 것일까?

17장 - 생성자 함수에 의한 객체 생성

생성자 함수

  • 생성자 함수: 객체를 생성하기 위해 new 연산자와 함께 호출하는 함수
  • 생성자 함수는 일반 함수와 다르게 다음과 같은 규칙을 따른다.
    • 함수 이름의 첫 글자는 대문자로 한다.
    • 함수 몸체 내부에서 this에 바인딩할 인스턴스를 생성하고 this를 반환한다.
  • 종류
    • 내장 생성자 함수: Object, Function, Array, String, Number, Boolean, Date, RegExp, Promise 등
    • 사용자 정의 생성자 함수: 개발자가 직접 정의한 생성자 함수 (typescript class constructor)
  • 자바와 같은 클래스 기반 객체지향 언어에서는 클래스를 통해 객체를 생성한다. 하지만 자바스크립트는 그 형식이 정해져 있는 것이 아니라 일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다.

객체 리터럴 방식의 한계

  • 반복 되는 프로퍼티 구조를 가진 객체를 생성할 때는 객체 리터럴 방식보다 생성자 함수를 사용하는 것이 효율적이다.
// 객체 리터럴 방식
const circle1 = {
  radius: 5,
  getDiameter() {
    return 2 * this.radius;
  },
};

const circle2 = {
  radius: 10,
  getDiameter() {
    return 2 * this.radius;
  },
};

// 생성자 함수
function Circle(radius) {
  // 인스턴스 초기화
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

생성자 함수의 인스턴스 생성 과정과 특징

  • 생성자 함수를 new 연산자와 함께 호출하면 다음과 같은 과정을 거친다.
    1. 인스턴스(빈 객체)를 생성하고 this에 바인딩한다.
    2. this를 통해 생성자 함수 내부의 코드를 실행하고 인스턴스를 초기화한다.(프로퍼티나 메서드를 추가하고 인수로 받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화 하거나 고정값을 할당한다.)
    3. (암묵적으로) this를 통해 생성된 프로퍼티와 메서드를 가진 객체를 반환한다.
      이 때 return 반환문이 있다면 return문에 명시된 객체를 반환한다. 명시된 객체가 없다면 this를 반환한다.
      return 반환문에 원시값을 명시하면 무시된다.
  • 함수는 객체이지만 일반 객체와는 다르게 호출할 수 있다.
    • 함수로써 동작(호출)하기 위해 Environment, FormalParameters 등의 내부 슬롯과 Call, Construct 등의 내부 메서드를 갖는다.
  • 호출 할 경우 Call 내부 메서드가 호출되고, new 연산자와 함께 호출 할 경우 Construct 내부 메서드가 호출된다.
    • ex) String, Number, Boolean은 new 와 사용할 시 생성자 함수로 동작하고, new 없이 사용할 시 데이터 타입을 변환하는 함수로 동작한다.
  • 다만 모든 함수가 생성자 함수로서 호출될 수 있는 것은 아니다. 일반 함수는 생성자 함수로서 호출될 수 없다.
    • 일반 함수는 Construct 내부 메서드를 갖지 않는다.
    • 일반 함수는 new 연산자와 함께 호출할 수 없다.
  • 생성자 함수로 사용되었는지 알수있는 방법 중 하나인 new.target은 ES6에서 도입되었다.
    • new.target은 new 연산자와 함께 호출되면 생성자 함수 자신을 가리키고, 일반 함수로 호출되면 undefined를 가리킨다.
    • new.target은 함수 내부에서만 참조할 수 있다.
    • new.target은 함수 내부에서 참조할 수 있으므로 생성자 함수 내부에서 new.target을 사용하여 생성자 함수로 호출되었는지 확인할 수 있다.

18장 - 함수와 일급 객체

  • 함수는 일급 객체이다.

    • 일급 객체는 다음과 같은 조건을 만족하는 객체를 말한다.
      • 무명의 리터럴로 생성할 수 있다. 즉, 런타임에 생성이 가능하다.
      • 변수나 자료구조(객체, 배열 등)에 저장할 수 있다.
      • 함수의 매개변수에게 전달할 수 있다. => 콜백 함수
      • 함수의 반환값으로 사용할 수 있다. => 클로저 생성 가능
      • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다. 즉, 런타임에 생성된 객체에 바인딩 된 이름이 없어도 메모리 주소로 구별이 가능하다.
  • 함수의 프로퍼티들

  • 함수 객체 고유의 프로퍼티들

    • arguments : 함수를 호출할 때 전달된 인수들의 정보를 담고 있는 유사 배열 객체
    • caller : 현재 실행 중인 함수를 호출한 함수의 참조를 반환한다. (ES5에서 폐기)
    • length : 함수에 정의된 매개변수의 개수를 반환한다.
    • name : 함수의 이름을 반환한다. 익명 함수의 경우 빈 문자열을 반환한다.
    • prototype : 함수가 생성자 함수로 사용될 때, 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
    • __proto__\ : 함수의 프로토타입을 가리킨다.

23장 - 실행 컨텍스트

실행 컨텍스트란?

  • 실행 컨텍스트는 자바스크립트 코드가 실행되는 환경이다.
  • 실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이다.
  • 실행 가능한 코드 : 전역 코드, 함수 코드, eval 코드, 모듈 코드
  • 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다.
  • 실행 컨텍스트는 Variable Object (VO / 변수객체), Scope Chain (SC), this value 등으로 구성된다.
  • 식별자와 스코프는 실행 컨텍스트의 렉시컬 환경에 의해 관리되고 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다.

실행 컨텍스트의 생성 과정

  • 소스코드의 평가 과정에서는 실행 컨텍스트를 생성 하고 소스코드의 실행 과정에서는 생성된 실행 컨텍스트를 참조한다. 이 때 평가 과정에서는 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다.

  • 전역 코드의 실행 컨텍스트 생성

    • 전역 코드가 실행되기 위한 환경을 구성하고 코드를 실행한다.
    • 전역 코드의 실행 컨텍스트는 애플리케이션이 종료될 때까지 유지된다.
  • 함수 코드의 실행 컨텍스트 생성

    • 함수가 호출되면 함수 코드가 실행되기 위한 환경을 구성하고 코드를 실행한다.
    • 함수 코드의 실행 컨텍스트는 함수의 호출이 종료되면 소멸된다.

렉시컬 환경

  • 실행 컨텍스트의 렉시컬 환경은 식별자와 식별자에 바인딩 된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조이다.
  • 렉시컬 환경은 실행 컨텍스트가 생성될 때 같이 생성된다.
  • 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조를 기록하는 구조이다.
  • 환경 레코드는 식별자와 식별자에 바인딩 된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조이다.
  • 외부 렉시컬 환경에 대한 참조는 상위 스코프에 대한 참조이다.

실행 컨텍스트 스택

  • 실행 컨텍스트 스택은 실행 컨텍스트가 생성되면 스택에 푸시되고 실행이 종료되면 스택에서 팝된다.
  • 실행 컨텍스트 스택은 LIFO(Last In First Out)(후입선출) 구조를 갖는다.
  • 실행 컨텍스트 스택은 함수의 호출과 관계되어 있다.
  • 함수가 호출되면 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 푸시된다.
  • 함수의 실행이 종료되면 실행 컨텍스트 스택에서 팝된다.

실행 컨텍스트와 블록 레벨 스코프

  • 블록 레벨 스코프는 함수가 아닌 조건문, 반복문 등의 모든 코드 블록을 지역 스코프로 인정하는 것이다.
  • let, const의 경우 렉시컬 환경에서 함수 레벨 스코프를 따르지 않으므로 이를 위해 선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 전역 렉시컬 환경을 교체한다.

0개의 댓글