3 minute read

모던 자바스크립트 Deep Dive 16~18장을 정리한 글입니다.

프로퍼티

내부 슬롯, 내부 메서드

ECMAscript 사양에 등장하는 이중 대괄호([[…]])로 감싼 이름들에 해당한다. 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAscript 사양에서 사용하는 의사 프로퍼티, 의사 메서드 이다. 대부분 직접 접근이 불가능하지만 일부에 한해 접근할 수 있는 수단을 제공한다.

프로퍼티 어트리뷰트

프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 값이다. 프로퍼티의 상태에는 아래와 같이 있다.

  • 값 (value)
  • 값의 갱신 가능 여부 (writable)
  • 열거 가능 여부 (enumerable)
  • 재정의 가능 여부 (프로퍼티 삭제, 프로퍼티 어트리뷰트 값 변경) (configurable)

[[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이 프로퍼티 어트리뷰트에 해당한다. Object.getOwnPropertyDescriptor 메서드를 이용하여 프로퍼티 디스크립터 객체를 통해 프로퍼티 어트리뷰트 정보를 제공받을 수 있다.

프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분한다.

데이터 프로퍼티

키와 값으로 이루어진 일반적인 프로퍼티이며 위의 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 4가지를 프로퍼티 어트리뷰트로 가진다.

접근자 프로퍼티

접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성되어있다. 프로퍼티 어트리뷰트는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 으로 구성된다

const person = {
    // 데이터 프로퍼티
    firstName: 'Hoyoung',
    lastName: 'Moon',

    // 접근자 프로퍼티
    get fullName(){
        return `${this.firstName} ${this.lastName}`
    }
    set fullName(name) {
        [this.firstName, this.lastName] = name.split(' ');
    }
}

console.log(person.fullName) // getter 함수 호출
person.fullName = 'Steve Jobs' // setter 함수 호출

프로퍼티 정의

위와 같이 직접 Object.defineProperty 정적 메서드를 통해 정의가 가능하다.

const person = {};

Object.defineProperty(person, "firstName", {
  value: "Hoyoung",
  writable: true,
  enumerable: true,
  configurable: true,
});

또한 객체 변경 방지를 위한 몇가지 메서드가 추가적으로 제공된다.

  • Object.preventExtensions

    메서드 이름 그대로 프로퍼티를 확장할 수 없기 때문에 프로퍼티 추가가 금지된다.

  • Object.seal

    객체를 밀봉하여 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의를 금지한다. 즉, 읽기와 쓰기만 가능하다.

  • Object.freeze

    프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의, 프로퍼티 값 갱신 모두 금지한다. 즉, 읽기만 가능하다.



생성자 함수를 통한 객체 생성

객체 리터럴이 아닌 생성자 함수를 통해 객체를 생성할 경우 같은 객체를 여러개 생성해야할때 효율적으로 생성할 수 있다.

function Circle(radius) {
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}
const circle1 = new Circle(10);
const circle2 = new Circle(15);

생성자 함수의 인스턴스 생성 과정은 크게 3가지로 나누어진다.

  1. 인스턴스 생성과 this 바인딩

    new를 이용하여 생성자 함수를 호출할 경우 빈 객체(인스턴스)가 암묵적으로 생성되고 이는 this에 바인딩된다.

  2. 인스턴스 초기화

    생성자 함수의 코드가 한 줄씩 실행되어 this에 바인딩된 인스턴스를 초기화한다. 위의 Circle의 경우 인수로 받은 radius를 이용하여 프로퍼티 (radius), 메서드 (getDiameter)를 초기화한다.

  3. 인스턴스 반환 모든 처리가 끝난 후 this가 암묵적으로 반환된다.

함수도 객체이지만 일반 객체와 달리 [[Call]], [[Construct]] 같은 내부 메서드를 추가로 가지고 있다. 일반 함수로 호출시 [[Call]]이, 생성자 함수로 호출시 [[Construct]]가 호출된다.

생성자 함수로 호출될 수 있는 함수로 constructor라 하고 호출될 수 없는 경우 non-constructor이다. 함수 정의 방식에 따라 아래와 같이 나뉘어진다.

  • constructor: 함수 선언문, 함수 표현식, 클래스
  • non-constructor: 메서드(ES6 메서드 축약 표현), 화살표 함수
const circle = Circle(5);
console.log(circle); // undefined
console.log(radius); // 5
console.log(getDiameter()); // 10

만약 위와 같이 new 연산자를 붙이지 않고 생성자 함수를 호출할 경우 [[Construct]]가 아닌 [[Call]]이 호출되고 일반 함수이므로 this에 전역 객체 window가 바인딩된다. 따라서 radius, getDiameter는 전역객체의 프로퍼티와 메서드가 된다.

이러한 연산자 없이 호출하는 일을 방지하기 위해 new.target을 이용할 수 있다. new.target은 함수 내부의 지역 변수와 같이 사용되는 메타 프로퍼티이다. new 연산자와 함께 호출될 시 함수 자신을 가리키고, 일반 함수로 호출시 undefined를 나타낸다.

function Circle(radius) {
  if (!new.target) return new Circle(radius);
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * radius;
  };
}

const circle = Circle(5);
console.log(circle.getDiameter); // 10

함수와 일급 객체

일급 객체는 다음과 같은 조건들을 만족해야 한다.

  1. 무명의 객체 리터럴로 생성할 수 있어야 한다. (런타임 생성 가능)
  2. 변수나 자료구조에 저장할 수 있다.
  3. 함수의 매개변수 또는 반환값으로 사용할 수 있다.

이는 함수에 적용해도 모두 만족한다. 즉, 객체와 동일하게 사용할 수 있는 함수는 일급 객체이다.

함수는 객체이므로 프로퍼티를 가질 수 있으며 함수만 가지고 있는 특별한 프로퍼티들이 있다.

arguments

arguments는 함수 호출 시 전달된 인수들이 정보를 담고 있는 순회 가능한 유사 배열 객체이다. 유사 배열 객체이므로 인수의 개수를 length 프로퍼티로 가지며 인수를 프로퍼티 값으로 가지고 키는 인수의 순서를 나타낸다.

length

함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.

name

함수 객체의 이름을 나타낸다. 익명 함수 표현식의 경우 ES6부터 함수 객체를 가리키는 식별자를 값으로 갖는다.

Categories:

Updated: