알게 된 점
- 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);
퀴즈
- 다음 코드의 실행 결과는?
function foo() {
console.log(this);
}
foo();
- 다음 코드의 실행 결과는?
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;
- 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}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
},
};
프로퍼티 정의
- 프로퍼티 정의: 객체에 프로퍼티를 추가하거나 기존 프로퍼티의 상태를 변경하는 것
- 프로퍼티 정의는 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);
객체 변경 방지
- 객체 변경 방지: 객체의 확장을 금지하거나 객체의 프로퍼티를 재정의할 수 없도록 하는 것
- 객체의 확장을 금지하면 객체에 새로운 프로퍼티를 추가할 수 없다.
- 객체의 프로퍼티를 재정의할 수 없도록 하면 이미 존재하는 프로퍼티의 상태를 변경할 수 없다.
- 객체의 확장을 금지하려면 Object.preventExtensions 메서드를 사용한다.
- Object.preventExtensions 메서드는 첫 번째 인수로 전달받은 객체의 확장을 금지하고, 첫 번째 인수로 전달받은 객체를 반환한다.
const person = { name: "Lee" };
Object.preventExtensions(person);
person.age = 20;
console.log(person);
- 객체 확장 금지 외에 밀봉, 동결 등의 메소드도 있다.
- 밀봉: 객체의 확장을 금지하고, 이미 존재하는 프로퍼티의 [[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 연산자와 함께 호출하면 다음과 같은 과정을 거친다.
- 인스턴스(빈 객체)를 생성하고 this에 바인딩한다.
- this를 통해 생성자 함수 내부의 코드를 실행하고 인스턴스를 초기화한다.(프로퍼티나 메서드를 추가하고 인수로 받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화 하거나 고정값을 할당한다.)
- (암묵적으로) 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의 경우 렉시컬 환경에서 함수 레벨 스코프를 따르지 않으므로 이를 위해 선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 전역 렉시컬 환경을 교체한다.