티스토리 뷰





반응형

배열과 마찬가지로 자바스크립트 객체 역시 컨테이너지만, 크게보면 다음 두 가지 측면에서 배열과 다릅니다.

  • 배열은 값을 가지며 각 값에는 숫자형 인덱스가 있습니다. 객체는 프로퍼티를 가지며 각 프로퍼티에는 문자열 이나 심볼 인덱스가 있습니다.
  • 배열에는 순서가 있습니다. 하지만 객체에는 그런 순서가 보장되지 않습니다. obj.a가 obj.b보다 앞에 있다고 말할 수는 없습니다.

객체를 순회하기 좋은 루프중에 for...in등이 있는데 주의할 점은 일반적인 배열에서는 for루프나 forEach를 사용하는게 더 유용합니다.

 

Object.keys

객체에서 나열 가능한 문자열 프로퍼티를 배열로 반환합니다.

예를 들어 객체에서 x로 시작하는 프로퍼티를 모두 가져온다면 다음과 같이 할 수 있습니다.

const o = { apple:1, xochitl:2, balloon:3, guitar:4, xylophone:5};

Object.keys(o)
	.filter(prop => prop.match(/^x/)
	.forEach(prop => console.log(`${prop}:${o[prop]}`));

클래스와 인스턴스 생성

ES6 이전에 자바스크립트에서 클래스를 만드는 건 직관적이지도 않고 무척 번거로운 일이었습니다. ES6에서는 클래스를 만드는 간편한 새 문법을 도입했습니다.

class Car {
  constructor(){
  }
}

이제 Car클래스에 제조사와 모델 데이터, 변속 기능을 추가해 봅시다.

class Car {
  constructor(make, model){
    this.make = make;
    this.model = model;
    this.userGears = ['P', 'N', 'R', 'D'];
    this.userGear = this.userGears[0];
  }
  shift(gear) {
    if(this.userGears.indexOf(gear) < 0)
    	throw new Error(`Invalid gear:${gear}`);
    this.userGear = gear;
  }
}

여기서 this 키워드는 의도한 목적, 즉 메서드를 호출한 인스턴스를 가리키는 목적으로 쓰였습니다. this를 일종의 플레이스홀더로 생각해도 좋습니다. 클래스를 만들 때 사용한 this 키워드는 나중에 만들 인스턴스의 플레이스홀더입니다. 이제 이 클래스를 사용해봅시다!

const car1 = new Car('Tesla', model S');
const car1 = new Car('Mazda', 3i');
car1.shift('D');
car2.shift('R');

shift에 올바르지 않은 기어가 들어오면 막아주지만 car1.userGear = 'X'; 와 같이 직접 지정하는건 막을 수 없습니다. 대부분의 객체지향 언어에서는 특정한 매커니즘으로 막을 수 있지만 자바스크립트는 그런게 없고 이는 언어의 문제로 자주 비판을 받습니다. 하지만 아래의 코드로 어느정도 막을 수 있습니다.

class Car {
  constructor(make, model){
    this.make = make;
    this.model = model;
    this._userGears = ['P', 'N', 'R', 'D'];
    this._userGear = this._userGears[0];
  }
  
  get userGear() {return this._userGear;}
  set userGear(value){
    if(this._userGears.indexOf(value) < 0)
    	throw new Error(`Invalid gear:${value}`);
    this._userGear = value;
  }
  shift(gear) {this.userGear = gear;}
}

사실 이렇게 해도 car1._userGear ='X';로 접근하면 변경이됩니다. 진정한 제한이라기 보다는 '밑줄이 있는 프로퍼티에 접근 하려고 하네? 이건 실수다' 하면서 빨리 찾을 수 있는 방편이라고 봐야합니다. 프로퍼티를 꼭 보호해야 한다면 스코프를 이용해 보호하는 WeakMap 인스턴스를 사용할 수 있습니다.

클래스는 함수다

ES6에서 class 키워드를 도입하기 전까지, 클래스를 만든다는 것은 곧 클래스 생성자로 사용할 함수를 만든다는 의미였습니다. class 문법이 훨씬 더 직관적이고 단순하긴 하지만, 사실 class는 단축 문법일 뿐이며 자바스크립트의 클래스 자체가 바뀐 것은 아닙니다. 따라서 자바스크립트의 클래스 자체를 이해하는 것이 중요합니다.

 

클래스는 사실 함수일 뿐입니다. ES5에서 Car 클래스를 다음과 같이 만들었을 겁니다.

function Car(make, model){
    this.make = make;
    this.model = model;
    this._userGears = ['P', 'N', 'R', 'D'];
    this._userGear = this._userGears[0];
  }
  
  class Es6Car {}	//생성자는 의도적으로 생략합니다.
  function Es5Car {}

ES6에서 클래스가 바뀐 것은 아닙니다. 단지 간편한 새 문법이 생겼을 뿐입니다.

프로토타입

클래스의 인스턴스에서 사용할 수 있는 메서드라고 하면 그건 프로토타입 메서드를 말하는 겁니다. 예를 들면 Car.prototype.shift처럼 표기할 때가 많습니다. 최근에는 프로토타입 메서드를 #으로 표시하는 기법이 널리 쓰입니다. Car#shift처럼 말입니다.

 

모든 함수에는 prototype이라는 특별한 프로퍼티가 존재합니다. 일반적인 함수에서는 프로토타입을 사용할 일이 없지만, 객체 생성자로 동작하는 함수에서는 프로토타입이 대단히 중요합니다. 함수의 prototype 프로퍼티가 중요해지는 시점은 new 키워드로 새 인스턴스를 만들었을 때입니다. new 키워드로 만든 새 객체는 생성자의 prototype 프로퍼티에 접근할 수 있습니다. 객체 인스턴스는 생성자의 prototype 프로퍼티를 __proto__ 프로퍼티에 저장합니다.

 

프로토타입에서 중요한 것은 동적 디스패치라는 매커니즘입니다. 여기서 디스패치는 메서드 호출과 같은 의미입니다. 객체의 프로퍼티나 메서드에 접근하려 할 때 그런 프로퍼티나 메서드가 존재하지 않으면 자바스크립트는 객체의 프로토타입에서 해당 프로퍼티나 메서드를 찾습니다. 클래스의 인스턴스는 모두 같은 프로토타입을 공유하므로 프로토타입에 프로퍼티나 메서드가 있다면 해당 클래스 인스턴스는 모두 그 프로퍼티나 메서드에 접근할 수 있습니다.

 

*클래스의 프로토타입에서 데이터 프로퍼티를 수정하는 것은 일반적으로 권하지 않습니다. 모든 인스턴스가 그 프로퍼티의 값을 공유하기만 하지만, 인스턴스 중 하나에 그런 이름의 프로퍼티가 있다면 해당 인스턴스는 프로토타입에 있는 값이 아니라 인스턴스에 있는 값을 사용합니다. 인스턴스에 초깃값이 필요하다면 생성자에서 만드는 편이 낫습니다.

const car1 = new Car();
const car2 = new Car();

car1.shift === Car.prototype.shift;	//true
car1.shift('D');
//car1.shift('d');			//error
car1.userGear;				//'D'
car1.shift === car2.shift		//true;

car1.shift = function(gear) {this.userGear = gear.toUpperCase();}
car1.shift === Car.prototype;		//false
car1.shift === car2.shift;		//false
car1.shift('r');
car1.userGear;				//'R'

예제와 같이 car1.shift('d')에서 해당하는 값이 없으므로 에러를 뱉어 냈지만 3번째 블럭에서 shift를 재정의하고 사용하면 r 값이 R로 바껴서 저장된 것을 확인할 수 있다.

 

 

 

 

 

 

 

반응형
댓글
반응형
최근에 달린 댓글
글 보관함
Total
Today
Yesterday
최근에 올라온 글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31