[Javascript] 심볼(Symbol) 사용 기초
심볼(Symbol)은 ES6에서 추가된 데이터 타입입니다.
문자열 값을 기초로 유일한 식별자를 생성하고 싶을 때 심볼을 사용합니다.
사용 빈도가 높지는 않지만, 용도에 맞게 사용하면 강력한 효과를 발휘합니다. 셋(Set)과 유사한 특징들을 여러 가지 가지고 있습니다.
1. 심볼의 생성
심볼은 다음과 같이 생성합니다.
반환받은 값은 유니크한 식별자 값입니다. 물론 식별할 대상이 없으므로 이것만으로 심볼의 어떤 기능을 하는 것은 아니며, 문자열 인자를 넘겨서 심볼 값을 생성하면 비로소 해당 문자열을 가리키는 유일한 식별자로서의 기능을 하게 됩니다.
const s = Symbol();
심볼을 생성할 때 인자로 넘기는 문자열은 "이름"입니다.
그리고 생성되어 반환된 심볼 값은 "키"가 됩니다.
같은 이름으로 생성한 2개의 심볼은 서로 다른 객체가 됩니다. 생성될 때마다 유일한 키 값을 반환하기 때문에 같은 이름으로 인한 중복 문제를 회피할 수 있습니다.
let userid = Symbol("userid"); // 새 심볼 생성
let userid2 = Symbol("userid"); // 같은 이름으로 심볼 생성
console.log( userid === userid2 ); // false
생성된 심볼의 이름은 description 속성으로 알 수 있습니다.
let userid = Symbol("userid");
console.log( userid.description ) // userid
심볼은 프리미티브 데이터 타입이기 때문에 심볼의 타입을 확인하면 "symbol"을 반환합니다.
console.log(typeof userid); // symbol
객체를 생성하는 new 연사자로는 심볼을 생성할 수 없습니다. constructor 에러를 발생시킵니다. 키 값으로 표현되는 프리미티브 데이터 타입으로 인식되어야 하기 때문에 객체 래퍼로 심볼 키를 감싸는 것은 불가능합니다.
const color1 = Symbol('red');
console.log(typeof color1); // symbol
const color2 = new Symbol('blue'); // Symbol is not a constructor
console.log(typeof color2);
2. 심볼의 문자열 변환
심볼은 그 자체로 데이터 타입이기 때문에 문자열이 아닙니다.
그리고 다른 타입과 결합할 때 자동으로 형 변환이 되지 않고 타입 에러를 발생시킵니다. 모든 것을 자동으로 알아서? 해주는 자바스크립트의 특징과 배치되지만 어쨌든 자동으로 형변환이 되지 않습니다.
명시적으로 문자열을 인자로 받는 전역 함수인 alert()에 심볼을 넣으면 타입 에러가 발생합니다.
const color1 = Symbol('red');
alert(color1); // Uncaught TypeError: Cannot convert a Symbol value to a string
반드시 명시적으로 심볼을 toString() 메서드로 문자열 형태로 변환을 해야 다른 데이터 타입과 혼용을 할 수 있습니다.
const color1 = Symbol('red');
alert(color1.toString());
3. 심볼의 감춤 특성
심볼은 실제 값을 감추고 값을 대변하는 유니크 키로 표현을 하는 데이터 형입니다.
그 자체로 데이터를 감추는 특징을 가지고 있으며, 자바스크립트의 다른 구분 기능에서도 심볼의 이 감춤 특성을 지원합니다. 다만, 일부 기능에서 제한적으로 지원하는 것이므로 만능은 아닙니다.
공통으로 사용하는 원격접속 정보를 객체로 만들어서 공유해서 사용하면서 모듈 단위로 기능을 나누어서 구현할 때, 다른 모듈을 작성하는 개발자가 실수로 apiconfig.ip 값을 변경했다면, 원격에서 정보를 가져오는 모듈들에서는 모두 에러가 발생하게 됩니다.
이런 문제를 피하기 위해 다른 다른 모듈을 작성하는 개발자는 모르는 심볼로 ip 정보를 담은 속성을 추가해서 이 심볼 값으로 접속 정보를 사용하면 실수로 설정 정보가 변경되더라도 에러를 최소화할 수 있습니다.
let apiconfig = {
ip: "192.168.0.3",
name: "API서버",
port: 8081,
id: 'api_user',
pw: 'elqlwjqthr'
}
let symip = Symbol(apiconfig.ip)
apiconfig[symip] = apiconfig.ip
console.log(apiconfig[symip])
심볼은 루프문으로 순회를 할 때, 순회에서 배제되는 특징이 있습니다.
앞서 만든 환경설정 객체를 객체를 순회하는 for~in 루프문으로 순회를 해서 키를 콘솔에 표시하면 다음과 같이 심볼은 제외가 됩니다.
for(let key in apiconfig){
console.log(key)
}
객체에서 키값(들)만 가져오는 Object.key() 메서드에서도 심볼은 제외된 키 목록이 반환 됩니다.
4. 전역 심볼과 심볼의 공유
이름이 같더라도 모두 별개의 심볼로 생성하는 (일반)심볼과 달리 같은 이름으로 만들어진 심볼은 모두 같은 객체를 가리키도록 할 필요가 있을 때 사용합니다. 여러 개의 심볼이 하나의 이름을 공유해서 사용하게 됩니다.
전역 심볼은 전역 심볼 레지스트리로 별도 관리를 하며, 이름이 같은 전역 심볼은 한 개만 생성됩니다. 같은 이름으로 호출을 하면 같은 심볼이 나오게 됩니다.
전역 심볼은 별도의 전용 정적 메서드인 for()로 심볼을 만들고, 또 심볼을 받환 받습니다.
for() 메서드로 심볼을 등록하려고 하면 항상 전역 심볼 레지스트리를 검색해서 이미 등록된 전역 심볼이 있는지를 검색합니다. 심볼이 없으면 새 심볼을 등록하고, 이미 등록된 전역 심볼이 있으면 동일한 심볼 키를 반환합니다.
let userid = Symbol.for("userid"); // 새 심볼 생성
let userid2 = Symbol.for("userid"); // 전역 레지스트리의 심볼을 반환
console.log( userid === userid2 ); // true
전역 레지스트리에 등록한 전역 심볼의 이름을 가져올 때도 전용의 정적 메서드인 keyFor()를 사용해야 합니다.
전역 심볼이 아닌 일반 심볼의 이름은 description 속성으로 가져와야 합니다.
let username = Symbol.for("라이언");
console.log( Symbol.keyFor(username) ); // 라이언
전역 심볼과 일반 심볼은 완전히 분리되어 있으며 상호 교차 동작하지 않습니다. 생성한 일반 심볼의 이름을 전역 메서드인 keyFor()로 찾으면 undefined가 반환됩니다.
let username = Symbol("라이언");
console.log( Symbol.keyFor(username) ); // undefined