MENU

【完全解説】JavaScriptのSymbol.speciesとは?仕組み・使い方・実務での活用まで

1. Symbol.speciesとは何か?

JavaScriptには「組み込みシンボル」と呼ばれる特別な値がいくつか存在します。その中でも Symbol.species は、派生クラスがインスタンスを生成する際に、どのコンストラクタを使うかを指定するためのシンボルです。

通常、Array や Promise を継承したクラスで map や then を呼ぶと、その戻り値は「派生クラスのインスタンス」になります。しかし、場合によっては「元のクラス(ArrayやPromise)のインスタンスを返したい」ことがあります。その制御を可能にするのが Symbol.species です。

イティセル/コード専門官

わかりやすくいうと、
Symbol.species は「この学校に通うなら制服を着なさい」というルールと同じです。  
普通なら生徒(派生クラスのインスタンス)は自由に服を選べますが、学校(クラス定義)が「制服を着ろ」と決めているので、戻り値は必ず制服(元のクラスのインスタンス)になります。

2. 基本的な使い方

例えば、Arrayを継承したクラス

イティセル/コード専門官

Symbol.species を return Array; に指定すると、「新しいインスタンスを作るときは必ず Array を使え」というルールになります。そのため、map や filter の戻り値は常に Array のインスタンス になり、派生クラス(MyArray)のインスタンスにはなりません。

したがって、console.log(mapped instanceof MyArray); は 戻り値が MyArray ではないので false。
一方で console.log(mapped instanceof Array); は 戻り値が Array なので true となります。

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;  →戻り値は常に Array にする
  }
}

const arr = new MyArray(1, 2, 3);
const mapped = arr.map(x => x * 2);

console.log(mapped instanceof MyArray);
console.log(mapped instanceof Array);
リクナ / JavaScript統括官

false
true

実行結果
false
true

map の戻り値が MyArray ではなく Array になっているのがポイントです。

イティセル/コード専門官

Symbol.species を定義しない場合、map や filter の戻り値は派生クラス自身のインスタンスになります。  
そのため mapped instanceof MyArray も mapped instanceof Array もどちらも true になります。

3. なぜSymbol.speciesが必要なのか?

背景

•  ES6でクラス継承が導入され、Array や Promise を拡張できるようになった。

•  しかし、拡張クラスで map や then を呼ぶと、戻り値も拡張クラスのインスタンスになってしまう。

•  これが便利な場合もあるが、ライブラリ開発やフレームワーク設計では「戻り値は標準のArray/Promiseであってほしい」というケースが多い。

イティセル/コード専門官

要するに、拡張クラスで map や then を呼ぶと、どちらもデフォルトでは拡張クラスのインスタンスとして返ってきます。本来は「map の戻り値は標準の Array にしたいが、then の戻り値は拡張クラスにしたい」といった細かい制御をしたい場面もありますが、デフォルトの挙動ではそれができず、両方とも常に拡張クラスのインスタンスになってしまいます。そのため、ライブラリやフレームワークの設計では扱いづらいケースが出てきます。

具体例

•  Promiseの拡張  

独自のロギングやキャンセル機能を持つ MyPromise を作ったとする。  

しかし then の戻り値まで MyPromise になると、利用者が意図せず特殊な挙動に巻き込まれる。  

→ Symbol.species を使って戻り値を標準の Promise に固定できる。

イティセル/コード専門官

要するに、MyPromise のままだと利用者が思わぬ使い方をしたときにバグが発生する可能性があります。そこで Symbol.species を導入し、戻り値を常に標準の Promise に固定することで、拡張部分は内部に閉じ込めつつ外部には通常の Promise として振る舞わせることができます。これにより、利用者がどのように使っても予期せぬバグを防ぐことができるのです。


4. Promiseでの利用例

イティセル/コード専門官

MyPromise のように Promise を拡張すると、そのままでは then の戻り値まで拡張クラスになってしまい、利用者が意図せず特殊な挙動に巻き込まれてバグが発生する可能性があります。そこで Symbol.species を使って戻り値を標準の Promise に固定すれば、拡張部分は内部に閉じ込めつつ、外部には通常の Promise として振る舞わせることができます。

class MyPromise extends Promise {
  static get [Symbol.species]() {
    return Promise;then/catch/finally の戻り値は標準Promise
  }
}

const p = new MyPromise(resolve => resolve(42));
const q = p.then(x => x * 2);

console.log(q instanceof MyPromise);
console.log(q instanceof Promise);
リクナ / JavaScript統括官

false
true

実行結果
false
true
イティセル/コード専門官

例えば、「水を頼んだら普通の水が欲しいのに、勝手にレモン入りの水が出てくる」ようなものです。飲めなくはないけれど、レシピや他の料理との相性を考えると困る場面がある。Symbol.species を導入することで「水は必ず普通の水を出す」と保証できるわけです。

5. 注意点と落とし穴

影響するのは一部のメソッドだけ  

map、filter、slice など「新しい配列やインスタンスを返すメソッド」にだけ効きます。その他のメソッドには影響しません。

Symbol.species = undefined の意味  

