모던 자바스크립트 Deep Dive 2

Modern JavaScript Deep Dive 11~20

11장 원시 값과 객체의 비교

원시 타입(값)

  • 변경 불가능한immutable 값, 즉 읽기전용

  • 변경 불가능하다는 것은 변수가 아니라 값에 대한 진술

  • 상수(const)는 재할당이 금지된 변수일 뿐, 변경 불가능한 값과 동일시 하면 안됨

  • 이러한 원시 값의 특성은 데이터의 신뢰성을 보장(불변성)

  • 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없음

문자열

  • 자바스크립트의 문자열(String 객체) 또한 원시 타입이며 변경 불가능함 (문자열이 생성된 이후에는 변경할 수 없음을 의미)

  • 문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근 가능

    • 유사 배열 객체(array-like object) : 배열처럼 인덱스로 프로펕 ㅣ값에 접근할 수 있고 length 프로퍼티를 갖는 객체

값에 의한 전달

  • 변수에 원시 값을 갖는 변수를 할당하면 할당받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달 => 값에 의한 전달

  • 하지만 할당받는 변수와 할당되는 변수의 값은 다른 메모리 공간에 저장된 별개의 값

  • 따라서 값을 전달해주는 변수의 값을 변경해도 복사한 변수의 값에는 어떠한 영향도 없음

  • 엄격하게 표현하면 변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달되기 때문에 값에 의한 전달이라는 용어는 오해가 있을 수 있음(변수와 같은 식별자는 값이 아니라 메모리 주소를 기억하기 때문)

  • 단, 전달된 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할 수 있음

  • 결국은 두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없음

객체

  • 자바스크립트 객체의 관리 방식 : 자바스크립트 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블이라고 생각할 수 있음, 또한 V8 자바스크립트 엔진에서는 프로퍼티에 접근하기 위해 동적탐색 대신 히든 클래스라는 방식을 사용해 성능을 보장

변경 가능한 값

  • 객체(참조)타입의 값, 즉 객체는 변경 가능한mutable 값

  • 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조reference 값(생성된 객체가 저장된 메모리 공간의 주소, 그 자체) 에 접근

  • 그리고 메모리에 저장되어 있는 참조 값을 통해 실제 객체에 접근

  • 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있음

  • 객체의 부작용 : 여러 개의 식별자가 하나의 객체를 공유할 수 있음(참조에 의한 전달)

얕은shallow 복사와 깊은deep 복사

  • 객체를 프로퍼티 값으로 갖는 객체의 경우

  • 얕은 복사 : 한 단계까지만 복사하는 것

  • 깊은 복사 : 객체에 중접되어 있는 객체까지 모두 복사하는 것

  • 얕은 복사와 깊은 복사로 생성된 객체는 원본과는 다른 객체, 즉 참조 값이 다른 별개의 객체

  • 참고로 원시 값을 할당한 변수를 다른 변수에 할당하는 것을 깊은 복사, 객체를 하랑한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 부르는 경우도 있음

참조에 의한 전달

  • 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달 => 참조에 의한 전달

  • 두개의 변수, 두개의 식별자가 하나의 객체를 공유

  • 결국 값에 의한 전달과 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서 동일

  • 따라서 자바스크립트에는 참조에 의한 전달은 존재하지 않고 값에 의한 전달만이 존재한다고 말할 수 있음(포인터 개념이 없기때문에)

  • 전달되는 값의 종류가 원시 값인지 참조 값인지 구별해서 강조하는 의미에서 값에 의한 전달/참조에 의한 전달로 구분

12장 함수

함수란?

  • 자바스크립트에서 가장 중요한 핵심 개념

  • 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것

  • 함수 정의를 통해 생성하고 인자와 함께 함수 호출을 통해 반환값을 반환

함수를 사용하는 이유

  • 코드의 재사용

  • 유지보수의 편의성

  • 코드의 신뢰성

  • 코드의 가독성

