Created by 이항희 / blog.javarouka.me
JavaScript 의 Object 는 Key-Value Pair.
즉, Dictionary 나 Map 으로 불리는 것과 비슷하다.
게다가 JS 객체는 Object 와 모두 연결되어 있다
별도 구현하지 않을 경우 prototype chain 으로 Object 의 구현이 사용된다.
객체의 문자열 표현을 반환
객체가 텍스트 혹은 문자열으로 표현될때 자동으로 호출된다.
const today = new Date();
const todayStrByOperator = today + " 입니다";
const todayStrByMethod = today.toString() + " 입니다";
console.log(todayStrByOperator === todayStrByMethod); // true
재미있는건 기본 toString, 즉 override 되지 않은 toString 은 반드시
'[object type]'
const object = {};
console.log(object.toString()) // [object Object]
console.log(Object.prototype.toString()) // [object Object]
const arr = [];
console.log(Object.prototype.toString.call(arr)) // [object Array]
const now = new Date();
console.log(Object.prototype.toString.call(now)) // [object Date]
이를 활용하여 뭔가 많이 부족한 typeof 를 대체할 타입 검사기를 만들 수도 있다
const typeChecker = ( _=> {
const defaultToString = Object.prototype.toString; // 기본 객체의 toString 을 보관해둔다
const types = [
'Object','Array','Date','Boolean','Function','String','Number','RegExp','Null','Undefined'
];
// reduce 함수를 사용하여 위에 정의한 타입으로 is + type 형식의 메서드를 생성한다.
return types.reduce((prev, type) => {
return prev[`is${type}`] = target => defaultToString.call(target) === `[object ${type}]`, prev
}, {});
})();
console.log(typeChecker.isFunction(Date)); // true
console.log(typeChecker.isFunction(new Date)); // false
console.log(typeChecker.isDate(new Date())); // true
console.log(typeChecker.isUndefined(undefined)); // true
객체의 기본값(primitive) 표현을 반환
명시적으로 호출할 일이 거의 없고, 대부분 엔진 내부에서 객체의 기본값 표현이 필요할 경우 자동으로 실행
toString 과 마찬가지로 대부분의 built-in Object 들은 이것을 override.
function NiceNumber(primitiveValue) {
this.primitiveValue = primitiveValue;
}
// @override
NiceNumber.prototype.valueOf = function() { return this.primitiveValue; }
const myNiceNumber = new NiceNumber(100);
console.log(myNiceNumber * 2) // 200
console.log(myNiceNumber.valueOf() * 2); // 200
console.log(myNiceNumber * myNiceNumber); // 10000
const now = new Date();
// Date 구현의 경우 기본값 표현이 필요할 경우에도 toString 이 호출된다.
// 필요한 경우 타 객체와 달리 valueOf 명시적인 호출이 필요하다
const oneYearsAfter = new Date(now2.valueOf() + (1000 * 60 * 60 * 24 * 365)) // 1년 뒤
객체의 JSON 표현을 반환
JSON.stringify 에 인자로서 호출될 경우 반환될 표현식을 정의할 수 있다
const sayMan = {
hello: {
'ko': '세계',
'en': 'world'
}
};
JSON.stringify(sayMan); // { "hello": { "ko": "세계", "en": "world" } }
const smartSayMan = {
locale: 'ko',
hello: {
'ko': '세계',
'en': 'world'
},
toJSON() {
// global navigator 객체의 language 이용.
return { hello: this.hello[navigator.language] }
}
};
JSON.stringify(smartSayMan); // { "hello": "세계" } }
할수있지!
저건 무엇인가?
난 무언가가 오리처럼 걷고 오리처럼 울어댄다면 그것을 오리라고 부르겠다
function cookDuckMeat(something) {
// 오리처럼 꿕꿕대고, 걷고있나?
if(!something.quack || !something.walk) {
throw new TypeError('이건 오리가 아니다...');
}
// TODO: ... 오리 고기 요리 구현 ...
}
문자열이 필요할 때 만일 객체가 toString 을 할 수 있다면 그것을 호출해 주겠다.
const duck = {
toString() { return "미운오리새끼" }
};
// toString 할 수 있어?
console.log(`저는 ${duck} 입니다`);
하지만 문자열로 행동을 체크하다니, 이거 위험한거 아닌가?
// 객체의 키로 사용
const nameKey = Symbol('company');
const grandCompany = {
[nameKey]: `대단한회사`
};
// const grandCompany = {};
// grandCompany[nameKey] = `대단한회사`;
// class 문법에도 사용가능
const quack = Symbol('duck.quack');
class Duck() {
[quack]() { return "꽥!" }
}
const duck = new Duck()
duck[quack](); // "꽥!"
const sym = Symbol('Symbol.key');
const prop = 'String.key'
const obj = { [sym]: '심볼 값', [prop]: '속성 값' }
for(var k in obj) {
console.log(k, obj[k]);
} // 'String.key 속성 값'
console.log(Object.keys(obj)) // ['String.key']
// Object.getOwnPropertySymbols 로 열거할 수 있다.
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(Symbol.key)]
// 생성시마다 유니크함
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2) // false;
const duck1 = Symbol('duck');
const duck2 = Symbol('duck');
console.log(duck1 === duck2) // false;
const unique = Symbol('unique'); // 그냥 생성
const uniqueFor1 = Symbol.for('unique'); // 전역 생성
const uniqueFor2 = Symbol.for('unique'); // 다시 생성할 경우 기존 생성된 Symbol 을 반환.
const uniqueFor2 = Symbol.keyFor('unique'); // 생성된 심볼을 가져올 경우 Symbol.for('unique') 와 동일 효과
console.log(unique === uniqueFor1); // false;
console.log(uniqueFor1 === uniqueFor2); // true;
Duck Typing 에 사용
네가 만약 Symbol.xxx 를 할수있다면 xxx 를 하겠다
const [ action,comedy,sing ] = ['action','comedy','sing'].map(v => Symbol(v));
function showVariety(star) {
if(star[action]) star[action]();
if(star[comedy]) star[comedy]();
if(star[sing]) star[sing]();
}
var suhyungHong = {
[action](){ return '연기를 합니다' }
}
console.log(showVariety(suhyungHong)); // 연기를 합니다
var changjungIm = {
[sing](){ return '노래도 하고' },
[action](){ return '연기도 하고' }
}
console.log(showVariety(changjungIm)); // 노래도 하고 연기도 하고
UUID 등 에 사용
불변 고유한 값이므로, 심볼을 공유하여 불변상태를 제어하기 쉽다
// 모델 코드. 변경 시마다 새로운 hash 를 저장한다.
user.update = function() {
this.hash = Symbol(); // 업데이트 해쉬 발급
// ... 모델 내 업데이트
}
// 화면 코드. 모델을 관리하는 store 등에서 변경 알림을 받는다고 가정한다.
userStore.subscribe = function(newUser) {
if(newUser.hash == this.user) return; // 기존 user 와 비교하여 업데이트되지 않으면 pass
// ... UI 업데이트
}
class private 구현에 사용
// @file Bart.js
const privateVars = Symbol('Bart.private');
export default class SecretDeveloper {
constructor() {
this[privateVars] = {
name: '갓갓갓',
desc: '비밀이 많다'
}
}
get name() { return this[privateVars].name; }
get desc() { return this[privateVars].desc; }
toString() { return `${this.name}, ${this.desc}` }
}
const person = new SecretDeveloper();
console.log(`개발자 소개 > ${person}`); // DuckTyping 으로 toString 이 자동 호출
person.name = 'GodGodGod';
console.log(`수정된(?) 개발자 소개 > ${person}`); // immutable!
앞서 설명한 toString, valueOf 등에 대응하는 심볼이 존재한다
참고: Well-known symbolstoString, valueOf 에 대응하지만 스펙이 좀 다르다.
const es6Man = {
// 변환의 힌트가 인자로 온다. 힌트는 'string', 'number', 'default' 셋이다.
[Symbol.toPrimitive](hint) {
switch(hint) {
case 'number': return 28;
case 'string': return 'ES2015';
default: return 'ES2015'
}
}
}
console.log(es6Man + 1); // ES20151
console.log(Number(es6Man)); // 28
console.log(es6Man); // ES2015
console.log(-es6Man); // 28
console.log(`${es6Man} 입니다`); // ES2015 입니다
이 속성의 문자열은 Object.prototype.toString 의 컨텍스트로 호출할 경우 표현할 타입이 된다.
function Duck() {}
const duck = new Duck();
console.log(Object.prototype.toString.call(duck)); // [object Object]
function SmartDuck() {}
SmartDuck.prototype[Symbol.toStringTag] = "SmartDuck";
const startDuck = new SmartDuck();
console.log(Object.prototype.toString.call(startDuck)); // [object SmartDuck]
생성자 객체가 어떤 객체를 자신의 인스턴스(instance)로 인식하는지 확인하는데 사용하는 메소드. instanceOf 연산 시 자동 호출된다.
const fakeDate = {
[Symbol.hasInstance](instance) {
return Date === instance.constructor;
}
};
// Date 가 아닌데!!
console.log(new Date() instanceof fakeDate);
아니다
Symbol 이라는게 있지만 아직 ES6의 몇몇 부분은 여전히 문자열을 사용한 DuckTyped 방식이다.
const thenable = {
then(resolve, reject) {
!!Math.round(new Date()%2) ? resolve('resolved!') : reject('rejected!')
}
};
// 이것은 데너블(then-able). 문자열 then 의 속성으로 Promise.resolve 가 동작한다.
Promise.resolve(thenable)
.then(fulfilled => {
console.log(`WoW! ${fulfilled}`);
})
.then(rejected => {
console.log(`Oops! ${rejected}`);
})
하지만 신규로 제작된 스펙은 Symbol Duck Typed 이다.
const arr = [1,2,3,4,5];
for(const el of arr) { console.log(el); }
const str = `hello`;
for(const el of str) { console.log(el); }
const mySet = new Set([1,2,3,4,5]);
for(const el of mySet) { console.log(el); }
function forOf(iterator) {
if(!(Symbol.iterator in iterator)) {
throw new TypeError(`${iterator}[Symbol.iterator] is not a function`);
}
// 이터러블을 가져온다.
// 이터러블은 next() 메소드를 가지며 { value: 순회값, done: 종료여부 } 를 반환하는 인터페이스를 말한다.
// 문자열이 아닌 Symbol.next 의 방식이었다면 좋았을것 같지만 문자열 함수 방식이다.
const iterable = iterator[Symbol.iterator]();
if(typeof iterable.next !== 'function') {
throw new TypeError(`iterator.next is not a function(…)`)
}
// ...순회 구현... for...while..
}
이 외의 Well-Known Symbol 은 다음 링크에.
Well-known symbols