JavaScriptのSymbolについて

# 初めに
今回は`Symbol`の基本、そして一部のメソッドの使い方をまとめてみました。

参考文章はこちらです。

https://ja.javascript.info/symbol#ref-2208

中国語が分かる方はこちらもおすすめです。

https://es6.ruanyifeng.com/#docs/symbol

# Memo

<details><summary>Symbolの使用について</summary><div>

- `Symbol`はプリミティブであり、一意の値を表します。
- `Symbol`で創られたすべての値はユニック（unique）で、見た目は同じでも違う存在として判定されます。
- `Symbol`はオブジェクトのプロパティ名に用いるとき、`[]`でアクセスしなければなりません。
</div></details>

<details><summary>Symbolへの反復処理について</summary><div>

- `for...in`、`for...of`を使って`Symbol`プロパティを列挙したり反復処理したりできません。
- `Symbol`はプライベートプロパティではないが、`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`に返されることができません。
- `Object.getOwnPropertySymbols()`に特定のオブジェクトから見つかるすべての`Symbol`プロパティを返す。
- 静的メソッド`Reflect.ownKeys()`なら、指定のオブジェクトすべてのプロパティキー（`Symbol`も）を返す。
</div></details>

プロパティがオブジェクトの内部に使ってほしい場合は、`Symbol`は普通の反復処理メソッドでアクセスできない特性を利用すると便利です。（プライベートプロパティではないけど。）

# Symbols
`Symbol`は`new`を使わない。呼び出しに値を入れる、あるいはオブジェクトの`toString()`メソッドが返した値をシンボル値を生成します。
```jsx
let id = Symbol('id');
console.log(id); // Symbol(id) // Symbol

const obj = {
  toString() {
    return 123;
  },
};
const sym = Symbol(obj);
console.log(sym); // Symbol(123)
```
一意であるため、中身の同じ説明文（値）が同じでも判定はいつも`false`になります。
```jsx
let id1 = Symbol('id');
let id2 = Symbol('id');
console.log(id1 === id2); // false
```

シンボル値は文字列に自動変換ができない。`toString()`か`String()`でそのまま出力する。中身の値を参照したいとき、`description`というプロパティを利用して表示します。
```jsx
let id = Symbol('id');

console.log(id); // Symbol(id) // Symbol
console.log(id.toString()); // Symbol(id) // string
console.log(String(id)); // Symbol(id) // string
console.log(id.description); // id // string
```
オブジェクトに既存のプロパティがあったとしても、シンボル値の説明文が独自の固有識別子を持つので衝突しません。
```jsx
let id = Symbol('id');
let id1 = Symbol('id');

let user1 = {
  name: 'Mick',
  id: 1,
};
user1[id] = 1;
user1[id1] = 2;
console.log(user1)
// { name: 'Mick', id: 1, [Symbol(id)]: 1, [Symbol(id)]: 2 }
```
（`['id']`という書き方が`id`という文字列をプロパティ名として使うが、`[id]`は変数`id`の値`Symbol(id)`を入れるということです。）

ほかの書き方↓は、オブジェクトに`[]`中に変数入れる、または`Object.defineProperty()`で定義する。
```jsx
let id = Symbol('id');
let user2 = {
  name: 'Lucy',
  [id]: 2,
};

let user3 = {};
Object.defineProperty(user3, id, { value: 3 });
console.log(user3[id]); // 3
```
もちろんプロパティ名だけではなく値としても入れます。
```jsx
const log = {};
log.level = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn'),
};
console.log(log.level.DEBUG.description); // debug
```
# Example1 `switch`
`Symbol`はユニックを値を生み出すので、`switch`などで唯一のケースが通せたいときとてもやりやすいです。
```jsx
const colorRed = Symbol();
const colorGreen = Symbol();

function getColor(color) {
  switch (color) {
    case colorRed:
      return 'Red';
    case colorGreen:
      return 'Green';
    default:
      throw new Error('Undefined color');
  }
}
console.log(getColor(colorRed)); // Red
```
```jsx
const shapeType = {
  triangle: Symbol(),
};

function getArea(shape, { width, height } = {}) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = 0.5 * width * height;
      break;
  }
  return area;
}
console.log(getArea(shapeType.triangle, { width: 100, height: 100 })); // 5000
```
衝突しない利点を応用したらいろいろできそうです！

