모던 자바스크립트 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 () { ... }());
재귀 함수
자기 자신을 호출하는 행위, 즉 재귀 호출을 수행하는 함수를 말함
재귀 함수는 자신을 무한 재귀 호출 함, 따라서 재귀 호출을 멈출 수 있는 탈출 조건을 만드시 만들어야함(스택 오버플로 에러 발생)
특수한 상황에 한정적으로 사용하는 것이 바람직
중첩 함수
함수 내부에 정의된 함수를 중첩 함수 도는 내부 함수 라고 함
중첩 함수를 포함하는 함수는 외부 함수
일반적으로 중첩 함수는 자신을 포함하는 외부함수를 돕는 헬퍼 함수의 역할을 함
콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고함
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(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 함수를 모두 정의할 수도 있고 하나만 정의할 수도 있음
프로토타입 : 어떤 객체의 상위(부모) 객체의 역할을 하는 객체(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 연산자
와 함께 호출하면 해당 함수는 생성자 함수로 동작
생성자 함수의 인스턴스 생성 과정
생성자 함수의 함수 몸체에서 수행해야 하는 것 : 인스턴스 생성, 생성된 인스턴스 초기화(프로퍼티 추가 및 초기화 할당)
인스턴스 생성과 this 바인딩 : 빈 객체를 생성하고 this에 바인딩 시킴
인스턴스 초기화 : 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화
인스턴스 반환 : 완성된 인스턴스가 바인딩된 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에서 클래스가 도입되었으나 기존의 프로토타입 기반 객체지향 모델을 대체하는 것은 아님
자바스크립트는 객체 기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 모든 것이 객체
객체지향 프로그래밍
프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말함
추상화 : 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것
객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조, 즉 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조
상속과 프로토타입
상속 : 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
자바스크립트는 프로토타입을 기반으로 상속을 구현함
프로토타입 객체
객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[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을 #으로 표기하는 경우도 있음
프로퍼티 존재 확인
in 연산자
key in object
in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받는 모든 프로토타입의 프로퍼티를 확인하므로 주의가 필요
in 연산자 대신 ES6 도입
Reflect.has
사용Object.prototype.hasOwnProperty
메서드인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환
프로퍼티 열거
for ... in
문객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거함
객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거하므로
Object.prototype.hasOwnProperty
메서드를 사용하여 객체 자신의 프로퍼티인지 확인하는 추가 처리가 필요배열에서는 일반적인
for
문이나for ... of
문 또는Array.prototype.forEach
메서드를 사용하기를 권장
Object.keys/values/entries
메서드객체 자신의 고유 프로퍼티만 열거하기 위해 권장되는 메서드들
keys
: 객체 자신의 열거가능한 프로퍼티 키를 배열로 반환values
: 객체 자신의 열거가능한 프로퍼티 값을 배열로 반환entries
: 객체 자신의 열거가능한 프로퍼티 키와 값의 쌍의 배열을 베열에 담아 반환
Last updated