15장 let, const 키워드와 블록 레벨 스코프
var 키워드로 선언한 변수를 중복 선언하면 초기화문 유무에 따라 다르게 작동한다. 초기화문이 있는 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작하고 초기화문이 없는 변수 선언문은 에러없이 무시된다.
var 키워드로 선언한 변수는 함수의 코드 블록만을 지역 스코프로 인정한다. 함수 외부에서 var 키워드로 선언한 변수는 코드 블록 내에서 선언해도 모두 전역 변수가 된다.
for문에서 var 키워드로 선언된 변수도 전역 변수가 된다.
함수 레벨 스코프는 전역 변수를 남발할 가능성을 높인다.
var 키워드로 변수를 선언하면 변수 호이스팅에 의해 스코프의 선두로 끌어 올려진 것처럼 동작한다. 그래서 변수 호이스팅에 의해 var 키워드로 선언된 변수는 변수 선언문 이전에 참조할 수 있다. 단, 할당문 이전에 변수를 참조하면 undefined를 반환한다.
let 키워드로 이름이 같은 변수를 중복 선언하면 문법 에러가 발생한다.
let 키워드로 선언한 변수는 모든 코드 블록(함수, if문, while문, try/catch문 등 )을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
let 키워드로 선언한 변수는 변수 선언문 이전에 참조하면 참조 에러가 발생한다.
var 키워드로 선언한 변수는 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 "선언 단계"와 "초기화 단계"가 진행된다.
선언 단계에서 스코프에 변수 식별자를 등록해 자바스크립트 엔진에 변수의 존재를 알린다. 그리고 바로 초기화 단계에서 undefined로 변수를 초기화한다. 그래서 변수 선언문 이전에 변수에 접근해도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않지만 undefined를 반환하고 이후 변수 할당문에 도달하면 값이 할당된다.
let 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행된다. 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다.
초기화 단계가 실행되기 전에 변수에 접근하려고 하면 참조 에러가 발생한다. 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 일시적 사각지대(Temporal Dead Zone:TDZ)라고 한다.
var 키워드로 선언한 전역 변수, 전역 함수, 암묵적 전역은 전역 객체 window의 프로퍼티가 된다.
let 키워드로 선언된 전역 변수는 전역 객체의 프로퍼티가 아니다.
const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 한다.
const 키워드로 선언한 변수는 let 키워드로 선언한 변수와 마찬가지로 블록 레벨 스코프를 가지고 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
var, let 키워드로 선언한 변수는 재할당이 자유롭지만 const 키워드로 선언한 변수는 재할당이 금지된다.
const 키워드로 선언한 변수에 원시 값을 할당한 경우 변수 값을 변경할 수 없다. 변수의 상대 개념인 상수는 재할당이 금지된 변수를 말한다.
const 키워드로 선언된 변수에 원시 값을 할당한 경우 원시 값은 변경할 수 없는 값이고 const 키워드에 의해 재할당이 금지되므로 할당된 값을 변경할 수 있는 방법이 없다.
const 키워드로 선언된 변수에 객체를 할당한 경우에는 값을 변경할 수 있다. const 키워드는 재할당을 금지할 뿐 불변을 의미하지는 않는다. 즉, 새로운 값을 재할당하는 것은 불가능하지만 프로퍼티 동적 생성, 삭제, 프로퍼티 값의 변경을 통해 객체를 변경하는 것은 가능하다.
변수 선언에는 const를 사용하고 let은 재할당이 필요한 경우 한정해서 사용하는 것이 좋다. 이때 변수의 스코프는 최대한 좁게 만든다.
16장 프로퍼티 어트리뷰트
ECMAScript 사양에 등장하는 이중 대괄호([[...]]
)로 감싼 이름들이 내부 슬롯과 내부 메서드이다.
내부 슬롯과 내부 메서드는 ECMAScript 사양에 정의된 대로 구현되어 자바스크립트 엔진에서 실제로 동작하지만 개발자가 접근할 수 있게 외부로 공개된 객체 프로퍼티는 아니다.
하지만 일부 내부 슬롯과 내부 메서드는 간접적으로 접근할 수 있다.
예를 들어 모든 객체는 [[Prototype]]
이라는 내부 슬롯을 갖는다. 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로는 접근할 수 없지만 [[Prototype]]
내부 슬롯은 __proto__
를 통해 간접적으로 접근할 수 있다.
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
프로퍼티의 상태란 프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부를 말한다.
프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯 [[Value]]
, [[Writable]]
, [[Enumerable]]
, [[Configurable]]
이다.
어트리뷰트에 직접 접근할 수는 없지만 Object.getOwnPropertyDescriptor
메서드를 사용해 간접적으로 확인할 수 있다.
Object.getOwnPropertyDescriptor
메서드를 호출할 때 첫 번째 매개변수에는 객체의 참조를 전달하고, 두 번째 매개변수에는 프로퍼티 키를 문자열로 전달한다.
이때 Object.getOwnPropertyDescriptor
메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
Object.getOwnPropertyDescriptor
메서드는 하나의 프로퍼티에 대해 프로퍼티 디스크립터 객체를 반환하지만 ES8에서 도입된 Object.getOwnPropertyDescriptors
메서드는 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환한다.
데이터 프로퍼티의 프로퍼티 어트리뷰트
접근자 프로퍼티의 프로퍼티 어트리뷰트
접근자 함수는 getter / setter 함수라고도 부른다.
프로퍼티 정의란 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말한다.
Object.defineProperty
메서드를 사용해 프로퍼티의 어트리뷰트를 재정의할 수 있다.
인수로는 객체의 참조와 데이터 프로퍼티의 키, 프로퍼티 디스크립터 객체를 전달한다.
Object.defineProperty
메서드는 한번에 하나의 프로퍼티만 정의할 수 있고, Object.defineProperties
메서드를 사용하면 여러 개의 프로퍼티를 한 번에 정의할 수 있다.
자바스크립트에는 객체의 변경을 방지하는 다양한 메서드가 있다.
Object.preventExtensions
Object.seal
Object.freeze