함수 리터럴

  • 함수는 객체 타입의 값으로 함수 리터럴로 생성 (함수 === 객체)

  • 함수 리터럴 : function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성

  • 일반 객체는 호출할 수 없지만 함수는 호출할 수 있음

함수 정의

  • 함수를 호출하기 이전에 인수에 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것

  • 정의된 함수는 엔진에 의해 평가되어 함수 객체가 됨

    함수 정의하는 방법

    • 함수 선언문 : function add(x, y) { return x+y; }

      • 이름생략 불가, 표현식X 문O

      • 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당

      • 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출

    • 함수 표현식 : var add = function(x, y) { return x+y; };

      • 자바스크립트 함수는 일급 객체

      • 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있음

      • 일반적으로 이름 생략, 표현식O 문O

        함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점은 다름

    • Function 생성자 함수 : var add = new Function('x', 'y', 'return x + y');

      • new 연산자 없이 호출해도 동일

      • 이러한 방식으로 생성된 함수는 클로저를 생성하지 않는 등 다르게 동작함

    • 화살표 함수(ES6) : var add = (x, y) => x + y;

      • 표현만 간략할 뿐만 아니라 내부 동작 또한 간략화됨(26장에서 설명)

  • 변수는 선언, 함수는 정의

  • 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 함수 호이스팅이라고함

  • 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생(표현식 이전에 참조하면 undefined를 반환)

함수 호출

  • 함수는 함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출

    매개변수와 인수

    • 매개변수는 함수를 정의할 때 선언하며 함수 내부에서 변수와 동일하게 취급

    • 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않음

    • 따라서 인수가 부족해서 인수가 할당되지 않은 매개변수 값은 undefined, 초과된 값은 무시

    인수 확인

    • 자바스크립트는 동적 타이핑 언어로 매개변수의 타입을 사전에 지정할 수 없음

    • 따라서 함수를 정의할 때 적절한 인수가 전달되었는지 확인할 필요가 있음

    매개변수의 최대 개수

    • 이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야함

    • 따라서 매개변수는 최대 3개 이상을 넘지 않는 것을 권장

    • 다수의 매개변수가 필요하다면 객체로 인수를 전달하는 것이 유리

      • 주의할 것은 함수 외부에서 내부로 전달한 객체를 함수 내부에서 변경하면 외부의 객체가 변경되는 부수 효과가 발생

    반환문

    • 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나감

    • 반환문은 return 키워드 뒤에 오는 표현식을 평가해 반환(명시적으로 지정하지 않으면 undefined가 반환)

참조에 의한 전달과 외부 상태의 변경

  • 매개변수 또한 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 그대로 따름

  • 객체타입 인수는 참조 값이 복사되어 매개변수에 전달 되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손됨

다양한 함수의 형태

  • 즉시 실행 함수(IIFE)

    • 함수 정의와 동시에 즉시 호출되는 함수로 단 한 번만 호출됨

    • 익명 함수를 사용하는 것이 일반적

    • 즉시 실행 함수는 반드시 그룹 연산자 (...)로 감싸야 함 (function () { ... }());

    // 즉시 실행 함수도 일반 함수처럼 인수를 전달할 수 있음
    res = (function (a, b) {
      return a * b;
    }(3, 5));
  • 재귀 함수

    • 자기 자신을 호출하는 행위, 즉 재귀 호출을 수행하는 함수를 말함

    • 재귀 함수는 자신을 무한 재귀 호출 함, 따라서 재귀 호출을 멈출 수 있는 탈출 조건을 만드시 만들어야함(스택 오버플로 에러 발생)

    • 특수한 상황에 한정적으로 사용하는 것이 바람직

  • 중첩 함수

    • 함수 내부에 정의된 함수를 중첩 함수 도는 내부 함수 라고 함

    • 중첩 함수를 포함하는 함수는 외부 함수

    • 일반적으로 중첩 함수는 자신을 포함하는 외부함수를 돕는 헬퍼 함수의 역할을 함

  • 콜백 함수

    • 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고함

    • 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(HOF) 라고 함

    • 즉, 고차 함수는 콜백 함수를 자신의 일부분으로 합성함

    • 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출

    • 콜백 함수는 고차 함수에 의해 호출되며 이때 고차 함수는 필요에 따라 콜백 함수에 인수를 전달

    • 콜백 함수는 함수형 프로그래밍 패러다임뿐만 아니라 비동기 처리(이벤트, ajax, 타이머 함수 등)에 활용되는 중요한 패턴

  • 순수 함수와 비순수 함수

    • 순수 함수 : 어떤 외부 상태에 의존하지도 변경하지도 않는, 부수 효과가 없는 함수

    • 비순수 함수 : 부수 효과가 있는 함수

    • 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임

