※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


【TypeScript】Symbolについて詳しく解説

TypeScript Symbol javascript

投稿日:2020年12月11日

このエントリーをはてなブックマークに追加
シンボル値は、オブジェクトプロパティの識別子として使用できます。これがデータ型の主な利用目的ですが、不透明なデータ型の有効化や、実装サポートされている一意の識別子として機能するなど、他の利用目的も存在します。この記事ではSymbolの利用方法について解説します。

実践

プリミティブ型とObject

はじめにJavaScriptにおけるプリミティブ型オブジェクトについて解説します。

JavaScriptではプリミティブ型イミュータブル(変更不可)であり、値が同じ場合は参照先は常に同じになります。

それと対象的にオブジェクトの様な非プリミティブ型はミュータブル(変更可)であり全く値が同じでも、参照先が同じとは限りません。

次に簡単な例を上げてみます。

Primitive型とObjectの比較例
//  これは値渡し
let num1 = "abc";
let num2 = num1;

// どちらの変数も参照先が同じ
console.log(num1 == num2); // true

num2 = "a" + "b" + "c";

// 値が同じなら参照先は常に同じ
console.log(num1 == num2); // true

let obj1 = { name: "marsquai" };
let obj2 = obj1;

// どちらの変数も参照先が同じ
console.log(obj1 == obj2); // true

obj2 = { name: "marsquai" };

// 参照先が変わった
console.log(obj1 == obj2); // false

プリミティブ型とオブジェクトで挙動が違うことが確認できますね。

Symbolの使い方(基礎)

Symbolは常に一意の値を返すためオブジェクトと非常に似た動きをしますが、実際にはプリミティブ型です。

下に簡単な使い方を示します。

let symbol1 = Symbol();
let symbol2 = Symbol();

//毎回違う値になる
console.log(symbol1 == symbol2); // false

この様にSymbolはSymbol()というグローバルの静的メソッドを使い生成します。

値はその環境の中で常に一意となります。

Symbolの有益さについて説明する前に、JavaScriptのObjectの特徴について説明しておきます。

使い方(プロパティとして利用)

Symbolのプロパティとしての利用について解説します。

と、その前にJavaScriptのある性質について話しておく必要があります。

JavsScriptではObjectのプロパティが動的です。そのため以下のようなコードを書くことができます。

/**
 * ユーザークラス
 */
class User {
    name;
    constructor(_name) {
        this.name = _name;
    }
}

/**
 * 配列に連番を追加
 * @param users {Array} 配列
 */
function addIdToUsers(users) {
    users.forEach((element, i) => {
        element.id = i + 1;
    });
}

/**
 * 配列を初期化
 */
const users = [
    new User("mars quai"),
    new User("Ichigo Kurosaki"),
    new User("Rukia Kuchiki"),
];

addIdToUsers(users);

console.log(users);
出力
[ User { name: 'mars quai', id: 1 },
  User { name: 'Ichigo Kurosaki', id: 2 },
  User { name: 'Rukia Kuchiki', id: 3 } ]

addIdToUsers()関数は渡された配列に連番のidをプロパティとして設定する関数です。Userクラスはnameというプロパティだけを持っているのですが、addIdToUsers()関数で後からidプロパティを追加しています。

この様にJavaScriptのObjectのプロパティは生成時から変化しうる動的な性質を持ちます。

これは非常に便利な機能なのですが、あるバグを生じる可能性があることがわかるでしょうか?

それは対象のObjectがすでに追加したいプロパティを持っていた場合です。

例えば上記Userクラスに対して、別のプログラマーが以下のようなクラスを作ってしまったとしましょう。

/**
 * Userを継承した管理者クラス
 */
class SuperUser extends User {
    id;
    password;
    constructor(_name, _id, _password) {
        super(_name);
        this.id = _id;
        this.password = _password;
    }
}

SuperUserクラスはすでにidをいうプロパティを持っています。このインスタンスの配列でうっかりaddIdToUsers()を呼び出してしまうと、idというプロパティが上書きされてしまいます!

/**
 * 配列を初期化
 */
const users = [
    new SuperUser("mars quai", "marsquai", "marsquai0203"),
    new SuperUser("Ichigo Kurosaki", "kurosaki", "kurosaki0900"),
];

addIdToUsers(users);

console.log(users);
出力
[ SuperUser { name: 'mars quai', id: 1, password: 'marsquai0203' },
  SuperUser { name: 'Ichigo Kurosaki', id: 2, password: 'kurosaki0900' } ]

インスタンス生成時に渡したidが意図せず上書きされてしまいました。

JavaScriptではプロパティが動的であるが故に、プロパティの衝突の可能性をはらんでいます。

ではここでSymbolを使う例を考えてみましょう。

Symbol()は毎回一意な値を返す為、衝突を起こさないプロパティのキーとして扱うことができます。addIdToUsersProperty()関数を以下の様に変更してみます。

[...]

/**
 * 配列に連番を追加
 * @param users {Array} 配列
 */
const addIdToUsersProperty = Symbol();
function addIdToUsers(users) {
    users.forEach((element, i) => {
        element[addIdToUsersProperty] = i + 1;
    });
}

[...]

Symbol()関数でaddIdToUsers()で使用するプロパティのキーを生成しています。このように定義することでプロパティの衝突は絶対に起こりません。

/**
 * 配列を初期化
 */
const users = [
    new SuperUser("mars quai", "marsquai", "marsquai0203"),
    new SuperUser("Ichigo Kurosaki", "kurosaki", "kurosaki0900"),
];

addIdToUsers(users);

console.log(users);
出力
[ SuperUser {
    name: 'mars quai',
    id: 'marsquai',
    password: 'marsquai0203',
    [Symbol()]: 1 },
  SuperUser {
    name: 'Ichigo Kurosaki',
    id: 'kurosaki',
    password: 'kurosaki0900',
    [Symbol()]: 2 } ]

Symbol()のキーとしての扱い

一つ注意する点として、SymbolJSON.stringfy()Object.keys()では吐き出されません。しかしプロパティは確かに存在します。

/**
 * 配列を初期化
 */
const users = [
    new SuperUser("mars quai", "marsquai", "marsquai0203"),
    new SuperUser("Ichigo Kurosaki", "kurosaki", "kurosaki0900"),
];

addIdToUsers(users);

console.log(Object.keys(users));
console.log(JSON.stringify(users));
console.log(users[0][addIdToUsersProperty]);
出力
[ '0', '1' ]
[{"name":"mars quai","id":"marsquai","password":"marsquai0203"},{"name":"Ichigo Kurosaki","id":"kurosaki","password":"kurosaki0900"}]
1

Symbol()はあくまでそのプログラムの中でだけ利用するプロパティで使用するのがよさそうですね。


さいごに

参考サイト

このエントリーをはてなブックマークに追加


関連記事

記事へのコメント