Symbols과 Symbol 프로퍼티
Symbol은 ECMAScript 6에서 도입된 Primitive 타입으로, 기존의 string
, number
, boolean
, null
, undefined
와 같은 타입입니다. Symbol은 객체의 Private 멤버를 생성하는 방법으로 시작되었는데, JavaScript 개발자들이 오랫동안 원했던 기능입니다. Symbol 이전에는 이름이 있는 프로퍼티는 이름의 모호함에 관계없이 쉽게 액세스할 수 있었고 Private name 기능은 개발자가 문자열이 아닌 프로퍼티 이름을 만들 수 있도록 하기위한 것입니다. 그리고 일반적인 방법으로 Private name에 대한 탐지는 작동하지 않습니다.
Private name에 대한 제안은 마침내 ECMAScript 6 Symbol로 진화했습니다. 이장에서는 Symbol을 효과적으로 사용하는 법을 가르쳐줄 것입니다. 구현 세부 사항은 동일하게 유지되었지만 (즉, 프로퍼티 이름에 문자열이 아닌 값을 추가 한 경우) Privacy에 대한 부분은 삭제되었습니다. 대신 Symbol 프로퍼티는 다른 객체 프로퍼티와 구분되어 분류됩니다.
Symbol 생성하기
Symbol은 JavaScript Primitive 중, boolean은 true
, number는 42
와 같은 리터럴이 없는 유일한 타입니다. 아래 예제와 같이 전역Symbol
함수를 사용하여 Symbol을 만들 수 있습니다.
|
|
위 예제의 Symbol firstName
은 person
객체에 새로운 프로퍼티를 할당하기 위해 만들어지고 사용됩니다. 이 Symbol은 동일한 프로퍼티에 액세스할 때마다 사용 해야합니다. Symbol 변수에 적절한 이름을 부여하는 것은 좋은 생각입니다. Symbol이 무엇을 나타내는지 쉽게 알 수 있기 때문입니다.
Symbol은 Primitive이기 때문에
new Symbol()
을 호출하면 오류가 발생합니다.Symbol
의 인스턴스를new Object (yourSymbol)
을 통해서 만들 수도 있습니다. 하지만 이 기능이 그렇게 유용하지 않습니다.
Symbol
함수는 Symbol에 대한 설명을 위해 Optional 파라미터도 받아들입니다. 설명 자체는 프로퍼티에 액세스하는데 사용할 수 없지만 디버깅 목적으로 사용될 수 있습니다.
|
|
Symbol의 설명은 내부적으로 [[Description]]
프로퍼티에 저장됩니다. 이 프로퍼티는 Symbol의 toString()
메서드가 명시적으로 또는 암시적으로 호출될 때마다 읽혀집니다. firstName
Symbol의 toString()
메서드는 이 예제에서 console.log()
에 의해 암시적으로 호출되므로 설명이 log에 출력됩니다. 코드에서 직접 [[Description]]
에 액세스할 수 없습니다. 필자는 항상 Symbol을 읽고 디버깅하기 쉽도록 설명을 제공할 것을 권장합니다.
Symbol 식별하기
Symbol은 Primitive이기 때문에 typeof
연산자를 사용하여 변수에 Symbol이 포함되어 있는지 확인할 수 있습니다. ECMAScript 6은 typeof
를 확장하여 Symbol에 사용될 때 "symbol"
을 반환하도록 합니다.
|
|
변수가 Symbol인지 여부를 결정하는 다른 간접적인 방법이 있지만 typeof
연산자가 가장 정확하고 선호되는 기술입니다.
Symbol 사용하기
계산된 프로퍼티 이름을 사용하는 곳이면 어디에서나 Symbol을 사용할 수 있습니다. 이장에서 Symbol 과 함께 사용된 괄호 표기법을 이미 보았지만 아래의 호출과 같이 Object.defineProperty()
와 Object.defineProperties()
뿐만 아니라 계산된 객체 리터럴 프로퍼티 이름에서도 Symbol을 사용할 수 있습니다.
|
|
위 예제는 먼저 계산된 객체 리터럴 프로퍼티를 사용하여 firstName
Symbol 프로퍼티를 만듭니다. 그 다음 라인은 프로퍼티를 읽기 전용으로 설정합니다. 그후에, Object.defineProperties()
메서드를 사용하여 읽기 전용 lastName
Symbol 프로퍼티를 생성합니다. 계산된 객체 리터럴 프로퍼티가 다시 한번 사용되지만 Object.defineProperties()
호출의 두 번째 파라미터의 일부입니다.
Symbol은 계산된 프로퍼티 이름이 허용되는 곳이면 어디에서나 사용할 수 있지만, 효과적으로 사용할 수 있도록 이들 Symbol*을 다른 코드 사이에서 공유할 수있는 시스템이 필요합니다.
Symbol 공유하기
여러분은 코드의 다른 부분에서 같은 Symbol을 사용하기 원할 수도 있습니다. 예를 들어, 응용 프로그램에서 고유 식별자를 나타내기 위해 동일한 Symbol 프로퍼티를 사용해야하는 두개의 다른 객체 타입이 있다고 가정하겠습니다. 파일이나 큰 코드베이스에서 Symbol을 추적하는 것은 어렵고 오류가 발생할 수 있습니다. 그래서 ECMAScript 6는 어느 시점에서나 액세스할 수 있는 전역 Symbol 레지스트리를 제공합니다.
공유할 Symbol을 생성하려면 Symbol()
메서드를 호출하는 대신 Symbol.for()
메서드를 사용합니다. Symbol.for()
메서드는 여러분이 생성하고자하는 Symbol을 위한 문자열 식별자로 단일 파라미터를 받아들입니다. 그리고 이 파라미터는 Symbol의 설명으로도 사용됩니다.
|
|
Symbol.for()
메서드는 먼저 전역 Symbol 레지스트리를 검색하여 “uid” 키가 있는 Symbol이 있는지 확인합니다. 만약 Symbol이 있다면 이 메서드는 기존의 Symbol을 리턴합니다. 그런데 만약 없다면 새로운 Symbol을 생성하고 지정된 키를 사용하여 전역 Symbol 레지스트리에 등록합니다. 그리고 새로운 Symbol을 리턴합니다. 즉, 같은 키를 사용하는 Symbol.for()
에 대한 후속 호출은 다음과 같이 동일한 Symbol을 반환합니다.
|
|
위 예제에서 uid
와 uid2
는 같은 Symbol을 사용하고 있습니다. 그래서 서로 바꿔서 사용할 수도 있습니다. Symbol.for()
에 대한 첫 번째 호출은 Symbol을 생성하고 두 번째 호출은 전역 Symbol 저장소에서 Symbol을 가져옵니다.
공유 Symbol의 또 다른 독특한 부분은 Symbol.keyFor()
메서드를 호출하여 전역 Symbol 레지스트리에서 Symbol과 연관된 키를 검색할 수 있다는 것입니다.
|
|
uid
와 uid2
는 모두 “uid”키를 반환합니다. Symbol uid3
은 전역 Symbol 레지스트리에 존재하지 않으므로, 관련된 키가 없으며Symbol.keyFor()
는 undefined
를 리턴합니다.
글로벌 Symbol 레지스트리는 글로벌 Scope와 같은 공유 환경입니다. 이는 해당 환경에 이미 존재하거나 존재하지 않는 것에 대해 가정할 수 없음을 의미합니다. 서드파티 컴포넌트를 사용할 때 충돌을 일으킬 가능성을 줄이기 위해 Symbol 키의 네임 스페이스를 사용할 수 있습니다. 예를 들어,jQuery
코드는 "jquery.element"
또는 유사한 방법으로 모든 키에 "jquery."
접두사를 사용할 수 있습니다.
Symbol 강제 변환 (Coercion)
타입 강제 변환은 JavaScript의 중요한 부분이며, 한 데이터 타입을 다른 데이터 타입으로 강제 변환하는 것은 많은 유연성이 있습니다. 그러나 Symbol은 타입 강제 변환에 있어서는 꽤 융통성이 없습니다. 왜냐하면 다른 타입은 Symbol과 논리적으로 동등하지 않기 때문입니다. 특히 Symbol은 string
이나 number
로 강제 변환 될 수 없으므로 실수로 Symbol로 예상되는 프로퍼티에 다른 타입을 사용할 수 없습니다.
이 장에서는 console.log()
를 사용하여 Symbol에 대한 결과를 보여줍니다. console.log()
가 Symbol에 대해 String()
을 호출하여 유용한 출력을 생성합니다. String()
을 직접 사용해도 같은 결과를 얻을 수 있습니다.
|
|
String()
함수는 uid.toString()
을 호출고 Symbol은 설명 문자열을 반환합니다. 그러나 Symbol을 문자열과 직접 연결하려고 하면 오류가 발생합니다.
|
|
uid
와 빈 문자열을 연결하려면 uid
가 먼저 string
으로 강제 변환 되어야합니다. JavaScript는 Symbol의 강제 변환이 발견되면 에러를 발생시킵니다. 비슷하게, Symbol을 number
로 강제 변환할 수도 없습니다. 모든 수학 연산자가 Symbol에 적용될 때 오류를 발생시킵니다.
|
|
이 예제는 Symbol 변수를 1
로 나눕니다. 사용된 수학 연산자에 관계없이 모두 오류가 발생합니다. 하지만 논리 연산자는 JavaScript의 다른 비어 있지 않은 값과 마찬가지로 true와 동일한 것으로 간주되기 때문에 오류가 발생하지 않습니다.
Symbol 프로퍼티 검색하기
Object.keys()
및 Object.getOwnPropertyNames()
메서드는 객체의 모든 프로퍼티 이름을 검색할 수 있습니다. 전자의 경우 열거 가능한 모든 프로퍼티 이름을 반환하고 후자는 열거 가능 여부에 관계없이 모든 프로퍼티를 반환합니다. 그러나 두 메서드 모두 ECMAScript 5 기능을 유지하기 위해 Symbol 프로퍼티를 반환하지 않습니다. 대신, 객체로부터 Symbol 프로퍼티를 검색할 수 있도록하기 위해 Object.getOwnPropertySymbols()
메서드가 ECMAScript 6에 추가되었습니다.
Object.getOwnPropertySymbols()
의 리턴 값은 자신의 Symbol 프로퍼티의 Array입니다.
|
|
이 코드에서 object
는 uid
라는 단일 Symbol 프로퍼티를 가지고 있습니다. Object.getOwnPropertySymbols()
에서 반환된 Array은 Symbol을 포함하는 Array입니다.
모든 객체는 0개
의 자체 Symbol 프로퍼티로 시작하지만 프로토타입에서 Symbol 프로퍼티를 상속받을 수 있습니다. ECMAScript 6는 Well-known Symbol이라고 불리는 미리 구현된 여러 프로퍼티를 정의합니다.
Well-Known Symbol을 이용한 내부 Operation 표현
ECMAScript 5의 핵심 테마는 JavaScript의 “magic” 부분 중 일부를 노출하고 정의하는 것이 었습니다. 이부분은 개발자가 Emulate할 수 없는 부분이었습니다. ECMAScript 6는 이전 버전 언어의 내부 논리를 더 많이 드러냄으로써 그 전통을 이어 나갔습니다. 주로 특정 객체의 기본 동작을 정의하기 위해 Symbol 프로토 타입 프로퍼티를 사용합니다.
ECMAScript 6에는 이전에 내부 전용 작업으로 간주되었던 JavaScript의 일반적인 동작을 나타내는 Well-known Symbol이라는 미리 정의된 Symbol이 있습니다. 각각의 Well-known Symbol은 Symbol.create
와 같이 Symbol
객체의 프로퍼티로 표현됩니다.
Well-known Symbol은 아래와 같습니다.
Symbol.hasInstance
- 객체의 상속을 결정하기 위해instanceof
가 사용하는 메서드.Symbol.isConcatSpreadable
- 컬렉션이Array.prototype.concat()
에 파라미터로 전달되면Array.prototype.concat()
이 컬렉션의 요소를 flat하게 해야한다는 것을 나타내는 boolean 값.Symbol.iterator
- Iterator를 반환하는 메서드. (Iterator는 7 장에서 다룹니다.)Symbol.match
- 문자열을 비교하기 위해String.prototype.match()
에 의해 사용되는 메서드.Symbol.replace
-String.prototype.replace()
가 substring을 치환하기 위해서 사용하는 메서드.Symbol.search
-String.prototype.search()
가 substring의 위치를 찾아 내기 위해서 사용하는 메서드.Symbol.species
- 파생된(Derived) 객체를 만들기위한 생성자. (Derived 객체에 대해서는 8 장에서 다룹니다.)Symbol.split
- 문자열을 분할하기 위해String.prototype.split()
에서 사용하는 메서드.Symbol.toPrimitive
- 객체의 Primitive 값 표현을 반환하는 메서드.Symbol.toStringTag
- Object 설명을 생성하기 위해서Object.prototype.toString()
에 의해 사용되는 문자열.Symbol.unscopables
-with
문에 포함되어서는 안되는 프로퍼티가 객체 프로퍼티의 이름인 객체.
흔히 사용되는 Well-known Symbol은 다음 절에서 논의하고 나머지는 책의 나머지 전체에서 논의합니다.
정의된 메서드를 Well-known Symbol로 덮어 쓰는 것은 내부 객체를 외부 객체로 바꾸는 것입니다. 결과적으로 코드에 실제적인 영향은 없으며, 객체 사양을 설명하는 방식이 변경됩니다.
Symbol.hasInstance 프로퍼티
모든 함수는 주어진 객체가 그 함수의 인스턴스인지 아닌지를 결정하는 Symbol.hasInstance
메서드를 가지고 있습니다. 이 메서드는 Function.prototype
에 정의되어 모든 함수가 instanceof
프로퍼티에 대한 기본 동작을 상속받으며 메서드는 쓰기가 불가능(nonwritable)하고 설정이 불가능(nonconfigurable)하고 열거가 불가능(nonenumerable)하여 실수로 덮어 쓸수 없습니다.
Symbol.hasInstance
메서드는 하나의 파라미터, 즉 확인할 값만 받아들입니다. 전달된 값이 함수의 인스턴스이면 true를 반환합니다. Symbol.hasInstance
가 어떻게 작동하는지 이해하기 위해 다음 코드를 살펴보겠습니다.
|
|
이 코드는 다음과 같습니다.
|
|
ECMAScript 6은 근본적으로 instanceof
연산자를 메서드 호출의 축약 구문으로 재정의했습니다. 메서드 호출로 변경되었기 때문에 여러분이 실제로 instanceof
가 어떻게 작동하는지 원하는데로 바꿀 수 있습니다.
예를 들어, 객체를 인스턴스로 요구하지 않는 함수를 정의한다고 가정합니다. Symbol.hasInstance
의 반환 값을 false
로 하드 코딩하면 다음과 같이할 수 있습니다.
|
|
쓰기가 불가능한(nonwritable) 프로퍼티를 덮어 쓰려면 Object.defineProperty()
를 사용 해야합니다. 그래서 이 예제는 그 메서드를 사용하여Symbol.hasInstance
메서드를 새로운 함수로 덮어 씁니다. 새로운 함수는 항상 false
를 반환하기 때문에 obj
가 실제로 MyObject
클래스의 인스턴스이더라도 instanceof
연산자는 Object.defineProperty()
호출 후에 false
를 반환합니다.
물론 여러분은 값을 검사하고 임의의 조건을 기반으로 값을 인스턴스로 간주해야하는지 여부를 결정할 수도 있습니다. 예를 들어, 1과 100 사이의 값을 가진 숫자는 특별한 number 타입의 인스턴스로 간주됩니다. 이 동작을 수행하기 위해 다음과 같이 코드를 작성할 수 있습니다.
|
|
이 코드는 값이 Number
의 인스턴스이고 또한 1과 100 사이의 값을 가지면 true
를 리턴하는 Symbol.hasInstance
메서드를 정의합니다. 따라서 SpecialNumber
함수와 two
변수 사이에 직접 정의된 관계가 없더라도 SpecialNumber
는 two
를 인스턴스로 요구합니다. instanceof
의 왼쪽 피연산자는 Symbol.hasInstance
호출을 트리거하는 객체여야합니다. 왜냐하면 객체가 아니면 instanceof
가 항상 단순히 false
를 반환하도록 해야하기 때문입니다.
또한
Date
와Error
함수와 같은 모든 내장 함수에 대한 기본Symbol.hasInstance
프로퍼티을 덮어 쓸 수 있습니다. 그러나 코드에 미치는 영향이 예기치 않게 혼동될 수 있기 때문에 권장하지 않습니다. 자신의 함수에 대해서만Symbol.hasInstance
를 덮어 쓰는 것이 좋은 생각입니다.
Symbol.isConcatSpreadable Symbol
JavaScript Array는 두개의 Array을 연결하기 위해 concat()
메서드를 가지고 있습니다. 다음 예제를 살펴 보겠습니다.
|
|
이 코드는 새로운 Array을 colors1
의 끝에 연결하여 colors2
를 생성합니다. 생성된 Array는 두 Array의 모든 항목을 갖는 Array입니다. 그러나 concat()
메서드는 Array이 아닌 파라미터도 받아 들일 수 있으며, 이 경우 그 파라미터는 단순히 Array의 끝에 추가됩니다.
|
|
여기에서 여분의 파라미터 "brown"
은 concat()
에 전달되고 colors2
Array의 다섯 번째 항목이됩니다. Array 파라미터가 문자열 파라미터와 다르게 취급되는 이유는 무엇일까요? JavaScript 사양에서는 Array가 자동으로 개별 항목으로 분리되고 다른 타입은 자동으로 분리되지 않는다고 말합니다. ECMAScript 6 이전에는 이 동작을 조정할 방법이 없었습니다.
Symbol.isConcatSpreadable
프로퍼티는 객체가 length 프로퍼티와 숫자 키를 가지고 있으며 숫자 프로퍼티 값이 concat()
호출의 결과에 개별적으로 추가되어야 함을 나타내는 boolean 값입니다. 다른 Well-known Symbol과 달리 이 Symbol 프로퍼티는 기본적으로 표준 객체에 나타나지 않습니다. 대신 Symbol은 특정 타입의 객체에서 concat()
이 어떻게 동작 하는지를 보완하는 방법으로 사용할 수 있어 기본 동작을 효과적으로 만듭니다. 다음과 같이 Array가 concat()
호출에서와 같이 동작하도록 모든 타입을 정의할 수 있습니다.
|
|
이 예제의 collection
객체는 length 프로퍼티와 두 개의 숫자 키를 가지고 있어 Array 처럼 보이도록 설정되어 있습니다. Symbol.isConcatSpreadable
프로퍼티는 true
로 설정되어 프로퍼티 값이 Array의 개별 항목으로 추가되어야 함을 나타냅니다. collection
이 concat()
메서드에 전달 될 때, 결과 Array는 "Hello"
와 "world"
가 분리되어 "Hi"
엘리먼트 다음에 나타납니다.
concat()
호출로 항목이 분리되지 않도록 Array 서브 클래스에서Symbol.isConcatSpreadable
을false
로 설정할 수도 있습니다. 서브 클래스는 8 장에서 논의합니다.
Symbol.match, Symbol.replace, Symbol.search, 그리고 Symbol.split Symbol들
문자열과 정규 표현식은 JavaScript에서 밀접한 관계가 있습니다. 특히 문자열 타입에는 정규 표현식을 파라미터로 사용하는 여러 가지 메서드가 있습니다.
match(regex)
- 주어진 문자열이 정규 표현식과 일치하는지 여부를 판별합니다.replace(regex, replacement)
- 정규 표현식에 매치된 문자열을replacement
로 대체합니다.search(regex)
- 문자열 내에서 정규 표현식과 일치하는 문자열을 찾습니다.split(regex)
- 문자열을 정규 표현식과 일치하는 문자열 Array로 나눕니다.
ECMAScript 6 이전에는 이러한 메서드가 정규 표현식과 상호 작용하는 방식이 개발자에게 숨겨져 있어 개발자가 정의한 객체에 정규 표현식을 사용할 수 없었습니다. ECMAScript 6은 이러한 네 가지 메서드에 해당하는 네 개의 Symbol을 정의하여 네이티브 동작을 RegExp
내장 객체에 효과적으로 아웃소싱할 수 있습니다.
Symbol.match
, Symbol.replace
, Symbol.search
및 Symbol.split
Symbol은 정규 표현식 파라미터에 대한 match()
,replace()
, search()
, split ()
메서드 각각에 대한 첫 번째 파라미터에서 호출되어야 합니다. 네 개의 Symbol 프로퍼티는 RegExp.prototype
에 문자열 메서드가 사용해야하는 기본 구현으로 정의됩니다.
이것을 알면 정규 표현식과 비슷한 방식으로 문자열 메서드에 사용할 객체를 만들 수 있습니다. 그렇게하기 위해 코드에서 다음과 같은 Symbol 함수를 사용할 수 있습니다.
Symbol.match
- 문자열 파라미터를 받아들이고 일치하는 Array를 반환하는 함수. 일치하는 것이 없으면null
입니다.Symbol.replace
- 문자열 파라미터와 대체 문자열을 받아들이고 문자열을 반환하는 함수입니다.Symbol.search
- 문자열 파라미터를 받아들이고 일치 항목의 숫자 인덱스를 반환하는 함수입니다. 일치하는 항목이 없으면 -1을 반환합니다.Symbol.split
- 문자열 파라미터를 받아들이고 일치하는 문자열을 포함하는 Array를 반환하는 함수입니다.
객체에 이러한 프로퍼티를 정의할 수 있으므로 정규 표현식 없이 패턴 일치를 구현하는 객체를 만들고 정규 표현식을 필요로하는 메서드에서 사용할 수 있습니다. 다음은 이러한 Symbol이 실제로 작동하는 것을 보여주는 예입니다.
|
|
hasLengthOf10
객체는 문자열 길이가 정확히 10일 때마다 일치하는 정규 표현식처럼 작동합니다. hasLengthOf10
에있는 네 개의 메서드는 각각 적절한 Symbol 을 사용하여 구현되고 두 문자열에 상응하는 메서드가 호출됩니다. 첫 번째 문자열인 message1
은 11 개의 문자를 가지므로 일치하지 않습니다. 두 번째 문자열 message2
는 10 개의 문자를 가지므로 일치합니다. 정규식이 아니더라도 hasLengthOf10
은 각 문자열 메서드에 전달되고 추가 메서드로 인해 올바르게 사용됩니다.
이것은 간단한 예제지만 보다 복잡한 매칭를 수행하는 기능도 가능합니다. 그리고 현재 정규 표현식으로 가능했던 것보다 커스텀 패턴 매처에 대한 많은 가능성을 열어줍니다.
Symbol.toPrimitive 메서드
JavaScript는 특정 작업을 적용할 때 객체를 Primitive 값으로 암시적으로 변환하려고 시도합니다. 예를 들어 문자열을 double equals (==
) 연산자를 사용하여 객체와 비교하면 비교하기 전에 객체가 Primitive 값으로 변환됩니다. 정확하게 어떤 Primitive 값이 사용되어야 하는가는 이전에는 내부 연산 이었지만, ECMAScript 6에서는 Symbol.toPrimitive
메서드를 통해 그 값을 밖으로 노출시킵니다.
Symbol.toPrimitive
메서드는 각 표준 타입의 프로토 타입에 정의되어 있으며, 객체가 Primitive로 변환 될 때 어떻게되어야 하는지를 규정합니다. Primitive 변환이 필요할 때, Symbol.toPrimitive
는 하나의 파라미터를 가지고 호출되며, 명세서에서 hint
라고 설명합니다. hint
파라미터는 세 개의 문자열 값 중 하나입니다. hint
가 "number"
이면 Symbol.toPrimitive
는 number를 반환해야합니다. hint
가 "string"
이면 string이 반환되어야하고, “default”이면 해당 연산은 그 타입에 대한 선호도가 없습니다.
대부분의 표준 객체에서 number 모드는 우선 순위에 따라 다음과 같은 동작을합니다.
valueOf()
메서드를 호출해, 결과가 Primitive인 경우는 그 값을 돌려 준다.- 그렇지 않은 경우는,
toString()
메서드를 호출해, 결과가 Primitive인 경우는 그 값을 돌려 준다. - 그렇지 않으면 오류를 발생시킵니다.
마찬가지로 대부분의 표준 객체에서 string 모드의 동작은 다음과 같은 우선 순위를 갖습니다.
toString()
메서드를 호출해, 결과가 Primitive인 경우는 그 값을 돌려 준다.- 그렇지 않은 경우는,
valueOf()
메서드를 호출해, 결과가 Primitive인 경우는 그 값을 돌려 준다. - 그렇지 않으면 오류를 발생시킵니다.
대부분의 경우 표준 객체는 Default 모드를 number 모드와 동일하게 취급합니다 (Default 모드를 string 모드와 동일하게 취급하는 Date
제외). Symbol.toPrimitive
메서드를 정의함으로써, 여러분은 이 Default 모드를 오버라이드 할 수 있습니다.
Default 모드는
==
연산자,+
연산자 및Date
생성자에 단일 파라미터를 전달할 때만 사용됩니다. 대부분의 작업에는 string 또는 number 모드가 사용됩니다.
Default 변환 행동을 무시하려면 아래 예제 처럼 Symbol.toPrimitive
를 사용하고 함수를 값으로 지정하십시오.
|
|
위의 예제는 Temperature
생성자를 정의하고 프로토타입에 대한 기본 Symbol.toPrimitive
메서드를 오버라이드합니다. hint
파라미터가string
모드,number
모드 또는 Default 모드 (JavaScript 엔진에 의해 hint
파라미터로 채워짐)를 나타내는지에 따라 다른값이 리턴됩니다. string
모드에서, Symbol.toPrimitive
메서드는 유니 코드 Symbol로 온도를 반환합니다. number
모드에서는 숫자값만 반환하고, Default 모드에서는 숫자 뒤에 "degrees"
라는 단어를 추가합니다.
각각의 로그문은 다른 hint
파라미터를 트리거합니다. +
연산자는 hint
를 "default"
로 설정함으로써 Default 모드를 트리거하고, /
연산자는 hint
를 "number"
로 설정함으로써 number
모드를 트리거하고, String()
함수는 hint
를 "string"
으로 설정함으로써 string
모드로 트리거합니다. 세 가지 모드 모두에 대해 다른 값을 반환하는 것이 가능합니다. 하지만 Default 모드를 string
또는 number
모드와 동일하게 설정하는 것이 훨씬 더 일반적입니다.
Symbol.toStringTag Symbol
JavaScript에서 가장 흥미로운 문제중 하나는 여러 글로벌 실행환경을 사용할 수 있다는 것입니다. 이는 페이지에 iframe이 포함될 때 웹 브라우저에서 발생합니다. 페이지와 iframe에는 각각 자체 실행 환경이 있기 때문입니다. 대부분의 경우 데이터를 주고받을 수 있으므로 문제가되지 않습니다. 하지만 문제는 객체가 다른 객체에 전달된 후 처리할 객체의 타입을 식별하려고 할 때 발생합니다.
이 문제의 일반적인 예는 iframe의 Array의 포함 페이지로 또는 그 반대로 전달하는 것입니다. ECMAScript 6 용어에서 iframe 및 포함 페이지는 각각 JavaScript의 실행 환경인 다른 영역(realm)을 나타냅니다. 각 영역에는 전역 객체의 자체 사본이 있는 고유한 전역 Scope이 있습니다. Array는 생성되는 영역에 관계없이 Array이어야 합니다. 그러나 다른 영역으로 넘어 갔을 때 Array가 이전 영역의 생성자로 만들어졌고 Array
가 현재 영역의 생성자를 나타내므로 instanceof Array
호출은 false
를 반환합니다.
식별 문제에 대한 해결 방법
이 문제에 직면한 개발자들은 곧 Array를 식별하는 좋은 방법을 발견했습니다. 그들은 객체에 대해 표준 toString()
메서드를 호출하면 항상 예측 가능한 문자열이 반환된다는 것을 발견했습니다. 그래서 많은 JavaScript 라이브러리는 다음과 같은 함수를 포함하기 시작했습니다.
|
|
이 방법이 조금 어색해 보일지 모르지만 모든 브라우저에서 Array를 식별하는 데는 매우 효과적입니다. Array의 toString()
메서드는 객체를 포함하는 문자열 표현을 반환하기 때문에 객체 식별에 유용하지 않습니다. 그러나 Object.prototype
에 대한 toString()
메서드는 quirk를 가지고 있습니다 : 반환된 결과에 [[Class]]
로 불리는 내부적으로 정의된 이름을 포함합니다. 개발자는 객체에서 이 메서드를 사용하여 JavaScript 환경에서 객체의 데이터 타입이 무엇이라고 생각 하는지를 검색할 수 있습니다.
개발자는 이 동작을 변경할 방법이 없었기 때문에 동일한 방법을 사용하여 Native 객체와 개발자가 만든 객체를 구별할 수 있다는 것을 신속하게 깨달았습니다. 가장 중요한 경우는 ECMAScript 5 JSON
객체입니다.
ECMAScript 5 이전에는 많은 개발자들이 Douglas Crockford의 json2.js를 사용하여 글로벌 JSON 객체를 생성했습니다. 하지만 브라우저가 JSON
전역 객체를 구현하기 시작하면서, JavaScript 환경에서 또는 다른 라이브러리를 통해 제공되는 전역 JSON이 필요하다는 것을 깨달았습니다. isArray()
함수에서 보여준 것과 같은 기술을 사용하여 많은 개발자들이 다음과 같은 함수를 만들었습니다.
|
|
개발자가 iframe 경계를 넘어서 Array를 식별할 수있게 해주는 Object.prototype
과 동일한 특성을 사용해 JSON
이 기본 JSON
객체인지 여부를 알 수있는 방법을 제공합니다. 기본이 아닌 JSON
객체는 [object Object]
를 반환하지만 Native 버전은 [object JSON]
을 반환합니다. 이 접근법은 Native 객체를 식별하기 위한 사실상의 표준이되었습니다.
ECMAScript 6의 해결책
ECMAScript 6은 Symbol.toStringTag
Symbol을 통해 이 동작을 재정의합니다. 이 Symbol은 Object.prototype.toString.call()
이 호출될 때 생성되어야 하는 값을 정의하는 각 객체의 프로퍼티를 나타냅니다. Array의 경우 함수가 반환하는 값은 Symbol.toStringTag
프로퍼티에 "Array"
를 저장하여 설명합니다.
마찬가지로, 자신의 객체에 대한 Symbol.toStringTag
값을 정의할 수 있습니다.
|
|
이 예제에서 Symbol.toStringTag
프로퍼티는 Person.prototype
에 정의되어 문자열 표현을 생성하기 위한 기본 동작을 제공합니다. Person.prototype
은 Object.prototype.toString()
메서드를 상속 받기 때문에 Symbol.toStringTag
에서 반환된 값은 me.toString()
메서드를 호출할 때도 사용됩니다. 그러나 Object.prototype.toString.call()
메서드의 사용에 영향을 미치지 않고 다른 동작을 제공하는 자신만의 toString()
메서드를 정의할 수 있습니다. 다음과 같이 사용할 수 있습니다.
|
|
이 코드는 Person.prototype.toString()
을 정의하여 name
프로퍼티 값을 반환합니다. Person
인스턴스가 더 이상 Object.prototype.toString()
메서드를 상속하지 않기 때문에 me.toString()
을 호출하면 다른 행동을 보입니다.
달리 명시하지 않는한 모든 객체는
Object.prototype
에서Symbol.toStringTag
을 상속받습니다."Object"
문자열이 기본 프로퍼티 값입니다.
개발자가 정의한 객체에서 Symbol.toStringTag
에 어떤 값을 사용할 수 있는지에 대한 제한은 없습니다. 예를 들어, 다음과 같이 Symbol.toStringTag
프로퍼티의 값으로 "Array"
를 사용하지 못하게하는 방법은 없습니다.
|
|
Object.prototype.toString()
을 호출 한 결과는 이 코드에서 "[object Array]"
이며 실제 Array에서 얻은 결과와 같습니다. 이것은 Object.prototype.toString()
이 더 이상 객체 타입을 식별하는 완전히 신뢰할 수있는 방법이 아니라는 사실을 강조합니다.
Native 객체에 대한 문자열 태그 변경도 가능합니다. 다음과 같이 객체의 프로토 타입에 Symbol.toStringTag
을 할당하면 됩니다.
|
|
이 예제에서 Symbol.toStringTag
가 Array에 대해 덮어 쓰여지더라도 Object.prototype.toString()
을 호출하면 대신 [[Object Magic]]
이 됩니다. 이런 방식으로 내장 객체를 변경하지 말 것을 권고 하지만 JavaScript에서 이것을 금지하지는 않습니다.
Symbol.unscopables Symbol
with
문은 JavaScript에서 가장 논쟁의 여지가 있는 부분중 하나입니다. 원래 반복적인 타이핑을 피하도록 설계된 with
문은 나중에 코드를 이해하기 어렵게 만들고 성능에 부정적이고 오류가 발생하기 쉬워서 많은 비난을 받고 있습니다.
결과적으로 with
문은 strict 모드에서는 허용되지 않습니다. 이러한 제한은 클래스와 모듈에도 영향을 미칩니다. 클래스와 모듈은 기본적으로 strict 모드이며 opt-out이 없습니다.
미래의 코드는 의심할 여지없이 with
문을 사용하지 않지만, ECMAScript 6는 하위 호환성을 위한 nonstrict
모드를 여전히 지원하며, with
를 사용하는 코드가 계속해서 제대로 작동하도록 하는 방법을 찾아야만 합니다.
이 작업의 복잡성을 이해하기 위해 다음 코드를 살펴보겠습니다.
|
|
이 예제에서, with
문 안에서 push()
를 두 번 호출하면 colors.push()
와 동등합니다. 왜냐하면 with
문은 push를 로컬 바인딩으로 추가했기 때문입니다. color
의 참조는 values
참조처럼 with
문 밖에서 생성된 변수를 참조합니다.
ECMAScript 6는 Array에 values
메서드를 추가했습니다. (values
메서드에 대해서는 7 장 “Iterator와 Generator”에서 자세히 설명합니다.) 즉, ECMAScript 6 환경에서 with
문 내의 values
참조는 지역 변수 values
를 참조하는 것이 아니라 코드를 깨뜨릴 수 있는 Array의 values
메서드를 참조해야합니다. 이것이 Symbol.unscopables
Symbol이 존재하는 이유입니다.
Symbol.unscopables
심볼은 Array.prototype
에 사용되어 어떤 프로퍼티가 with
문 안에서 바인딩을 생성해서는 안된다는 것을 나타냅니다. 현재 존재하는 Symbol.unscopables
는 with
명령문 바인딩을 생략하고 values
가 true
인 블록을 시행하기 위한 식별자를 키로 가지는 객체입니다. 다음은 Array에 대한 기본 Symbol.unscopables
프로퍼티입니다.
|
|
Symbol.unscopables
객체는 Object.create(null)
호출에 의해 생성되고 ECMAScript 6에 있는 새로운 Array 메서드들을 모두 포함하는 null
프로토 타입을 가지고 있습니다. (이 메서드들은 7장 “Iterator와 Generator”및 9장 “Arrays.”에서 설명합니다.) 이러한 메서드에 대한 바인딩은 with
문 내에 만들어지지 않으므로 이전 코드가 아무런 문제없이 계속 작동할 수 있습니다.
일반적으로 with
문을 사용하지 않고 코드베이스의 기존 객체를 변경하지 않는한 객체에 Symbol.unscopables
을 정의할 필요가 없습니다.
Summary
Symbol은 JavaScript에서 새로운 유형의 Primitive 타입이며 Symbol을 참조하지 않고는 액세스할 수 없는 프로퍼티를 만드는데 사용됩니다.
진정한 Private은 아니지만 이러한 프로퍼티는 실수로 변경하거나 덮어 쓰기가 어렵기 때문에 개발자로부터 일정 수준의 보호가 필요한 기능에 적합합니다.
Symbol 값을 쉽게 식별할 수 있도록 Symbol에 대한 설명을 제공할 수 있습니다. 동일한 설명을 사용하여 코드의 다른 부분에서 공유 Symbol을 사용할 수 있는 전역 Symbol 레지스트리가 있습니다. 이런 식으로 여러 장소에서 같은 이유로 동일한 Symbol을 사용할 수 있습니다.
Object.keys()
또는 Object.getOwnPropertyNames()
와 같은 메서드는 Symbol을 반환하지 않고, ECMAScript 6에 새로운 메서드인 Object.getOwnPropertySymbols()
가 추가되어 Symbol 프로퍼티를 검색할 수 있습니다. Object.defineProperty()
및Object.defineProperties ()
메서드를 호출하여 Symbol 프로퍼티를 변경할 수 있습니다.
Well-known Symbol은 표준 객체에 대한 이전의 내부 전용 기능을 정의하고 Symbol.hasInstance
프로퍼티와 같이 전역적으로 사용 가능한 Symbol 상수를 사용합니다. 이 Symbol은 스펙에서 접두어 Symbol.
을 사용하며 개발자가 다양한 방법으로 표준 객체의 동작을 수정할 수 있도록 합니다.
이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://leanpub.com/understandinges6/read#leanpub-auto-symbols-and-symbol-properties]
참고
- ECMAScript 6 Block Binding
- ECMAScript 6 문자열과 정규 표현식
- ECMAScript 6 함수
- ECMAScript 6 객체의 확장된 기능
- ECMAScript 6 쉬운 데이터 액세스를 위한 Destructuring
- ECMAScript 6 Symbol과 Symbol 프로퍼티
- ECMAScript 6 Set과 Map
- ECMAScript 6 Iterator와 Generator
- ECMAScript 6 JavaScript 클래스 소개
- ECMAScript 6 Array 기능 향상
- ECMAScript 6 Promise와 비동기 프로그램밍
- ECMAScript 6 프록시와 리플렉션 API
- ECMAScript 6 Module로 코드 캡슐화하기
- ECMAScript 6 부록 A. 작은 변경 사항
- ECMAScript 6 부록 B. ECMAScript 7(2016) 이해하기