13장 스코프

  • 스코프(유효범위)는 자바스크립트를 포함한 모든 프로그래밍 언어의 기본적이며 중요한 개념

  • var 키워드로 선언한 변수와 let 또는 const 키워드로 선언한 변수의 스코프는 다르게 동작

    • var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용

  • 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정 됨 => 스코프 === 식별자가 유효한 범위

  • 식별자를 검색(결정)할 때 사용하는 규칙 => 스코프

  • 스코프 내에서 식별자는 유일해야 하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있음 즉, 스코프는 네임스페이스

렉시컬lexical 환경

  • 코드가 어디서 실행되며 주변에 어떤 코드가 있는지

  • 즉, 코드의 문맥context는 렉시컬 환경으로 이루어짐 => 이를 구현한 것이 실행 컨텍스트(23장)

스코프의 종류

  • 전역global : 코드의 가장 바깥 영역, 전역 스코프, 전역 변수

    • 전역 변수는 어디서든지 참조할 수 있음

  • 지역local : 함수 몸체 내부, 지역 스코프, 지역 변수

    • 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효

스코프 체인

  • 함수는 중첩될 수 있으므로 함수의 지역 스코프도 중첩될 수 있음 => 스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미

  • 스코프 체인 === 모든 스코프는 하나의 계층적 구조로 연결 되며 모든 지역스코프의 최상위 스코프는 전역 스코프

  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색함

    • 따라서 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조할 수 있음

    • 스코프 체인으로 연결된 스코프의 계층적 구조는 부자 관계로 이뤄진 상속과 유사함(부모의 자산을 자식이 사용할 수 있지만 반대는 불가능)

함수 레벨 스코프

  • 함수 레벨 스코프 => 코드 블록이 아닌 함수에 의해서만 지역 스코프가 생성됨

    • var 키워드로 선언된 변수는오로지 함수의 코드 블록만을 지역 스코프로 인정(함수 레벨 스코프)

  • 블록 레벨 스코프 => 모든 코드 블록이 지역 스코프를 만듬

    • ES6에서 도입된 let과 const 키워드는 블록 레벨 스코프를 지원

렉시컬 스코프

  • 렉시컬 스코프(정적 스코프) <=> 동적 스코프

  • 함수를 어디서 정의했는지에 따라 상위 스코프를 결정, 호출된 위치는 스코프 결정에 어떠한 영향도 주지 않음

14장 전역 변수의 문제점

변수의 생명 주기

  • 지역 변수의 생명 주기

    • 함수 내부에서 선언된 지역 변수는 함수가 호출되면 생성되고 함수가 종료하면 소멸

    • 변수 호이스팅은 엄밀히 말해서 전역변수에 한정된 것

    • 지역 변수의 생명 주기는 함수의 생명 주기와 일치

    • 스코프는 누군가 참조하고 있다면 해제되지 않고 생존함(24장 클로저)

    • 호이스팅은 스코프 단위로 동작 => 지역 변수의 호이스팅은 지역 변수의 선언이 지역 스코프의 선두로 끌어 올려진 것처럼 동작

    • 즉, 호이스팅은 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징

  • 전역 변수의 생명 주기

    • 전역 객체 : 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 어떤 객체보다 먼저 생성되는 특수한 객체로 CS환경에서는 window, SS환경에서는 global객체를 의미

    • var 키워드로 선언한 전역 변수의 생명주기는 전역 객체의 생명주기와 일치

