제대로 배우자, 자바스크립트(Javascript) #4 – 객체

JavaScript 객체는 동적이라 언제든지 바뀔 수 있다는 것을 기억하자. 반면에 Java 같은 언어에서는 한 번 정의한 클래스는 객체를 수정할 수 없도록 만든다.

I. 프로퍼티 정의

객체를 만드는 방법에는 Object 생성자를 사용하는 방법과 객체 리터럴을 사용하는 방법이 있다.

  1. Object 생성자 사용

var obj = new Object();
obj.name = “라이언”;

2. 객체 리터럴 사용

var obj = {
name: “라이언”
};

JavaScript는 프로퍼티를 처음 추가할 때 객체에 있는 [[Put]]이라는 내부 메소드를 호출한다. [[Put]] 메소드는 객체에 프로퍼티 저장 공간을 생성한다. 이 과정은 해시 테이블에 처음 키를 추가하는 것과 비슷하다. 이 동작은 수행하면 초기값은 물론 프로퍼티의 속성도 설정한다.

[[Put]]을 호출하면 객체에 고유 프로퍼티(own property)가 만들어진다. 고유 프로퍼티는 객체의 특정 인스턴스에 속해있으며 인스턴스에 바로 저장된다. 또한 프로퍼티에 동작을 수행하려면 소유 객체를 거쳐야 한다. 참고로 프로토타입 프로퍼티(prototype property)는 특정 객체에 소유되지 않아 동일한 객체가 공유하는 프로퍼티를 뜻한다.

기존 프로퍼티에 새 값을 할당하면 [[Set]]이 호출된다. [[Set]]은 프로퍼티의 현재 값을 새 값으로 교체한다.

II. 프로퍼티 탐지

프로퍼티의 존재여부를 탐지하려면 in 연산자를 사용한다. 이 방식을 사용하면 실제 프로퍼티의 값을 확인하지 않아 성능에 영향을 끼치지 않는다.

console.log(“name” in person1);
console.log(“age” in person1);

참고로 in 연산자는 고유 프로퍼티(own property)와 프로토타입 프로퍼티(prototype property)을 모두 찾기 때문에 고유 프로퍼티를 확인하려면 hasOwnProperty() 메소드를 활용한다.

프로퍼티 검토 in 연산자

III. 프로퍼티 제거

객체에서 프로퍼티를 완전히 제거할 때는 delete 연산자를 사용한다. 객체 프로퍼티에 delete 연산자를 사용하면 내부적으로 [[Delete]]가 호출된다. 이 동작은 해시 테이블에서 키/값 쌍을 없애는 것으로 볼 수 있다. delete 연산자는 호출 성공 시 true를 반환한다.

IV. 열거

객체를 추가하는 프로퍼티는 기본적으로 열거(enumerable)가 가능하다. 즉 for-in 반복문을 사용해 훑을 수 있다. 열거 가능 프로퍼티에는 [[Enumerable]]이라는 내부 속성이 true로 설정되어 있다.

for-in 반복문에서는 열거 가능한 프로토타입 프로퍼티도 반환하지만 Object.keys()는 고유 프로퍼티만 반환한다. 참고로 네이티브 프로퍼티는 대부분 열거가능하지 않다.

모든 프로퍼티를 열거할 수 있는 것은 아니다. 객체의 네이티브 메소드는 대부분 [[Enumerable]] 속성이 false로 설정되어 있다. 특정 프로퍼티가 열거 가능한지 확인할 때는 propertyIsEnumerable() 메소드를 사용하면 된다.

V. 프로퍼티 종류

프로퍼티에는 값을 포함하고 있는 데이터 프로퍼티(Data Property)와 값을 포함하지 않는 대신 프로퍼티를 읽었을 때 Getter와 Setter를 포함하는 접근자 프로퍼티(Accessor Property)가 있다.

VI. 프로퍼티 속성

데이터 프로퍼티와 접근자 프로퍼티의 공통 속성은 [[Enumerable]], [[Configurable]] 속성이다. 프로퍼티 속성은 Object.defineProperty() 메소드로 변경할 수 있는데 이 메소드는 아래 인수 세 개를 전달한다.

  1. 프로퍼티를 소유하고 있는 객체
  2. 프로퍼티 이름
  3. 설정할 프로퍼티 속성 값을 갖고 있는 프로퍼티 서술자(Property Descriptor)

var person = { name: “Ryan” };

Object.defineProperty(person, “name”, { enumerable: false });

데이터 프로퍼티에는 접근자 프로퍼티에는 없는 [[Value]], [[Writable]] 속성이 있다. [[Value]] 속성은 프로퍼티의 값을 저장하고 있고 [[Writable]] 속성은 프로퍼티에 값을 쓸 수 있는지 여부를 설정한다.

반면에 접근자 프로퍼티에는 [[Get]], [[Set]] 속성이 있다.