# Hidden Properties
`Symbol`プロパティは、
プロパティの反復処理メソッド、`for...in`、`for...of`、
プロパティの配列を返すメソッド、`Symbol`は`Object.keys()`、`Object.getOwnPropertyNames()`、
そしてJSONへの置き換え`JSON.stringify()`では表しません。

下は一部の例です。
```jsx
let a = Symbol('foo');
let b = Symbol('bar');

let obj = {
  [a]: 'foo',
  [b]: 'bar',
};

for (let prop in obj) {
  console.log(prop); // nothing happens
};

console.log(Object.getOwnPropertyNames(obj)); // []
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(foo), Symbol(bar) ]
console.log(Reflect.ownKeys(obj)); // [ Symbol(foo), Symbol(bar) ]
```
`Symbol`は`Object.keys()`と`Object.getOwnPropertyNames()`から勝手に返されないので隠せる。
容易に外に出さない、それに内部メソッドに提供するプロパティを創るときに`Symbol`は便利だと思います。（プライベートプロパティではないけど。）
```jsx
let size = Symbol('size');

class Collection {
  constructor() {
    this[size] = 0;
  }

  add(item) {
    // this[this[size]] => this.0 // this[size] = 0, ...etc.
    this[this[size]] = item;
    console.log(this[this[size]]);
    this[size] += 1;
  }

  static sizeOf(instance) {
    return instance[size];
  }
}

let x = new Collection();
console.log(Collection.sizeOf(x)); // 0
console.log(Object.getOwnPropertySymbols(x)); // [ Symbol(size) ]
console.log(x); // Collection { [Symbol(size)]: 0 }

x.add('foo');
console.log(Collection.sizeOf(x)); // 1
console.log(Object.getOwnPropertySymbols(x)); // [ Symbol(size) ]
console.log(x); // Collection { '0': 'foo', [Symbol(size)]: 1 }
```
上は新しいプロパティ創るとき、プロパティ`[Symbol(size)]`持っている値がプロパティを作っていきます。（このやり方は一応indexの効果もあると思う。）
```jsx
console.log(Object.keys(x)); // [ '0' ]
console.log(Object.getOwnPropertyNames(x)); // [ '0' ]
```

# Methos
## `Symbol.for()`
`Symbol()`はユニックで同じ説明文（description）でも異なる。
しかし同じ説明文のシンボルが一つの参照にしたいときもあって、`Symbol.for()`は`Symbol()`と違って、生成されたシンボル値は**global symbol registry**に保存しているので、同じ説明文のシンボルがあればそれを返す、ない場合は生成するというメソッドです。
```jsx
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2);
//
let s1 = Symbol.for('foo');
{
  let s2 = Symbol.for('foo');
  console.log(s1 === s2); // true
};
```
（`Symbol.for`は別のスコープでもシンボル値をグローバルに登録する。）

## `Symbol.keyfor()`
`Symbol.for()`はグローバル環境にシンボル値を登録することに対し、`Symbol.keyfor()`はそれを見つかったら登録されたシンボル値を返します。
`Symbol()`はそうではありません。`Symbol()`は毎回のコールは新しいシンボル値を生成し、そしてグローバル環境にも登録しません。
```jsx
let s1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1)); // foo

let s2 = Symbol('foo');
console.log(Symbol.keyFor(s2)); // undefined
```

# Example2 Singleton
シングルトンは単一のインスタンスのことに指します。何度も同じインスタンスを創るより、すべてのインスタンスが一つのオブジェクトに参照したらメモリーにやさしいです。

`Symbol.for`はグローバルに値を登録する、という特性を利用してグローバルにプロパティを創り、シングルトンを値としてそのプロパティにアサインしたら、このグローバルプロパティがどこで呼び出されても同じ参照（シングルトン）を返してくれます。
```jsx:moudle.js
const fooKey = Symbol.for('foo');

function A() {
  this.foo = 'hello';
}

// if global[Symbol.for('foo')] doesn't exist, set property and value
if (!global[fooKey]) {
  global[fooKey] = new A();
}
const globalSymbol = global[fooKey];
export default globalSymbol;
```
上のグローバルプロパティをインポートしたら、今のファイルにも使えるようになる。
```jsx:app.js
import globalSymbol from "./symbol.js";

console.log(globalSymbol); // A { foo: 'hello' }
console.log(global[Symbol.for('foo')]); // A { foo: 'hello' }
console.log(globalSymbol === global[Symbol.for('foo')]); // true

global[Symbol.for('foo')] = { foo: 'world' };
console.log(global[Symbol.for('foo')]); // { foo: 'world' }
console.log(globalSymbol === global[Symbol.for('foo')]); // false
```
しかし、これでは肝心な値がいつでも書き換える。