전역 변수의 문제점

  • 암묵적 결합 : 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합을 허용하는 것

  • 긴 생명주기 : 전역 변수는 생명 주기가 길어 메모리 리소스도 오랜 기간 소비하고 상태 변경에 의한 오류 발생확률이 높음

  • 스코프 체인 상에서 종점에 존재 : 전역 변수의 검색 속도가 가장 느림

  • 네임스페이스 오염 : 자바스크립트의 가장 큰 문제점 중 하나는 파일이 분리되어 있다 해도 하나의 전역 스코프를 공유한다는 것. 따라서 다른 파일 내에서 동일한 이름으로 명명될 경우 예상치 못한 결과를 가져 올수 있음

전역 변수 사용을 억제하는 방법

  • 전역 변수를 반드시 사용해야할 이유를 찾지 못한다면 지역 변수를 사용

  • 변수의 스코프는 좁을수록 좋음

  • 즉시 실행 함수 : 모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역 변수가 됨

  • 모듈 패턴 : 클래스를 모방해서 관련 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈로 만듬. 클로저를 기반으로 동작하며 전역 변수의 억제는 물론 캡슐화까지 구현할 수 있음

  • ES6 모듈 : ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공, var 키워드로 선언해도 더는 전역 변수도 window객체의 프로퍼티도 아님(모던 브라우저에서 사용)

    • <script type="module" src="lib.mjs"></script>

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

var 키워드로 선언한 변수의 문제점

  • 변수 중복 선언 허용

    • 초기화문이 있는 변수 선언문은 엔진에 의해 var 키워드가 없는 것처럼 동작(이전에 선언한 변수 값이 변경)

  • 함수 레벨 스코프

    • 함수 외부에서 var 키워드로 선언한 변수는 코드 블록 내에서 선언해도 모두 전역 변수

  • 변수 호이스팅

    • var 키워드로 선언한 변수는 변수 선언문 이전에 참조할 수 있음 단, 언제나 undefined를 반환

    • 에러를 발생시키지는 않지만 프로그램 흐름상 맞지 않을 뿐더러 가독성을 떨어뜨리고 오류를 발생할 여지를 남김

let 키워드

  • 변수 중복 선언 금지

    • let 키워드로 이름이 같은 변수를 중복 선언하면 SyntaxError가 발생

  • 블록 레벨 스코프

    • 모든 코드 블록을 지역 스코프로 인정하는 블록 레벨 스코프를 따름 (함수도 코드 블록이므로 스코프를 만듬)

  • 변수 호이스팅

    • let 키워드로 선언한 변수는 "선언"과 "초기화"가 분리되어 진행, 따라서 선언문 이전에 참조하면 ReferenceError가 발생

    • 일시적 사각지대(TDZ) : 스코프 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간

  • 전역 객체와 let

    • let 키워드로 선언한 전역 변수는 전역 객체(window)의 프로퍼티가 아님(보이지 않는 개념적인 블록 내에 존재)

const 키워드

  • const 키워드는 상수를 선언하기 위해 사용(대부분 let 키워드와 동일, 아래는 let과의 차이점)

  • 선언과 초기화

    • const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 함(SyntaxError)

  • 재할당 금지

    • const 키워드로 선언한 변수는 재할당이 금지됨(TypeError)

  • 상수

    • 재할당이 금지된 변수를 말함

    • 상수는 상태 유지와 가독성, 유지보수의 편의를 위해 적극적으로 사용해야 함

    • const 키워드로 선언된 변수에 원시 값을 할당한 경우 원시 값은 변경할 수 없는 값이고 const 키워드에 의해 재할당이 금지되므로 할당된 값을 변경할 수 있는 방법은 없음

    • 일반적으로 상수의 이름은 대문자로 선언해 상수임을 명확히 나타냄(스테이크 케이스)

  • const 키워드와 객체

    • const 키워드로 선언된 변수에 객체를 할당한 경우 값을 변경할 수 있음

    • 변경 가능한 값인 객체는 재할당 없이도 직접 변경이 가능

    • const 키워드는 재할당을 금지할 뿐 "불변"을 의미하지 않음

