[모던 자바스크립트 Deep Dive] 15장~17장
모던 자바스크립트를 2회독하는 시점에서 인상깊은 점을 정리합니다.
15장 let, const, 키워드와 블록 레벨 스코프
기존 var 키워드가 가지는 문제점 3가지 (1. 변수 중복 선언 허용 2. 함수 레벨 스코프 3. 변수 호이스팅) 를 설명하는 부분 중 1. 변수 중복 선언 허용에서 나중에 작성한 중복 선언문의 초기화 문 포함 유무에 따라 다르게 동작한다는 부분이 인상 깊었습니다.
var x = 1;
var x = 100; // 초기화문 0
console.log(x); //100
var y = 1;
var y; //초기화문 x
console.log(y); //1
초기화문이 포함되어 있다면 var 키워드가 생략된 것처럼 재할당이 일어나며, 초기화문이 포함되어 있지 않다면 해당 문은 무시됩니다.
let 키워드의 경우 선언단계- 일시적 사각지대 TDZ- 초기화 단계- 할당 단계로 동작이 이루어지는 점을 다시 짚게되는 시간이였습니다. 이전까지 let 키워드가 일시적 사각지대에 놓여 변수 호이스팅이 일어나지 않아 보이는 것은 알고 있었지만 이후 var 키워드와 동일하게 초기화 단계를 거친다는 것을 잊고 있었는 데 다시 한번 상기했습니다.
1. var 키워드로 선언한 전역 변수 2. 전역함수 3. 선언하지 않는 변수에 값을 할당한 암묵적 전역 이 세가지는 전역 window 의 프로퍼티가 되는 반면 let 키워드로 선언한 전역 변수의 경우 전역 객체의 프로퍼티가 되지 않는 다는 점이 인상깊었습니다. var 키워드로 선언한 전역변수와 달리 let, const 키워드로 선언한 전역 변수의 경우 전역 렉시컬 환경의 선언적 환경 레코드내에 존재하기 때문입니다.
16장 프로퍼티 어트리뷰트
16장은 주로 용어들의 개념을 재차 익히는 시간을 가졌습니다.
내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드 입니다. 자바스크립트는 내부 슬롯과 내부 메서드의 직접 접근은 막았지만 일부 내부에 한에 간접적으로 접근할 수 있도록 허용했습니다.
간접 접근을 허용한 내부 슬롯 중 프로퍼티 어트리뷰트가 있습니다. 프로퍼티 어트리뷰트는 프로퍼티의 상태를 나타내는 내부 슬롯 입니다. (그전에 프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 나눌 수 있는데 그중 데이터 프로퍼티의 프로퍼티 어트리뷰트에 대해서 설명합니다 ) 데이터 프로퍼티에는 총 4가지 프로퍼티 어트리뷰트를 가지고 있습니다.
- [[value]]: 프로퍼티의 값
- [[writable]]: 값의 갱신 가능 여부
- [[enumberable]]: 열거 가능 여부 -> false 인 경우 for ...in 문 , Object.keys 메서드 등으로 열거 x
- [[configurable]]: 재정의 가능 여부 -> false 인경우 프러포티 삭제 불가, 프로퍼티 어트리뷰트 값 변경 금지
여기서 다시 익힌 점은 configurable : false 인경우 프로퍼티 어트리뷰트 재정의를 막는 것 뿐 아니라 프로퍼티 삭제도 막는 다는 것. 그리고 writable과 configurable의 차이와 관계에 대해 다시 익혔습니다. writable은 할당연산자 = 로 속성값을 변경하는 일을 하고 configurable은 속성 Attribute의 재정의가 가능한지 여부를 판단하는 것. 그리고 우선순위가 writable이 configurable보다 높아 configurable:false 이고 writable : true 일때는 값 변경과 writable 프로퍼티 어트리뷰트 값 변경이 가능하다는 점입니다.
또 주의해야할 것은 프로퍼티의 프로퍼티 어트리뷰트 값, writable를 false 처리 했다면 이후 수정시 에러가 나지 않고 수정이 되지 않는다는 것입니다.(configurable:false 처리때 삭제시도시 무시) 다만 strict mode일경우 에러를 반환합니다. 근데 [[configurable]]이 false 인 해당 프로퍼티 재정의 시도시 에러가 발생합니다.
그리고 기존에 존재하는 객체에 단순히 프로퍼티를 추가했다면(점표기법 등) 만들어진 프로퍼티의 프로퍼티 어트리뷰트의 값은 전부 true 값으로 지정됩니다. 하지만 프로퍼티 어트리뷰트 재정의시 특정 프로퍼티 어트리뷰트값을 작성하지 않는다면,즉 디스크립터객체의 프로퍼티를 누락시키면 undefined, false (기본값)처리 됩니다.
const person = {
name: 'Lee',
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// 단순히 프로퍼티를 추가했다면(점표기법등) 프로퍼티 어트리뷰트의 값은 전부 true 처리
//{value "Lee" ,writable:true,enumerable:true,configurable:true}
const object1 = { property1: 11 };
Object.defineProperty(object1, 'property2', {
value: 22,
writable: false, //할당연산자로 속성값 변경 불가 처리
configurable: false, // 프로퍼티 어트리뷰트 재정의 변경불가, 프로퍼티 삭제 불가 처리
});
object1.property2 = 22; //수정이 되지 않지 않지만 에러가 나지 않습니다.
//strict mode일경우 에러를 반환합니다.
delete object1.property2; //삭제가 되지 않지만 에러가 나지 않는다.
console.log(object1); //property2가 보이지 않는다 왜일까? 프로퍼티 재정의시 enumarable 설정하지 않으니 자동 기본값 false 처리하여 보이지 않게 된다.
console.log(object1.property2); //22
*모든 객체는 [[prototype]]이라는 내부 슬롯을 갖으며 ._ _ proto _ _ 로 간접 접근이 가능합니다.
접근자 프로퍼티가 프로퍼티 값에 접근 동작하는 과정이 인상깊었습니다.
만약 접근자 프로퍼티 "fullName" 가 프로퍼티 값에 접근한다면
- [[Get]]내부 메서드가 호출
- 프로퍼티 키"fullName"가 유효한지 확인 : 심벌또는 문자열인지
- 프로토타입 체인에서 프로퍼티 검색
- 검색한 프로퍼티 fullName이 데이터 프로퍼티인지, 접근자 프로퍼티인지 확인
- 접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값 즉 getter 함수를 호출 -> 결과 반환
객체 변경 금지하기 위한 메서드들을 seal 과 freeze 도 있지만 객체의 확장만 금지하는 , 프로퍼티 추가만 할 수 없는 더 느슨한 개념의 Object.preventExtensions 가 있다는 점을 주목했습니다. 또 확장가능한 객체인지 확인하고 싶다면 Object.isExtensible() 메서드를 , 읽기 쓰기만 가능한 밀봉된 객체인지 알고 싶다면 Object.isSealed() , 읽기만 가능한 동결된 객체인지를 알고 싶다면 Object.isFrozen() 를 사용할 수 있다는 점을 잊지 말아야 겠습니다.
17장 생성자 함수에 의한 객체 생성
this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다는 점. 그리고 생성자 함수의 인스턴스 생성 과정이 인상깊었습니다.
생성자 함수의 인스턴스 생성 과정
- 인스턴스 생성과 this 바인딩 암묵적으로 빈 객체 생성 및 this에 바인딩(식별자와 값을 연결함) -> 함수 몸체의 코드가 한 줄씩 실행되는 런타임 이전에 실행됨.
- 인스턴스 초기화
- 인스턴스 반환 : 완성된 인스턴스가 바인딩된 this 를 암묵적으로 반환
- 다른 객체를 명시적으로 반환시 그 객체가 반환되며 원시값 반환시 원시값 무시, this 가 반환됨
함수는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드+ [[Environments]],[[FormalParameters]]등의 내부 슬롯과 [[Call]] ,[[Construct]] 선택) 를 가지며 함수는 callable 이자 non-constructor 과 constructor로 나눌 수 있는데 이는 함수 정의 방식에 따라 구분할 수 있다는 점이 새롭게 알게되었습니다. non-constructor 인 함수는 메서드(es6축약표현), 화살표함수 로 함수가 정의되었을 때 constructor 는 함수 선언문. 함수 표현식. 클래스 로 정의되었을 때 로 구분할 수 있습니다.
new.target 와 스코프 세이프 생성자 패턴
new.target은 생성자 함수가 new 연산자없이 호출되는 것을 방지하는 메타 프로퍼티 입니다. es6문법에 추가된 최신 문법이며 그전에는 스코프 세이프 생성자 패턴을 사용했습니다.
new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. new 연산자없이 일반함수로서 호출된 내부의 new.target은 undefined 다.
function Circle(radius) {
if (!new.target) {
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle = Circle(5); // new 없이 호출해도 생성자함수로 호출됨
//스코프 세이프 생성자 패턴
function Circle(radius) {
if (!(this instanceof Circle)) {
// new 연산자와 함께 호출되지 않았다면 this는 window 를 가리키고 circle 과 연결되지 않는다.
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle = Circle(5); // new 없이 호출해도 생성자함수로 호출됨