【JavaScript】Privateなプロパティの実装に出てくるSymbolは何をしているのか

はじめに

こんにちは、SHOJIです。

JavaScript(ES6)でプライベートプロパティの実装について調べるとSymbolを使った方法がよく出てきます。

本記事では、Symbolとは何なのか?について記載します。

Symbolを用いたPrivate Propertiesの参考記事

stackoverflow.com

Symbolの使用例

Symbolを使わない例(Privateプロパティになっていない状態)

// 即時関数のため、DogにはDogクラスの定義が格納される
var Dog = (() => {
    const propSound = "propSound"; // プロパティとしてpropSoundを使用

    class Dog{       
        constructor(sound){
            this[propSound] = sound;
        }
        calls(){
            console.log(this[propSound]);
        }
    }
    
    return Dog;
})();

const dog = new Dog("bow-wow"); // Dogクラスのインスタンスを生成(鳴き声にbow-wowを設定)
console.log(dog);               // Dogインスタンスの中身:Dog { propSound: 'bow-wow' }

dog["propSound"] = "woof"; // DogのpropSoundプロパティをwoofに変更する
dog.calls();               // woofが出力される

上記のコードはdogインスタンスのsoundを変更できますが、次のSymbolを使う例では外からプロパティを変更できません。

Symbolを使う例(Privateプロパティになった状態1

// 即時関数のため、DogにはDogクラスの定義が格納される
var Dog = (() => {
    const propSound = Symbol(); // プロパティとしてSymbol()を使用

    class Dog{       
        constructor(sound){
            this[propSound] = sound;
        }
        calls(){
            console.log(this[propSound]);
        }
    }
    
    return Dog;
})();

const dog = new Dog("bow-wow"); // Dogクラスのインスタンスを生成(鳴き声にbow-wowを設定)
console.log(dog);               // Dogインスタンスの中身:Dog { [Symbol()]: 'bow-wow' }

dog[Symbol()] = "woof"; // DogのSymbol()プロパティをwoofに変更する
dog.calls();            // bow-wowが出力される(変更できていない)

処理終了時点でdogオブジェクトがどうなっているかというと、Symbolが二つ存在しています。

console.log(dog); // Dog { [Symbol()]: 'bow-wow', [Symbol()]: 'woof' }

Symbolは何をしているのか

mozillaのSymbolのページ(https://developer.mozilla.org/ja/docs/Glossary/Symbol)ではこのような説明がされています。

動的に無名の一意の値を生み出します。シンボルはオブジェクトプロパティとして使用されることがあります。

なるほど、Symbolは必ず一意の値を生成するから

dog[Symbol()] = "woof"; // DogのSymbol()プロパティをwoofに変更する

このようにしてもdogインスタンスが元々持っていたSymbolプロパティとは別物とみなされたんですね。これなら処理終了時点でdogオブジェクトにSymbolプロパティが二つ存在している(ように見える)ことにも納得がいきます。

ちなみに、Symbolは文字列を引数に取ることができます。

const propSound = Symbol("sound property");

この引数はあくまで説明書きとしてしか機能しないので、同じ文字列を引数にしたSymbolであっても別の値が生成されます。

console.log(Symbol("sound property") === Symbol("sound property")) // false

  1. アクセスする手段はあるので厳密にはPrivateではないです。

【JavaScript】ReduceでObject(連想配列)を一つにまとめる方法

はじめに

こんにちは、SHOJIです。

本記事では、JavaScriptのReduceを使って、Object(連想配列)のリストを一つのObjectにまとめる方法を記載します。

やりたいこと

以下のようなデータがあった場合、id(ユニークキー)をキーとするObjectに変換して、idからアイテム名を取得できるようにしたい。

処理対象データイメージ


const list = [
  {id:1, item:"アイテム1"},
  {id:2, item:"アイテム2"},
  {id:3, item:"アイテム3"},
];

処理後データイメージ

{ '1': 'アイテム1', '2': 'アイテム2', '3': 'アイテム3' }

Reduceによる変換

const list = [
  {id:1, item:"アイテム1"},
  {id:2, item:"アイテム2"},
  {id:3, item:"アイテム3"},
];

const obj = list.reduce((x, y) => Object.assign(x, {[y.id]:y.item}), {});

console.log(obj); // { '1': 'アイテム1', '2': 'アイテム2', '3': 'アイテム3' }

処理内容の説明

Reduce自体は世にたくさんの解説記事があるので探してください、だけだと不親切なので簡単に。

const obj = list.reduce((x, y) => Object.assign(x, {[y.id]:y.item}), {});

Reduceの第一引数はメソッド、第二引数は初期値です。

第一引数のメソッドはlistの要素数分繰り返されます。

2つの引数(サンプルの x と y )は、

  • x = 初期値、または前回の実行結果(サンプルの Object.assign(x, {[y.id]:y.item} の実行結果)

  • y = listの要素

です。

 

今回のサンプルだと以下のような処理が行われます。

  1. x = {}(初期値)y = {id:1, item:"アイテム1"}(listの1番目の要素)として、Object.assign(x, {[y.id]:y.item})を実行。

  2. x = { '1': 'アイテム1' }(1の結果)、y = {id:2, item:"アイテム2"}(listの2番目の要素)として、Object.assign(x, {[y.id]:y.item})を実行。

  3. x = { '1': 'アイテム1', '2': 'アイテム2' }(2の結果)、y = {id:3, item:"アイテム3"}(listの3番目の要素)として、Object.assign(x, {[y.id]:y.item})を実行。

  4. listの全要素に対して処理を行ったので、3の結果である{ '1': 'アイテム1', '2': 'アイテム2', '3': 'アイテム3' } を結果として返し、処理を終了します。

使うシーン

一番使うであろうシーンは、REST APIを介してテーブルデータをJSON形式で取得したあとですかね。

そういう場合はカラムが複数あると思うので、キーに対して全項目を引けるようにまとめた方がいいかもしれません。

const list = [
  {id:1, item:"アイテム1", price:50},
  {id:2, item:"アイテム2", price:100},
  {id:3, item:"アイテム3", price:150},
];

const obj = list.reduce((x, y) => Object.assign(x, {[y.id]:y}), {});

console.log(obj);
// {
//   '1': { id: 1, item: 'アイテム1', price: 50 },
//   '2': { id: 2, item: 'アイテム2', price: 100 },
//   '3': { id: 3, item: 'アイテム3', price: 150 }
// }

この形だと、id = 1 のデータとして、item と price の両方の情報を取得できます。