var vs. let vs. const

  • ES6를 사용한다면 var 키워드는 사용하지 않음

  • 재할당이 필요한 경우에 한정해 let 키워드를 사용(이때 변수 스코프는 최대한 좁게)

  • 변경이 발생하지 않고 읽기 전용으로 사용하는 원시 값과 객체에는 const 키워드를 사용

  • 변수를 선언할 때는 일단 const 키워드를 사용하고 재할당이 필요하다면 let으로 변경

16장 프로퍼티 어트리뷰트

내부 슬롯과 내부 메서드

  • 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드

  • 자바스크립트 엔진의 내부 로직이므로 원칙적으로 자바스크립트는 내부 슬롯과 내부 메서드에 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않음

  • 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공

    • ex. 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가짐. __proto__를 통해 간접적으로 접근 가능

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

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동정의

  • 프로퍼티의 상태 => 프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부

  • 프로퍼티 어트리뷰트 => 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯 [[Value]], [[Writeable]], [[Enumerable]], [[Configurable]]

    • 직접 접근할 수는 없지만 Object.getOwnPropertyDescriptor() 메서드를 사용하여 간접적으로 확인할 수 있음

    • => 하나의 프로퍼티에 대해 프로퍼티 디스크립터 객체를 반환

    • Object.getOwnPropertyDescriptors() ES8에서 도입된 메서드로 모든 프로퍼티에 대해 프로퍼티 디스크립터 객체를 반환

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

  • 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티

    • 프로퍼티 어트리뷰트를 가짐 [[Value]], [[Writeable]], [[Enumerable]], [[Configurable]]

  • 접근자 프로퍼티 : 자체적으로는 값을 갖지 않고 데이터 프로퍼티의 값을 읽거나 저장될 때 호출되는 접근자 함수로 구성된 프로퍼티

    • [[Get]], [[Set]], [[Enumerable]], [[Configurable]]

    • getter와 setter 함수를 모두 정의할 수도 있고 하나만 정의할 수도 있음

      get fullName() {
        return `${this.firstName} ${this.lastName}`
      }
      set fullName(name) {
        [this.firstName, this.lastName] = name.split(' ');
      }
  • 프로토타입 : 어떤 객체의 상위(부모) 객체의 역할을 하는 객체(19장)

프로퍼티 정의

  • 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것

  • Objcet.defineProperty() 메서드를 사용하면 프로퍼티의 어트리뷰트를 정의할 수 있음

  • 생략했을 때의 기본 값 : undefined false

  • Object.defineProperties() 메서드를 사용하면 여러 개의 프로퍼티를 한번에 정의할 수 있음

객체 변경 방지

  • 객체는 변경 가능한 값이므로 재할당 없이 직접 변경할 수 있음

  • 자바스크립트는 객체의 변경을 방지하는 다양한 메서드를 제공

  • 객체 확장 금지

    • Object.preventExtensions()

    • 확장이 금지된 객체는 프로퍼티 추가만 금지됨

    • Object.isExtensible() 확장 가능 객체 여부 확인

  • 객체 밀봉

    • Object.seal()

    • 밀봉된 객체는 읽기와 쓰기만 가능(추가,삭제,재정의 금지)

    • Object.isSealed() 밀봉 여부 확인

  • 객체 동결

    • Object.freeze()

    • 동결된 객체는 읽기만 가능

    • Object.isFrozen() 동결 여부 확인

  • 위와 같은 변경 방지 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지 못함

  • 불변 객체

    • 객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용 객체

    • 구현하기 위해서 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze()를 호출해야함

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

Object 생성자 함수

  • new Object() : 빈객체를 생성하여 반환

  • 생성자 함수(constructor)란 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수

  • Object 이외에도 String Number Boolean Function Array Date RegExp Promise 등의 빌트인 생성자 함수를 제공