そこで、キーが必ず一意のシンボル値を生成する`Symbol()`に変えたら、プロパティへのアクセスができなくなって、書き換えられるわけでもなくなります。
```jsx:moudle.js
const fooKey = Symbol('foo');
// following the same procedure
```
```jsx:app.js
console.log(globalSymbol); // A { foo: 'hello' }
console.log(global[Symbol.for('foo')]); // undefined
console.log(globalSymbol === global[Symbol.for('foo')]); // false
```
（毎回このスクリプトを実行するとシンボル値違いますが。）

# Symbol methods in `Class` & `Object`
## [Symbol.hasInstance]
`[Symbol.hasInstance]`は、オブジェクト内部メソッドで、`instanceof`運算子と遭遇したらメソッドを実行する。

下はクラスで`[1, 2, 3] instanceof Array`を実装してみたコードです。
```jsx
class Myclass {
  [Symbol.hasInstance](instance) {
    return instance instanceof Array;
  }
}
console.log([1, 2, 3] instanceof new Myclass()); // true
```
カスタマイズも可能です。
```jsx
class Even {
  static [Symbol.hasInstance](item) {
    return Number(item) % 2 === 0;
  }
}
console.log(1 instanceof Even); // false
console.log(2 instanceof Even); // true
//
const Even = {
  [Symbol.hasInstance](item) {
    return Number(item) % 2 === 0;
  }
};
console.log(1 instanceof Even); // false
console.log(2 instanceof Even); // true
```
`[Symbol.hasInstance]`が`instanceof`に触発される特性を用いて`class Even`は入れられた値の処理して返す。オブジェクト`Even`も同じです。
（`class Even`では`static`をつけて内部の静的メソッドとして使われるので、`new Even()`ではこのカスタマイズされた`[Symbol.hasInstance]`メソッド使えません。）

## [Symbol.species]
`[Symbol.species]`はコンストラクタの参照先を返す。インスタンスを創るとこの`[Symbol.species]`が使われます。
```jsx
class MyArray extends Array {
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

console.log(b instanceof MyArray); // true
console.log(c instanceof MyArray); // true
```
`b`と`c`はインスタンスとしてプロトタイプチェーンから`Array`のメソッドをアクセスするので、どれも`class MyArray`のインスタンスでした。
このときの`[Symbol.species]`はこんな感じです。（デフォルト状態）
```jsx
class MyArray extends Array {
  static get [Symbol.species]() {
    return this;
  }
}
```
そして`[Symbol.species]`に`Array`を返してもらってみたら、
```jsx
class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

console.log(b instanceof MyArray); // false
console.log(c instanceof MyArray); // false
console.log(b instanceof Array); // true
console.log(c instanceof Array); // true
```

## [Symbol.toPrimitive]
`Symbol.toPrimitive`メソッドは`hint`が`string`、`number`、`default`のどれかを返してくれます。なのでそれぞれの値によって、各自の処理を行うメソッドをカスタマイズできます。

```jsx
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error('It is not number, string, or default.');
    }
  }
};

console.log(10 * obj); // 1230
console.log(3 + obj); // 3default
console.log('123' + obj); // 123default
console.log(String(obj)); // 'str'

console.log(obj == 'default');
// Error: It is not number, string, or default.
```
## [Symbol.iterator]

https://qiita.com/hu-yu/items/ea2164c0bb448c9c3934#symboliterator-in-object


## [Symbol.asyncIterator]

https://qiita.com/hu-yu/items/755dcae591874122cf3e#symbolasynciterator

...`Symbol`メソッドはほかにまだたくさんあるのですが、今回はとりあえずこれぐらいにしておきたいと思います。