여러 프로퍼티를 설정하려면 Object.defineProperties() 메소드를 사용한다. 이 메소드는 아래 두 개 인수를 전달한다.

  1. 대상 객체
  2. 정의할 프로퍼티의 정보를 담고 있는 객체

var person = {};

Object.defineProperties(person, {
_name: {
value: “Ryan”,
enumerable: true,
configurable: true,
writable: true
},

_name: {
get: function() {
console.log(“name 읽는 중”);
return this._name;
},

set: function(value) {
console.log(“name의 값을 %s로 설정하는 중”, value);
this._name = value;
}
});

Object.defineProperties()를 사용하면 프로퍼티를 몇 개든 정의할 수 있으며 기존 프로포티 수정과 새 프로퍼티 추가를 동시에 수행할 수 있다.

프로퍼티 속성을 가져오려면 Object.getOwnPropertyDescriptor() 메소드를 사용한다. 이 메소드는 아래처럼 인수가 두 개다.

  1. 대상 객체
  2. 정보를 가져올 프로퍼티의 이름

VII. 객체 수정 방지

객체에는 객체의 동작을 제어하는 내부 속성이 있다. 그 중 하나인 [[Extensible]]은 객체 자체의 수정 가능 여부를 가리키는 논리값을 갖고 있다. 객체를 수정할 수 없도록 만드는 방법은 크게 세 가지가 있다.

  1. 확장 방지 – Object.preventExtensions(), Object.isExtensible() 사용
  2. 객체 봉인 – Object.seal(), Object.isSeal() 사용
  3. 객체 동결 – Object.freeze(), Object.isFreeze() 사용

VII. 요약

자바스크립트 객체는 프로퍼티가 키/값 쌍으로 되어있는 만큼 해시 맵에 빗대어 생각하면 이해하기 쉽다. 객체 프로퍼티에 접근할 때는 점 표기법 또는 각괄호 표기법 중 무엇을 사용해도 상관없다. 프로퍼티에 값을 할당하면 언제든 객체에 새 프로퍼티를 추가할 수 있으며 delete 연산자를 사용하면 언제든 프로퍼티를 제거할 수 있다. 프로퍼티의 존재 여부는 프로퍼티 이름과 객체를 in 연산자와 함께 사용하면 알 수 있다. 이때 고유 프로퍼티만 확인하고 싶다면 모든 객체에 다 포함되어 있는 hasOwnProperty()를 사용하면 된다. 모든 객체 프로퍼티는 기본적으로 열거 가능하다. 열거 가능하다는 말은 [[Enumerable]] 내부 속성 값이 true 여서 for-in 반복문이나 Object.keys()를 사용할 때 볼 수 있다는 뜻이다.

프로퍼티는 데이터 프로퍼티접근자 프로퍼티로 나눌 수 있다. 데이터 프로퍼티는 값을 담아두기 위한 공간으로 값을 읽거나 저장할 수 있다. 데이터 프로퍼티에 함수를 저장하면 이 프로퍼티는 객체의 메소드로 취급된다. 접근자 프로퍼티는 Getter와 Setter를 조합하여 특정 동작을 수행한다. 데이터 프로퍼티와 접근자 프로퍼티 모두 객체 리터럴 표기법을 사용해 만들 수 있다.

모든 프로퍼티에는 관련된 내부 속성이 몇 가지 있으며 이러한 속성이 프로퍼티가 어떻게 동작하는지 결정한다. 데이터 프로퍼티와 접근자 프로퍼티에는 둘 다 [[Enumerable]]과 [[Configurable]]이라는 속성이 있다. 데이터 프로퍼티에는 [[Writable]]과 [[Value]]라는 속성이 추가로 있는 반면, 접근자 프로퍼티에는 [[Get]]과 [[Set]] 속성이 더 있다. 모든 프로퍼티의 [[Enumerable]]과 [[Configurable]] 속성은 true가 기본 값이며 데이터 프로퍼티의 [[Writable]] 속성 역시 true가 기본 값이다. Object.defineProperty() 또는 Object.defineProperties()를 사용하면 속성을 변경할 수 있으며 Object.getOwnPropertyDescriptor()를 사용하면 설정된 속성 값을 가져올 수 있다.

객체의 프로퍼티를 수정할 수 없게 만드는 방법은 세 가지가 있다. Object.preventExtensions()를 사용하면 객체에 프로퍼티를 추가할 수 없다. Object.seal() 메소드를 사용하면 봉인된 객체를 만들 수 있는데 봉인된 객체는 확장 불가능해지며 객체의 프로퍼티는 설정 불가능해진다. Object.freeze()를 사용해 객체를 동결하면 객체가 봉인되는 것은 물론 데이터 프로퍼티에 값을 저장할 수도 없게 된다. 확장 불가능한 객체를 다룰 때는 잘못된 방식으로 객체에 접근했을 때 에러가 발생할 수 있도록 항상 엄격한 모드를 사용하는 것이 좋다.

제대로 배우자, 자바스크립트(Javascript) #3 – 함수

이 포스트는 JavaScript를 작정하고 정리하기 위한 시리즈다. 이번에는 JavaScript의 함수를 알아보자. 시작하기 전에 기억하자 JavaScript에서 함수는 객체다.

I. 내부 속성

함수는 객체인데 다른 객체에는 없는 함수만의 특성을 꼽으라면 [[Call]]이라는 내부 속성을 들 수 있다. 내부 속성은 코드로 접근할 수는 없지만 코드의 동작을 정의한다. 자바스크립트에는 ECMAScript에서 정한 여러 객체 내부 속성이 있는데 이러한 내부 속성은 각괄호를 두 개 겹친 표기법으로 정의한다.

[[Call]]은 함수에만 있는 속성으로 객체가 실행될 수 있는지 없는지 판단한다. 이 속성은 함수에만 있기 때문에 ECMAScript에서는 어떤 객체든 [[Call]] 속성을 포함하고 있으면 typeof 연산자를 사용했을 때 “function”을 반환하도록 정의했다.

II. 호이스팅(Hoisting)

자바스크립트의 호이스팅에 대해 설명하기 전에 먼저 호이스트(hoist)가 뭔지 알아보자. 호이스트(hoist)는 무거운 거를 들어올린다는 영어 단어로 명사와 동사로 쓸 수 있다.

hoist

v. to lift something heavy, sometimes using ropes or a machine:
n.a device used for lifting heavy things

함수 호이스팅(hoisting)이란 코드가 실행될 때 컨텍스트(선언된 함수를 포함하고 있는 함수 스코프 또는 전역 스코프) 상단에 끌어올려지는 것을 말한다.

JavaScript에서 함수는 함수 선언(function declaration)함수 표현식(function expression) 또는 익명 함수(anonymous function)라고도 한다.

함수 선언(function declaration)은 아래와 같이 함수를 정의한다.

function add(num1, num2) {
return num1 + num2;
}

함수 표현식(function expression)은 아래와 같이 함수를 정의한다.

var add = function(num1, num2) {
return num1 + num2;
};

위 두 함수 중 함수 호이스팅(hoisting)은 함수 선언(function declaration)에만 적용된다.

III. 인수(arguments)

자바스크립트 함수의 다른 특성 중 하나는 인수를 몇 개 전달하든 에러가 발생하지 않는다는 것이다. 이는 함수가 실제로는 배열과 비슷한 arguments라는 구조체에 저장되기 때문이다. 평범한 자바스크립트 배열과 마찬가지로 arguments라는 구조체에 저장되기 때문이다.

참고로 ES6에서는 rest parameter와 default parameter로 arguments를 대체할 수 있다. 코드 가독성과 JavaScript VM 성능 향상을 위해 arguments 사용을 자제하는 게 좋다고 함

출처: ES6 In Depth: 레스트 파라메터와 디폴트 파라메터

JavaScript에서 인수의 개수는 length 프로퍼티로 확인할 수 있다.

함수 length 프로퍼티

IV. 오버로딩 미지원

함수 오버로딩(function overloading)은 시그니처(signature)를 통해 이름이 같은 함수를 구분하는 기능이다.

시그니처(signature)는 함수 이름, 인수 타입으로 이루어짐

V. this 객체

자바스크립트의 모든 스코프에는 함수를 호출하는 객체를 뜻하는 this 객체가 있다. 전역 스코프에서 this는 전역 객체(웹 브라우저에서는 window)를 참조한다. 객체에 붙어있는 함수를 호출하면 this의 값은 해당 객체가 된다. 따라서 메소드 안에서라면 객체를 직접 참조하는 대신 this를 참조할 수 있다.

VI. this값 변경

일반적으로 this 값은 자동으로 할당되지만 목적에 따라 바꿀 수 있으며 여기에는 세 가지 방법이 있다.

1. call() 메소드

call() 메소드의 첫 번째 인수는 함수를 실행할 때 this로서 사용될 값이다. 두 번째 인수 부터는 함수를 실행할 때처럼 전달된다.

2. apply() 메소드

call()과 완전히 동일하지만 인수를 두 개만 사용한다는 점이 다름. apply() 메소드의 첫 번째 인수는 this로 사용할 값이고, 두 번째 인수는 함수에 전달할 인수를 담고 있는 배열 또는 배열과 유사한 객체다.

일반적으로 가지고 있는 데이터 종류에 따라 call() 또는 apply() 중 하나를 선택한다. 데이터가 이미 배열 형태로 존재한다면 apply()를 사용하고 데이터가 개별 변수로 존재한다면 call()을 사용하는 편이 좋다.

3. bind() 메소드

bind()의 첫 번째 인수는 새 함수에서 this로 사용할 값이다. 그 밖의 인수는 새 함수를 실행할 때 항상 전달되는 고정 인수를 의미한다.