생성자 함수

  • 객체 리터럴에 의한 객체 생성 방식의 문제점

    • 직관적이고 간편하지만 단 하나의 객체만 생성하므로 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야하는 경우 매번 같은 프로퍼티를 기술해야 하기 때문에 비효율적

  • 생성자 함수에 의한 객체 생성 방식의 장점

    • 객체를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있음

      • this : 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수(self-referencing variable), this 바인딩은 함수 호출 방식에 따라 동적으로 결정(22장)

    • 형식이 정해져 있는 것이 아니라 일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작

  • 생성자 함수의 인스턴스 생성 과정

    • 생성자 함수의 함수 몸체에서 수행해야 하는 것 : 인스턴스 생성, 생성된 인스턴스 초기화(프로퍼티 추가 및 초기화 할당)

      1. 인스턴스 생성과 this 바인딩 : 빈 객체를 생성하고 this에 바인딩 시킴

      1. 인스턴스 초기화 : 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화

      1. 인스턴스 반환 : 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환

  • 내부 메서드 [[Call]]과 [[Construct]]

    • 함수는 객체이므로 일반 객체와 동일하게 new 연산자와 함께 호출하여 객체를 생성할 수 있음(생성자 함수로서 호출)

    • 함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드를 모두 가지고 있음

    • 일반 객체는 호출할 수 없지만 함수 객체는 호출할 수 있음(함수로 동작하기 위한 내부 슬롯 + 내부 메서드)

    • 일반 함수로서 호출되면 [[Call]]이 호출되고 생성자 함수로서 호출되면 [[Construct]]가 호출

    • callable : 호출할 수 있는 객체 === 모든 함수 객체

    • constructor : 생성자 함수로서 호출할 수 있는 함수

    • non-constructor : 생성자 함수로서 호출할 수 없는 함수

  • constructor와 non-construcotr의 구분

    • 자바스크립트 엔진은 함수 정의를 평가하여 함수 객체를 생성할 때 함수 정의 방식에 따라 구분함

    • constructor : 함수 선언문, 함수표현식, 클래스(클래스도 함수)

    • non-constructor : 메서드(ES6 메서드 축약 표현만), 화살표 함수

  • new 연산자

    • new 연산자와 호출하는 함수는 constructor 이어야 함([[Construct]]호출)

    • new 연산자와 함께 생성자 함수로서 호출하면 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킴(그렇지 않으면 전역 객체 window를 가리킴)

    • 일반 함수와 생성자 함수에 특별한 형식적 차이가 없으므로 생성자 함수는 파스칼 케이스로 명명하여 구별할 수 있도록 노력

  • new.target

    • 생성자 함수가 new 연산자 없이 호출되는 위험성을 회피하기 위해 ES6에서는 new.target을 지원

    • new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리키고 new 연산자 없이 힐반 함수로서 호출된 함수 내부의 new.target은 undefined

    • 함수 내부에서 new.target을 사용하여 생성자 함수로서 호출했는지 확인하여 그렇지 않은 경우 new 연산자와 함께 재귀 호출을 통해 생성자 함수로서 호출할 수 있음

18장 함수와 일급 객체

일급 객체

  • 무명의 리터럴로 생성할 수 있다. 즉 런타임에 생성이 가능하다.

  • 변수나 자료구조(객체, 배열 등)에 저장할 수 있다.

  • 함수의 매개변수에 전달할 수 있다.

  • 함수의 반환값으로 사용할 수 있다.

  • 함수는 객체이지만 일반 객체와는 다르게 호출할 수 있고 함수 고유의 프로퍼티를 소유함

