JavaScript ES6+ 중급 (1) Symbol

염겨레·2021년 2월 25일
0

이 글은 정재남 개발자님의 인프런 강의인 JavaScript ES6+ 제대로 알아보기 중급편을 정리한 내용입니다.

1. 특성

  • primitive value로서 유일무이하고 고유한 존재이다.
  • private member에 대한 needs에서 탄생했다.
  • 기본적인 열거 대상에서 제외된다. (예: for ... in문)
  • 암묵적인 형변환이 불가하다.

2. 예제

예제를 통해 위 특성들을 파악해 보자. 먼저 Symbolnew 연산자 없이 생성하며( Symbol([string]) ), 문자열이 아닌 타입은 자동으로 toString 처리한다.

const a = Symbol('hello');
const b = Symbol('hello');
console.log(a == b, a === b);	// false false

위에서 변수 a, b에 같은 인자를 넣어 Symbol을 생성했지만, Symbol은 고유한 존재이므로, 두 변수에 담긴 값들은 서로 다른 값이다.

이번에는 Symbol을 형변환 시켜보자. 보통 문자와 숫자를 더하면 문자로 자동으로 형변환되지만, Symbol의 경우에는 에러를 발생시킨다. Symbol과 숫자를 더해도 마찬가지이다.

const a = Symbol('hi');
console.log(a + 1);
// Uncaught TypeError: Cannot convert a Symbol value to a number at ...

다음에는 object를 통해 Symbole이 열거 대상에서 어떻게 나타나는지 보자. 먼저 예제로 사용할 object를 만들어 보자.

const NAME = Symbol('이름');
const AGE = Symbol('나이');
const giraffe = {
	[NAME]: 'girin',
  	[AGE]: 20,
  	height: 300
}
const tiger = {
	[NAME]: 'horang',
  	[AGE]: 5,
  	height: 120
}
const chicken = {
	[NAME]: 'dak',
  	[AGE]: 1,
  	height: 20
}

유의할 점은 Symbol 자체를 key에 넣게 되면 해당 value에 접근할 수 없기 때문에 변수를 선언한 뒤 대괄호 표기법을 사용해야 한다. 예를 들어 아래와 같이 object를 만들면 Symbol('a')을 알고 있는 변수가 없으므로, value 1에 접근이 안된다(Reflect.ownKeys를 사용하면 가능).

const obj = {
  [Symbol('a')]: 1
};

다시 돌아와서 위의 객체에 대해 반복문을 돌려보면, Symbol 변수가 아닌 height만 값이 출력되는 것을 알 수 있다.

for(const key in giraffe) {
  console.log(key, giraffe[key]);
}
// height 300

이는 forEach에서도 마찬가지이다. Symbol로 된 key에 접근할 수 있는 방법은Object.getOwnPropertySymbols(giraffe).forEach() ...와 같은 방식이 있지만, 이는 Symbol이 아닌 key는 제외하게 된다. Symbol 구분을 하지 않고 접근하고자 한다면 Reflect.ownKeys를 사용할 수 있다.

3. private member

즉시 실행 함수로 된 예제를 먼저 보자.

const obj = (() => {
  const _privateMember1 = Symbol('private1');
  const _privateMember2 = Symbol('private2');
  return {
    [_privateMember1]: '외부에서 보이는데 접근이 마땅치 않은 놈',
    [_privateMember2]: '위와 마찬가지인 놈',
    publicMember: '접근이 수월한 놈'
  }
})()

console.log(obj);
console.log(obj[Symbol('private1')]);
//{
//  publicMember: "접근이 수월한 놈", 
//  Symbol(private1): "외부에서 보이는데 접근이 마땅치 않은 놈", 
//  Symbol(private2): "위와 마찬가지인 놈"
//}
// undefined

즉시 실행 함수 내부에 선언된 _privateMember1_privateMember2는 블록 스코프에 갇혀서 블록스코프 외부에서는 접근을 할 수 없다. 물론 위에서 소개한 Object.getOwnPropertySymbolsReflect.ownKeys를 사용하면 접근이 가능하다. 정재남님에 따르면 이는 private member를 흉내낸 정도라고 한다.

추후에 private member를 선언하는 방식이 공식적으로 나온다고 하는데, 이미 나와있는지 한번 찾아봐야겠다.🧐

3. 공유 심볼 - Symbol.for

Symbol에는 전역 공간에서 public member로서 공유심볼이라 놈이 있다! 이 놈은 어디서 만들든 스코프에 갇히지 않고 공유된다. 선언 방법은 단순히 Symbol.for([string])로 변수를 만들면 된다.

const a = Symbol.for('hello');
const b = Symbol.for('hello');
console.log(a === b)	// true

기존 Symbol과 달리 인자를 같게 하는 Symbol.for 두 변수를 비교했을 때 true가 나오게 된다. 이는 Symbol.for를 관리하는 전역 공간이 존재함을 알 수 있다.

const obj = (() => {
  const GREETING = Symbol.for('hi');
  return {
  	[GREETING]: '공유가 됩니다'
  }
})();
console.log(obj[Symbol.for('hi')]); // '공유가 됩니다'

또한 기존 Symbol과 달리 Symbol.for에 쓰인 문자열만 알고 있으면 객체 프로퍼티에 접근할 수도 있다.

4. 표준 심볼

Symbol에는 개발자가 자기 구미에 맞게 본래 기능을 다르게 구현할 수 있도록 하는 property들을 제공한다. 이를 표준 심볼이라 한다. 그 중에는 Symbol.iterator, Symbol.match, Symbol.replace, Symbol.split, Symbol.toStringTag 등이 있다. 이 중에서 문자열을 나누는 조건을 설정할 수 있는 Symbol.split을 예로 들어 보겠다.

const str = '매일 매일 주말이었으면 좋겠어';

console.log(str.split(' '));
// ["매일", "매일", "주말이었으면", "좋겠어"]

String.prototype[Symbol.split] = function(string) {
  let result = '';
  let residue = string;
  let index = 0;
  
  do {
    index = residue.indexOf(this);
    if(index <= -1) {	// this가 없으면 멈춘다
      break;
    }
    result += residue.substr(0, index) + ' 아아 ';
   	residue = residue.substr(index + this.length);
  } while(true)
  result += residue;
  
  return result;
}

console.log(str.split(' '));
// 매일 아아 매일 아아 주말이었으면 아아 좋겠어

본래 문자열의 split 메소드는 delimiter를 기준으로 문자열을 구분한 요소들을 array로 return 한다. 하지만 위에서는 Symbol.split를 활용해 기존 split 메소드의 작동 방식을 재정의하였다. delimiter 위치에 아아 문자를 넣어 array가 아닌 전체 문자열로 반환하도록 바꿀 수도 있다.

Symbol은 언제 어떻게 써야할지 아직 크게 와닿지 않는다. 나중에 점점 필요한 순간이 오겠지?😳

profile
차근차근 나아가는 시나브로 개발자

0개의 댓글