독서/모던 자바스크립트 Deep Dive

모던 자바스크립트 Deep Dive (16장 ~ 18장)

nan-noo 2023. 3. 29. 23:55
728x90

🔰프로퍼티 어트리뷰트

내부 슬롯과 내부 메서드

자바스크립트 엔진 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 pseudo property와 pseudo method다. ECMAScript 사양에 등장하는 이중 대괄호([[...]])로 감싼 이름들이 내부 슬롯과 내부 메서드다.

개발자가 직접 접근할 수 있는 공개된 객체의 프로퍼티가 아니다. 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접 접근 수단을 제공하기도 한다.

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

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

프로퍼티 상태란 프로퍼티의 값(value), 값의 갱신 가능 여부(writable), 열거 가능 여부(enumerable), 재정의 가능 여부(configurable)를 말한다.

프로퍼티 어트리뷰트란 자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이다. Object.getOwnPropertyDescriptor 메서드로 간접적으로 확인할 수 있다.

프로퍼티 디스크립터 객체는 프로퍼티 어트리뷰트 정보를 제공하는, Object.getOwnPropertyDescriptor 메서드가 반환하는 객체다.

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

데이터 프로퍼티(data property)는 키와 값으로 구성된 일반적인 프로퍼티다.

프로퍼티 어트리뷰트 프로퍼티 디스크립터 객체의 프로퍼티 설명
[[Value]] value 프로퍼티 값에 접근하면 반환되는 값이다.
프로퍼티 값을 변경하면 [[Value]]에 값을 재할당한다. 프로퍼티가 없으면 프로퍼티를 동적 생성하고 [[Value]]에 값을 저장한다.
[[Writable]] writable 프로퍼티 값의 변경 여부를 나타내며, 불리언 값이다.
false인 경우, 읽기 전용 프로퍼티가 된다.
[[Enumerable]] enumerable 프로퍼티의 열거 가능 여부를 나타내며, 불리언 값이다.
false인 경우, for...in문이나 Object.keys 등으로 열거할 수 없다.
[[Configurable]] configurable 프로퍼티의 재정의 가능 여부를 나타내며, 불리언 값이다.
false인 경우 해당 프로퍼티의 삭제와 프로퍼티 어트리뷰트 값의 변경이 금지된다. 단, [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다.

프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로 초기화되며, [[Writable]], [[Enumerable]], [[Configurable]]의 값은 모두 true로 초기화된다.

접근자 프로퍼티(accessor property)는 자체적으로 값([[Value]])을 갖지 않는, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티다.

프로퍼티 어트리뷰트 프로퍼티 디스크립터 객체의 프로퍼티 설명
[[Get]] get 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수다.
접근자 프로퍼티 키로 프로퍼티 값에 접근하면 [[Get]]의 값인 getter 함수가 호출되고 값을 반환한다.
[[Set]] set 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다.
접근자 프로퍼티 키로 프로퍼티 값을 저장하면 [[Set]]의 값인 setter 함수가 호출되고 결과가 프로퍼티 값으로 저장된다.
[[Enumerable]] enumerable 데이터 프로퍼티의 [[Enumerable]]과 같다.
[[Configurable]] configurable 데이터 프로퍼티의 [[Configurable]]과 같다.

프로퍼티 정의

새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말한다. Object.defineProperty 메서드로 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부 등을 정의할 수 있다.

객체 변경 방지

기본적으로 객체는 변경 가능한 값이지만, 자바스크립트는 객체의 변경을 방지하는 다양한 메서드를 제공한다.

구분 메서드 프로퍼티 추가 프로퍼티 삭제 프로퍼티 값 읽기 프로퍼티 값 쓰기 프로퍼티 어트리뷰트 재정의 설명
객체 확장 금지 Object.preventExtensions X O O O O 프로퍼티 추가가 금지된다.
객체 밀봉 Object.seal X X O O X 읽기와 쓰기만 가능하다.
객체 동결 Object.freeze X X O X X 읽기만 가능하다.

위의 객체 변경 방지 메서드들은 얕은 변경 방지(shallow only)로 중첩 객체까지 영향을 주진 못한다. 모든 중첩 객체까지 동결하기 위해선 재귀적으로 Object.freeze를 호출해야 한다.

🔰생성자 함수에 의한 객체 생성

Object 생성자 함수

new 연산자와 함께 Object 생성자 함수를 호출하면 빈 객체를 생성하여 반환한다. 빈 객체를 생성하고 프로퍼티 또는 메서드를 추가하여 객체를 완성할 수 있다.

🍎 생성자 함수
생성자 함수(constructor)란, new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수를 말한다. 생성자 함수에 의해 생성된 객체를 인스턴스라 한다.
자바스크립트는 Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수를 제공한다.

생성자 함수

객체 리터럴에 의한 객체 생성 방식은 직관적이고 간편하지만, 단 하나의 객체만 생성한다. 따라서 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 비효율적이다.

생성자 함수에 의한 객체 생성 방식은, 마치 객체를 생성하기 위한 클래스처럼 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.

new 연산자와 함께 생성자 함수를 호출하면 자바스크립트 엔진은 다음과 같은 과정을 거친다.

  1. 인스턴스 생성과 this 바인딩: 런타임 이전에 암묵적으로 빈 객체(인스턴스)가 생성되고, this에 바인딩된다.
  2. 인스턴스 초기화: 생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다. 즉, 프로퍼티나 메서드를 this에 바인딩되어 있는 인스턴스에 추가하고 초기화한다.
  3. 인스턴스 반환: 생성자 함수 내부의 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다. 만약 this가 아닌 다른 객체를 명시적으로 반환하면 this가 반환되지 않는다. 하지만 원시 값을 명시적으로 반환하면 원시 값 반환은 무시된다. 이러한 문제때문에 생성자 함수 내부에는 return 문을 반드시 생략해야 한다.

함수 선언문 또는 함수 표현식으로 정의한 함수는 일반 함수는 물론, 생성자 함수로서도 호출할 수 있다. 따라서 일반 함수와 구별하기 위해 생성자 함수는 일반적으로 파스칼 케이스로 명명한다.

일반 함수로서 호출하면 함수 객체의 내부 메서드 [[Call]]이 호출되고, new 연산자와 함께 생성자 함수로 호출하면 내부메서드 [[Construct]]가 호출된다.

내부 메서드 [[Call]]을 갖는 함수 객체를 callable, 내부 메서드 [[Construct]]를 갖는 함수 객체를 constructor, [[Contructor]]를 갖지 않는 함수 객체를 non-constructor라 부른다.

함수 객체는 반드시 callable이어야 한다. 즉, [[Call]]을 갖고 있어서 호출할 수 있다. 다만, 모든 함수 객체가 [[Construct]]를 갖는 것은 아니다. 

constructor에는 함수 선언문, 함수 표현식, 클래스로 정의한 함수가 있다. non-constructor에는 메서드(ES6 메서드 축약 표현), 화살표 함수로 정의한 함수가 있다.

🍎 자바스크립트의 메서드
일반적으로 함수를 프로퍼티 값으로 사용하면 메서드라 하지만, ECMAScript 사양에서 메서드는 ES6의 메서드 축약 표현만을 의미한다. 함수 표현식으로 정의한 함수를 객체 프로퍼티 값으로 할당한 것은 메서드로 인정하지 않는다.
다시 말해 함수가 어디에 할당되어 있는지에 따라 메서드를 판단하는 것이 아니라, 함수가 정의된 방식에 따라 constructor와 non-constructor를 구분한다.

ES6부터 지원하는 new.target은 this와 유사하게 constructor인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용되며, 메타 프로퍼티라 부른다. new.target을 사용하면 new 연산자와 함께 생성자 함수로서 호출되었는지 확인할 수 있다. new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. 일반 함수로 호출된 new.target은 undefined다.

대부분의 빌트인 생성자 함수는 new 연산자와 함께 호출되었는지를 확인한 후 적절한 값을 반환한다. Object와 Function 생성자 함수는 new 연산자 없이도 동일하게 동작하지만, String, Number, Boolean 생성자 함수는 다르게 동작한다.

🔰함수와 일급 객체

일급 객체

런타임에 생성이 가능하고, 변수나 자료구조에 저장할 수 있고, 함수의 매개변수에 전달하거나 반환값으로 사용할 수 있는 객체를 일급 객체라 한다.

자바스크립트 함수는 일급 객체다. 즉, 값으로 사용할 수 있다.

함수는 객체이지만 일반 객체와는 달리 호출할 수 있고 함수 고유의 프로퍼티를 소유한다.

함수 객체의 프로퍼티

arguments, caller, length, name, prototype 프로퍼티는 모두 함수 객체 고유의 데이터 프로퍼티다. 

arguments arguments 프로퍼티의 값은 arguments 객체다. arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있는 iterable 유사 배열 객체이며, 함수 내부에서 지역 변수처럼 사용된다.
arguments 프로퍼티는 ES3부터 표준에서 폐지되었으므로 Function.arguments 사용은 권장하지 않는다. 함수 내부에서 사용할 수 있는 arguments 객체를 사용하자.
함수가 호출될 때 전달된 모든 인수는 암묵적으로 arguments 객체의 프로퍼티로 보관된다. 프로퍼티 키는 인수의 순서, 프로퍼티 값은 인수를 나타낸다.
arguments 객체의 callee 프로퍼티는 호출되어 함수 자신을 가리키고, length 프로퍼티는 인수의 개수를 나타낸다.
매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 유용하다.
caller ECMASCript 사양에 포함되지 않은 비표준 프로퍼티다. 사용하지 말자.
함수 자신을 호출한 함수를 가리킨다.
length 함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.
name 함수 이름을 나타낸다. ES6에서 정식 표준이 되었다.
ES5와 ES6에서 동작을 달리한다. 익명 함수 표현식의 경우 ES5에서 name 프로퍼티는 빈 문자열을 값으로 갖는다. ES6에선 함수 객체를 가리키는 식별자를 값으로 갖는다.
prototype constructor만이 소유하는 프로퍼티다. non-constructor에는 prototype 프로퍼티가 없다.
함수가 생성자 함수로 호출될 때 생성자 함수가 생성할 인스턴스의 프로토타입 객체를 가리킨다.
728x90