함수 객체의 프로퍼티

  • 함수는 객체이기 때문에 프로퍼티를 가질 수 있음 console.dir()

    • arguments 프로퍼티

      • 함수 객체의 auguments 프로퍼티 값은 arguments 객체

      • arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있는 순회 가능한 유사 배열 객체이며 함수 내부에서 지역변수 처럼 사용

      • 함수가 호출되면 함수 몸체 내에서 암묵적으로 매개변수가 선언되고 undefined로 초기화된 이후 인수가 할당

      • 자바 스크립트는 함수의 매개변수와 인수의 개수가 일치하는지 확인하지 않음

      • 인수가 전달되지 않은 매개변수는 undefined, 초과된 인수는 무시되고 arguments 객체 프로퍼티로 보관

      • arguments 객체는 매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 유용

    • caller 프로퍼티

      • ECMAScript 사양에 포함되지 않은 비표준 프로퍼티

      • 함수 자신을 호출한 함수를 가리킴

    • length 프로퍼티

      • 함수를 정의할 때 선언한 매개변수의 개수를 가리킴

      • arguments는 인자 / length는 매개변수

    • name 프로퍼티

      • 함수 이름을 나타내는 프로퍼티로 ES6에서 정식 표준이 됨

      • 익명 함수 표현식의 경우 ES5에서는 빈 문자열을, ES6에서는 함수 객체를 가리키는 식별자를 값으로 가짐

    • __proto__ 접근자 프로퍼티

      • 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티임

      • 간접적으로 프로토타입 객체에 접근하기 위한 접근자

    • prototype 프로퍼티

      • 생성자 함수로 호출할 수 있는 함수 객체, 즉 constructor만이 소유하는 프로퍼티로 non-constructor에는 prototype 프로퍼티가 없음

      • 함수가 객체를 생성하는 생성자 함수로 호출될 때 생성자 함수가 생성할 인스턴스의 프로토타입 객체를 가리킴

19장 프로토타입

  • 자바스크립트는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어(imperative, functional, prototype-based OOP)

  • ES6에서 클래스가 도입되었으나 기존의 프로토타입 기반 객체지향 모델을 대체하는 것은 아님

  • 자바스크립트는 객체 기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 모든 것이 객체

객체지향 프로그래밍

  • 프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말함

  • 추상화 : 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것

  • 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조, 즉 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조

상속과 프로토타입

  • 상속 : 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것

  • 자바스크립트는 프로토타입을 기반으로 상속을 구현함

function Circle(radius) {
  this.radius = radius;
}

Circle.protorype.getArea = function() {
  return Math.PI * this.radius ** 2;
}
// Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유