特に指定しないのと同じで、デフォルトのコンストラクタ(普通はそのクラス自身)が使われます。つまり「この機能をオフにする」のと同じです。

乱用すると混乱のもと  

Symbol.species を設定していることに気づかない人から見ると、「なぜ戻り値が拡張クラスじゃなくて標準クラスなの?」と不思議に思われるかもしれません。なので使うときは意図をコメントやドキュメントで説明しておくのが安心です。

6. 関連する組み込みシンボル

イティセル/コード専門官

この記事では Symbol.species を中心に解説しましたが、JavaScript には他にも「クラスやオブジェクトの振る舞いを制御するための組み込みシンボル」が用意されています。ここでは代表的なものを軽く紹介します。

•  Symbol.iterator  

for…of やスプレッド構文で使われるデフォルトの反復処理を定義。

•  Symbol.asyncIterator  

for await…of で使われる非同期イテレータを定義。

•  Symbol.hasInstance  

instanceof 演算子の挙動をカスタマイズ。

•  Symbol.isConcatSpreadable  

Array.prototype.concat でオブジェクトを展開するかどうかを制御。

•  Symbol.match / Symbol.matchAll / Symbol.replace / Symbol.search / Symbol.split  

文字列と正規表現の各メソッドの挙動をカスタマイズ。

•  Symbol.toPrimitive  

オブジェクトをプリミティブ値に変換する際の挙動を制御。

•  Symbol.toStringTag  

Object.prototype.toString.call(obj) の出力をカスタマイズ。

•  Symbol.unscopables  

with 文でスコープに入れないプロパティを指定。

•  Symbol.species  

map や then など「新しいインスタンスを返すメソッド」の戻り値のコンストラクタを制御。

イティセル/コード専門官

たくさんの組み込みシンボルがありますが、すべてを暗記する必要はありません。必要になったときに調べれば十分です。

7. 実務での活用シナリオ

•  ライブラリ開発  

→ ライブラリを作るとき、ユーザーに返すオブジェクトは「標準の Array や Promise」であってほしい。  

もし拡張クラスをそのまま返すと、ユーザーが「なんで挙動が違うの?」と混乱する可能性がある。  

→ Symbol.species を使えば、内部では拡張クラスを使っていても、外部に返すのは標準クラスにできる。

•  フレームワーク設計  

→ フレームワーク内部では効率化や追加機能のために拡張クラスを使うことがある。  

でも外部 API で返すのは標準クラスにしておけば、利用者は「普通の Promise / Array」として扱えるので学習コストが下がる。

•  教育・学習用  

→ Symbol.species は「クラス継承の仕組み」や「組み込みシンボルの役割」を理解するのにちょうどいい題材。  

実務での利用例を学びながら、JavaScript の言語仕様を深く理解できる。

イティセル/コード専門官

実際のアプリ開発で直接使うことはあまりありませんが、ライブラリ開発やフレームワーク設計ではよく利用されています。そのため、こうした設計に関わる開発者にとっては非常に有用な仕組みと言えるでしょう。

また、教育・学習の題材としても適しています。Symbol.species を通じて「クラスを継承したときに戻り値のクラスをどう扱うか」という仕組みを理解できるからです。私自身も「派生クラスって何だろう?」と思って調べたときに、新しいクラスと既存のクラスを組み合わせて作られるものだと知り、とても面白く感じました。

8. まとめ

Symbol.species は、普段のアプリ開発ではあまり意識されることのない小さな仕様かもしれません。しかし、ライブラリやフレームワークの設計に携わる人にとっては、利用者に返すオブジェクトの型を制御できるという点で非常に重要な役割を果たします。内部的には拡張クラスを使って効率化や追加機能を実現しつつ、外部には標準の Array や Promise を返すことで、利用者は余計な学習コストを負わずに安心して使える――この「内と外のバランス」を保つ仕組みこそが Symbol.species なのです。

また、学習の観点から見ても、Symbol.species はクラス継承や組み込みシンボルの仕組みを理解するうえで格好の題材です。派生クラスの戻り値をどう扱うかという具体的な問題を通じて、JavaScript の言語仕様がどのように設計されているのかを実感できます。私自身も「派生クラスとは何か?」を考える中で、既存の仕組みに新しい要素を組み合わせていく面白さを感じました。

小さなシンボル一つが、コードの可読性や利用者体験、さらには言語理解そのものにまで影響を与える――Symbol.species はまさにその好例です。仕様の細部に目を向けることで、JavaScript の奥深さと設計思想をより鮮明に感じ取れるでしょう。

もしこの記事が役に立ったと思ったら、シェアやコメントで教えてください。  

いただいた声を今後の改善に活かしていきます。  

最後まで読んでくださり、本当にありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITTIのアバター ITTI 運営長

私はフロントエンドエンジニアを目指す初心者で、ITパスポートを取得済みです。現在はCopilotを活用しながらAIや最新のIT技術を学び、日本の開発現場で求められるチーム開発やセキュリティの知識を吸収しています。学んだことはコードや仕組みを整理し、わかりやすく発信することで、同じ学びの途中にいる人たちの力になりたいと考えています。

コメント

コメントする

目次