프로토타입 객체

  • 객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[prototype]]에 저장됨

  • 모든 객체는 하나의 프로토타입을 가지며 모든 프로토타입은 생성자 함수와 연결되어 있음

  • __proto__ 접근자 프로퍼티 : 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있음

    • __proto__ 는 접근자 프로퍼티다

    • __proto__ 접근자 프로퍼티는 상속을 통해 사용된다 : 객체가 직접 소유 하는 프로퍼티가 아님

    • __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유 : 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서(순환 참조), 아무런 체크 없이 무조건적으로 프로토타입을 교체할 수 없도록 함

    • __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다 : 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문, 접근자 프로퍼티 대신 프로토타입의 참조를 취득하고 싶은 경우 Object.getPrototypeOf 메서드를 사용하고 프로토타입을 교체하고 싶은 경우 Object.setPrototypeOf 메서드를 사용할 것을 권장

  • 함수 객체의 prototype 프로퍼티

    • 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴

    • non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않음

    • 모든 객체가 가지고 있는(Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토 타입을 가리킴, 다만 사용하는 주체가 다름(모든 객체 or 생성자 함수)

  • 프로토타입의 constructor 프로퍼티와 생성자 함수

    • 모든 프로토타입은 constructor 프로퍼티를 가지며 이 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킴

    • 이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이루어짐

리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

  • 리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재하지만 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없음

  • ECMAScript 사양에 따르면 Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로는 추상 연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성

  • 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님

  • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재

  • 큰 틀에서 생각해보면 리터럴 표기법으로 생성한 객체도 생성자 함수로 생성한 객체와 본질적인 면에서 큰 차이는 없음

프로토타입의 생성 시점

  • 모든 객체는 생성자 함수와 연결되어 있고 프로토타입은 생성자 함수가 생성되는 시점에서 더불어 생성

사용자 정의 생성자 함수와 프로토타입 생성 시점

  • 생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성

  • non-constructor는 프로토타입이 생성되지 않음

  • 사용자 정의 생성자 함수는 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 더불어 생성되며, 생성된 프로토타입의 프로토타입은 언제나 Object.prototype

빌트인 생성자 함수와 프로토타입 생성 시점

  • 빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성

  • 생성자 함수와 프로토타입은 이미 객체화 되어 존재, 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당

객체 생성 방식과 프로토타입의 결정

  • 다양한 방식(객체 리터럴, Object 생성자 함수, 생성자 함수)으로 생성된 모든 객체는 각 방식마다 세부적인 객체 생성 방식의 차이는 있으나 추상 연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 있음

  • 즉, 프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정

  • 사용자 정의 생성자 함수에 의해 생성된 객체의 프로토타입

    • 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체

    • 사용자 정의 생성자 함수와 더불어 생성된 프로토타입 생성자함수.prototype의 프로퍼티는 constructor뿐임

    • 프로토타입은 객체이기 때문에 일반 객체와 같이 프로퍼티를 추가/삭제할 수 있음 그리고 이렇게 추가/삭제된 프로퍼티는 프로토타입 체인에 즉각 반영

프로토타입 체인

  • 프로토타입의 프로토타입은 언제나 Object.prototype

  • 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하고 이를 프로토타입 체인이라 함

  • 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘

  • Object.prototype : 프로토타입 체인의 종점(end of prototype chain)

  • 프로토타입 체인은 상속과 프로퍼티 검색을 위한 매커니즘

  • 자바스크립트 엔진은 함수의 중첩 관계로 이루어진 스코프의 계층적 구조에서 식별자를 검색, 따라서 스코프 체인은 식별자 검색을 위한 매커니즘

  • 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용

오버라이딩과 프로퍼티 섀도잉

  • 프로퍼티 섀도잉 : 상속 관계에 의해 프로퍼티가 가려지는 현상

  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가함. 이때 인스턴스 메서드는 프로토타입 메서드를 오버라이딩 했음

프로토타입의 교체

  • 프로토타입은 임의의 다른 객체로 변경할 수 있음 === 부모 객체인 프로토타입을 동적으로 변경할 수 있다는 것을 의미

  • 프로토타입은 생성자 함수 또는 인스턴스 에 의해 교체할 수 있음(294p)

  • 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 번거로우므로 직접 교체하지 않는 것이 좋음

instanceof 연산자

  • 객체 instanceof 생성자 함수

  • 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가되고 그렇지 않은 경우 false로 평가

  • 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인

직접 상속

Object.create에 의한 직접 상속

  • Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성(OrdinaryObjectCreate 호출)

  • new 연산자가 없이도 객체를 생성할 수 있음

  • 프로토타입을 지정하면서 객체를 생성할 수 있음

  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음

객체 리터럴 내부에서 __proto__ 에 의한 직접 상속

  • ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현

정적 프로퍼티/메서드

  • 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말함

  • 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니므로 인스턴스로 접근할 수 없음

  • 프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음

프로퍼티 존재 확인

  1. in 연산자 key in object

    • in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받는 모든 프로토타입의 프로퍼티를 확인하므로 주의가 필요

  2. in 연산자 대신 ES6 도입 Reflect.has 사용

  3. Object.prototype.hasOwnProperty 메서드

    • 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환

프로퍼티 열거

  1. for ... in

    • 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거함

    • 객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거하므로 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인하는 추가 처리가 필요

    • 배열에서는 일반적인 for 문이나 for ... of 문 또는 Array.prototype.forEach 메서드를 사용하기를 권장

  2. Object.keys/values/entries 메서드

    • 객체 자신의 고유 프로퍼티만 열거하기 위해 권장되는 메서드들

    • keys : 객체 자신의 열거가능한 프로퍼티 키를 배열로 반환

    • values : 객체 자신의 열거가능한 프로퍼티 값을 배열로 반환

    • entries : 객체 자신의 열거가능한 프로퍼티 키와 값의 쌍의 배열을 베열에 담아 반환

Last updated