- 技術 💻
プロトタイプとは|試作品とJavaScriptのprototypeを実行で解説
結論
プロトタイプとは、本番の製品やシステムを作る前に、考えを形にして確かめるための「試作品」のことです。JavaScript では意味が変わり、オブジェクトが機能(メソッド)を借りに行く「継承のもとになるオブジェクト」を prototype と呼びます。
この記事でわかること
- プロトタイプの意味を、ものづくり・画面設計・JavaScript・設計パターンの場面別に整理した表
- HTML と CSS で作った画面の試作(48行)を Chromium で操作した結果と、作り方の手順
- JavaScript の prototype チェーン・class との関係を Node.js と Chromium で実行した出力、エラー原文、メモリの実測
この記事で実際に動かしたもの:Node.js 22.23.4 で prototype チェーンをたどるスクリプト・コンストラクター関数と class の比較・エラー10種の原文・20万個のオブジェクトでのメモリ測定・コピー方法ごとの prototype の残り方を実行し、Chromium 153 で DOM 要素の prototype チェーンと、HTML と CSS だけで作った会議室予約の画面プロトタイプ(3画面)を操作しました。
「プロトタイプ」は、企画やデザインの話では「試作品」、プログラミングの話では JavaScript の仕組みを指します。同じ言葉で話がかみ合わないことがあるため、最初に2つの意味を図で分けます。
flowchart TD
P[プロトタイプ] --> A[試作品<br>製品・画面の試作]
P --> B[JavaScript の prototype<br>メソッドの借り先]
A --> A2[確かめたら捨てる]
B --> B2[class の中身]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| プロトタイプとは(意味) | 本番の前に作って確かめる試作品。英語の prototype は「原型」 | プロトタイプとは(ふだんの意味とITの意味) |
| プロトタイプの意味は分野で違うか | 違う。製品の試作機、画面の試作、JavaScript の仕組み、設計パターン | 場面別のプロトタイプの意味 |
| MVP・PoC・ワイヤーフレームとの違い | プロトタイプは「使い方・見た目が伝わるか」を確かめる試作 | プロトタイプとMVP・PoC・ワイヤーフレームの違い |
| プロトタイプの作り方 | 確かめたいことを1つ決め、紙→画面ツール→HTML と忠実度を上げる | プロトタイプの作り方の手順 |
| JavaScript の prototype とは | オブジェクトに無いプロパティを探しに行く先のオブジェクト | JavaScriptのprototypeとは |
| prototype と __proto__ の違い | prototype は関数が持つ置き場所、__proto__ はインスタンスから見た借り先 | prototypeと__proto__と[[Prototype]]の違い |
| class があるのに prototype を学ぶ意味 | class の中身は prototype。エラーやコピーの不具合を読み解ける | classとprototypeの関係 |
| prototype でよく出るエラー | is not a function・cannot be invoked without 'new' など | よくあるエラー原文と直し方 |
確認環境:Ubuntu 24.04.5 LTS(2 CPU・メモリ 8GB の共用マシン)の上で、Node.js 22.23.4 を使い、この記事のために書いたスクリプト(prototype チェーンの表示、コンストラクター関数と class の比較、エラーの再現、--expose-gc を付けたメモリ測定、プロパティ参照の速さ、コピー方法の比較)を実行しました。ブラウザは Playwright 1.63.0 に付属する Chromium 153.0.8010.12(ヘッドレス)で、DOM 要素の prototype チェーンを調べ、HTML と CSS と少しの JavaScript で作った画面のプロトタイプを 400×520 の画面で開いてクリック・入力し、スクリーンショットを撮りました。[[Prototype]]・__proto__・class の位置づけは MDN Web Docs(日本語版)と ECMAScript の仕様、デザインツールのプロトタイプ機能は Figma の公式ページで確かめています。図は mermaid-cli 11.16.0 で描画を確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「プロトタイプとは|試作品とJavaScriptのprototypeを実行で解説」
プロトタイプとは(ふだんの意味とITの意味)
この節では、プロトタイプという言葉のもとの意味と、IT・プログラミングで使うときの意味の違いが分かります。
プロトタイプ(prototype)は、ギリシャ語の「最初の(protos)」と「型(typos)」から来た言葉で、日本語では「原型」「試作品」と訳されます。本番の前に一度作ってみて、形・使い心地・仕組みに問題がないかを確かめるためのものです。
ふだんの会話・ものづくり・IT・プログラミングで、指すものが少しずつ違います。次の表で並べます。
| 場面 | プロトタイプが指すもの | 例 |
|---|---|---|
| ふだんの意味 | 原型・試作品・ひな形 | 新しい車の試作車、新商品の試作 |
| ものづくり(ハードウェア) | 量産前に作る試作機 | 3D プリンターで出した外装、基板の試作 |
| Web・アプリの企画 | 画面を触って流れを確かめる試作 | ボタンで画面が切り替わるだけの予約画面 |
| システム開発の進め方 | 早く試作を見せて要望を固める進め方(プロトタイピング) | 試作を見ながら打ち合わせ、作り直して再確認 |
| JavaScript | オブジェクトがメソッドを借りに行く先のオブジェクト | Array.prototype.map |
| 設計パターン(GoF) | 既存のオブジェクトを複製して新しいオブジェクトを作る型 | 見本の設定を複製して少し変える |
上の4行は「本番の前に作って確かめるもの」という同じ考え方です。JavaScript と設計パターンの2行は「もとになるオブジェクト」という意味で、言葉の由来(原型)は同じでも、使い方はまったく別です。会話の中で「プロトタイプを作る」と言えば試作、「prototype に足す」と言えば JavaScript の話、と見分けられます。
場面別のプロトタイプの意味(試作品・UI・JavaScript)
この節では、場面ごとに「誰が、何のために」プロトタイプを使うのかが分かります。
製品の試作機
量産の前に形・大きさ・強度を確かめる。工場で作る前に問題を見つけ、作り直しの費用を抑える。
ハードウェア・家電・車
画面のプロトタイプ
Web やアプリの画面を、触れる形にして流れを確かめる。デザイナー・企画・開発者・利用者が同じものを見て話せる。
UI・UX の設計
プロトタイピング
試作を作って見せ、意見をもらって直す、を繰り返す開発の進め方。要望が言葉だけでは固まらないときに向く。
システム開発の進め方
JavaScript の prototype
オブジェクトが自分に無いメソッドを探しに行く先。配列の map や文字列の slice が使えるのはこの仕組み。
プログラミング
同じチームの中でも、企画の人は1と2、開発者は2と4の意味で使うことがあります。打ち合わせで「プロトタイプ」と出てきたら、「画面の試作のことか、コードの話か」を最初に確かめると、話のずれを防げます。
この記事の前半(次の4節)は試作品としてのプロトタイプ、後半は JavaScript の prototype を扱います。どちらか一方だけ知りたい場合は、早見表の「詳しい節」から読み進められます。
試作品としてのプロトタイプの種類(忠実度・紙・デジタル・HTML)
この節では、画面のプロトタイプにどんな種類があり、どれだけ作り込むか(忠実度)をどう決めるのかが分かります。
画面のプロトタイプは「どれだけ本物に近いか」を表す忠実度(フィデリティ)で、ローファイ(低い)とハイファイ(高い)に分けて考えます。見た目の近さと、操作できる範囲の近さは別々に決められます。たとえば「見た目はグレーの箱だけれど、ボタンで画面は切り替わる」試作もあります。
| 種類 | 作るもの | 向く場面 | 弱いところ |
|---|---|---|---|
| 紙のプロトタイプ | 手描きの画面を紙で用意し、指で押したら次の紙に差し替える | 最初の案出し・数分で何案も比べたい | 操作の速さ・動きの感覚は伝わらない |
| ローファイのデジタル | グレーの箱と文字だけの画面を、デザインツールでつなぐ | 画面の数・順番・ボタンの位置を決める | 色・文言の印象は確かめられない |
| ハイファイのデジタル | 色・画像・文言まで本番に近い画面をつなぎ、動きを付ける | 利用者テスト・社内の承認・見積もりの前 | 作るのに時間がかかり、直すのも重い |
| HTML(コード)のプロトタイプ | HTML・CSS・少しの JavaScript で、ブラウザで動く画面を作る | 入力・画面の大きさの違い・実機での確認 | 作る人に技術が要る。作り込むと捨てにくい |
デザインツールの例として、Figma の公式ページでは、クリック・ホバー・スクロールなどの操作でフレーム(画面)をつなぐ、変数や条件で動きを変える、画面の大きさを変えて確かめる、共有する、といった機能が紹介されています。同じページでは、プロトタイプを「最終的なデザインの具体的、視覚的、機能的な表現」と説明しています。
ローファイで足りる問い
画面はいくつ要るか、どの順に進むか
ボタン・入力欄はどこに置くか
案を3つ比べてどれがよいか
ハイファイが要る問い
利用者が迷わずに最後まで操作できるか
色・文言・画像の印象はどうか
スマホで指で押しやすいか
最初からハイファイで作ると、見た目の細かい意見ばかり集まり、「そもそもこの画面は要るか」という大きな問いが後回しになりがちです。確かめたいことが大きいほど、作り込みは浅くします。
プロトタイプとMVP・PoC・ワイヤーフレームの違い
この節では、似た言葉(MVP・PoC・ワイヤーフレーム・モックアップ)とプロトタイプの違いが、「何を確かめるか」で分かります。
プロトタイプは「使い方・見た目が伝わるか」を確かめる試作です。技術的にできるかを確かめるのが PoC、お客さまが本当に使い、お金を払うかを確かめるのが MVP です。
| 言葉 | 確かめる問い | 見せる相手 | 動くか |
|---|---|---|---|
| ワイヤーフレーム | 画面に何をどこに置くか | チームの中 | 動かない(配置の図) |
| モックアップ | 見た目(色・文字・画像)はどうか | チーム・発注者 | 動かない(見た目の絵) |
| プロトタイプ | 使い方・流れが伝わるか | 想定する利用者(少人数) | 触れる(画面が切り替わる程度) |
| PoC(概念実証) | その技術で実現できるか | 開発チーム・決裁者 | 技術の核だけ動く |
| MVP | お客さまが使い、払うか | 実際のお客さま | 最小の機能が本当に動く |
プロトタイプを作るときに避けたいこと
試作は答えを得るための道具で、製品の一部ではありません
MVP と PoC の違い、MVP の作り方は、関連記事「MVPとは|PoC・プロトタイプとの違いと作り方」で扱っています。この記事はプロトタイプそのもの(種類・作り方・JavaScript での意味)を扱い、MVP の進め方には立ち入りません。
ワイヤーフレームの書き方(要素の洗い出し・配置・テキストで作るひな形)は、関連記事「ワイヤーフレームの書き方:作り方の手順とテキストで作る例・テンプレート」で扱っています。ワイヤーフレームで配置を決め、その画面をつないで触れるようにしたものがプロトタイプ、という順番で使うと進めやすくなります。
プロトタイプの作り方の手順(画面の試作)
この節では、画面のプロトタイプを作るときの順番と、それぞれで決めることが分かります。
最初に「このプロトタイプで何を確かめるか」を1文で決めます。問いが決まらないまま作ると、何を見せても「よさそうですね」で終わり、次に何を直すかが決まりません。
確かめる問いを1文で書く
例:「会議室の予約が、3回のタップ以内で終わると感じてもらえるか」。数えられる言葉で書くと、見せたあとに判断しやすい。
画面の流れを書き出す
一覧→詳細→完了のように、問いに関係する画面だけを矢印でつなぐ。設定画面やログインなど、問いに関係しない画面は作らない。
忠実度を決める
流れを確かめたいならローファイ、迷わず操作できるかを確かめたいならハイファイ、入力や実機の感触なら HTML。前の節の表で選ぶ。
作る(データは固定でよい)
検索や保存は本当に動かさず、決まった表示を出すだけにする。動くように見えれば、問いには答えられる。
利用者に触ってもらい、黙って見る
「予約してみてください」とだけ頼み、迷った場所・押し間違えた場所を書き留める。説明を足すと、迷いが見えなくなる。
直すか、捨てるかを決める
問いへの答えが出たら、試作で分かったことを要件にまとめる。答えが出なければ、問いか試作のどちらかを変えてもう一度。
手順の5で見るのは「どこで迷ったか」です。利用者が「いいですね」と言っても、操作の途中で手が止まった場所があれば、そこが直す候補です。
HTMLとCSSで画面のプロトタイプを作る最小の例
この節では、デザインツールが無くても、テキストエディタとブラウザだけで触れるプロトタイプを作る方法が、実物で分かります。
次の1ファイル(48行・2,033バイト)で、会議室予約の「一覧→予約→完了」の3画面が切り替わるプロトタイプになります。データは固定で、保存もしません。画面の枠を点線にして、本番ではないことが一目で分かるようにしています。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>会議室予約 プロトタイプ</title>
<style>
body { font-family: sans-serif; margin: 0; background: #f3f4f6; }
.screen { display: none; max-width: 360px; margin: 24px auto; background: #fff; border: 2px dashed #9ca3af; padding: 16px; }
.screen.active { display: block; }
h2 { font-size: 18px; margin: 0 0 12px; }
.room { display: block; width: 100%; text-align: left; padding: 12px; margin-bottom: 8px; border: 1px solid #d1d5db; background: #fafafa; }
.primary { width: 100%; padding: 12px; background: #2563eb; color: #fff; border: 0; }
.note { color: #6b7280; font-size: 12px; }
</style>
</head>
<body>
<section class="screen active" id="list">
<h2>空いている会議室</h2>
<button class="room" data-go="detail">A会議室(6人)10:00〜11:00</button>
<button class="room" data-go="detail">B会議室(10人)13:00〜14:00</button>
<p class="note">※ 試作品です。データは固定です</p>
</section>
<section class="screen" id="detail">
<h2>A会議室を予約</h2>
<p>日時:10:00〜11:00</p>
<label>用件 <input id="purpose" placeholder="例:定例"></label>
<p><button class="primary" data-go="done">予約する</button></p>
<button data-go="list">戻る</button>
</section>
<section class="screen" id="done">
<h2>予約しました</h2>
<p id="msg"></p>
<button data-go="list">一覧へ</button>
</section>
<script>
document.addEventListener('click', (e) => {
const go = e.target.dataset.go;
if (!go) return;
if (go === 'done') {
document.getElementById('msg').textContent = '用件:' + (document.getElementById('purpose').value || '(未入力)');
}
document.querySelectorAll('.screen').forEach((s) => s.classList.toggle('active', s.id === go));
console.log('screen:', go, performance.now().toFixed(0) + 'ms');
});
</script>
</body>
</html>
3つの section のうち、active の付いた1つだけを表示し、data-go の付いたボタンを押すと表示する画面を切り替えます。これを Chromium(Playwright で操作)で開き、一覧のボタンを押す→用件に「週次定例」と入力→「予約する」を押す、の順に操作しました。
browser 153.0.8010.12
done text: 用件:週次定例
screen: detail 167ms
screen: done 252ms
| 画面 | 表示された内容(400×520 のスクリーンショット) | 画像の大きさ |
|---|---|---|
| 1. 一覧 | 見出し「空いている会議室」、ボタン2つ(A会議室・B会議室)、注記「※ 試作品です。データは固定です」 | 19,736 バイト |
| 2. 予約 | 見出し「A会議室を予約」、日時、用件の入力欄(週次定例)、青い「予約する」ボタン、「戻る」 | 16,422 バイト |
| 3. 完了 | 見出し「予約しました」、「用件:週次定例」、「一覧へ」 | 12,411 バイト |
このくらいの試作なら、ファイルをメールやチャットで渡して、相手のパソコンやスマホのブラウザで開いてもらえます。入力欄にスマホのキーボードが重なるか、文字が小さすぎないかといった「実機でしか分からないこと」も確かめられます。
自分で確かめる①:上のコードを proto.html という名前で保存し、ブラウザにドラッグして開きます。開発者ツール(Windows・Linux は F12、Mac は Command+Option+I)の「コンソール」を開いてからボタンを押すと、screen: detail 167ms のような行が出て、画面が切り替わったことと、ページを開いてからの時間が分かります。
JavaScriptのprototypeとは(プロトタイプチェーン)
この節では、JavaScript の prototype が何をしているのかと、プロトタイプチェーンの動きが、実行結果で分かります。
JavaScript のオブジェクトは、それぞれ「プロトタイプ」と呼ばれる別のオブジェクトへのつながりを持っています。プロパティやメソッドを読むとき、自分に無ければプロトタイプを、そこにも無ければさらにそのプロトタイプを、と null に着くまで順に探します。このつながりをプロトタイプチェーンと呼びます。MDN は JavaScript の継承を「オブジェクトを用いて」実装していると説明しています。
flowchart TD
I["dog(インスタンス)<br>name: ポチ"] -->|探す| D["Dog.prototype<br>speak()"]
D -->|無ければ| A["Animal.prototype<br>speak()"]
A -->|無ければ| O["Object.prototype<br>toString()"]
O -->|無ければ| N["null(undefined を返す)"]
いろいろな値のプロトタイプチェーンを、Node.js 22.23.4 で順にたどって表示しました。
// o の [[Prototype]] を null まで順にたどって名前を並べる
function chain(o) {
const names = [];
let p = Object.getPrototypeOf(o);
while (p) {
names.push(p.constructor.name + '.prototype');
p = Object.getPrototypeOf(p);
}
return names.concat('null').join(' -> ');
}
class Animal { constructor(n) { this.name = n; } speak() { return this.name + ' が鳴く'; } }
class Dog extends Animal { speak() { return this.name + ' がワンと鳴く'; } }
console.log('{} :', chain({ city: 'Tokyo' }));
console.log('[] :', chain([1, 2]));
console.log('"abc" :', chain('abc'));
console.log('function :', chain(function () {}));
console.log('new Date() :', chain(new Date()));
console.log('new Dog() :', chain(new Dog('ポチ')));
console.log('Object.create(null):', chain(Object.create(null)));
const lit = { city: 'Tokyo' };
console.log(lit.toString(), Object.hasOwn(lit, 'city'), Object.hasOwn(lit, 'toString'), 'toString' in lit);
console.log(new Dog('ポチ').speak());
{} : Object.prototype -> null
[] : Array.prototype -> Object.prototype -> null
"abc" : String.prototype -> Object.prototype -> null
function : Function.prototype -> Object.prototype -> null
new Date() : Date.prototype -> Object.prototype -> null
new Dog() : Dog.prototype -> Animal.prototype -> Object.prototype -> null
Object.create(null): null
[object Object] true false true
ポチ がワンと鳴く
1どの値も最後は Object.prototype
配列・文字列・関数・日付・自作の class も、たどると Object.prototype を経て null で終わった。toString が何にでも使えるのはこのため。
2toString は借り物
{ city } に toString を書いていないのに "[object Object]" が返った。Object.hasOwn は false、in 演算子は true。自分の物か借り物かを見分けられる。
3先に見つかった方が使われる
Dog と Animal の両方に speak があり、近い Dog.prototype の speak が呼ばれた。これを「シャドウ化(覆い隠す)」と呼ぶ。
文字列 "abc" は本来プロパティを持たない値(プリミティブ)ですが、メソッドを呼ぶ瞬間だけ String のオブジェクトとして扱われるため、String.prototype の slice や includes が使えます。Object.create(null) で作ったオブジェクトは、つながりが最初から無く、toString も持っていません。
ブラウザでも同じ仕組みで動いています。Chromium 153 で、ページの body 要素とボタン要素のチェーンを表示しました。
body : HTMLBodyElement.prototype -> HTMLElement.prototype -> Element.prototype -> Node.prototype -> EventTarget.prototype -> Object.prototype -> null
button : HTMLButtonElement.prototype -> HTMLElement.prototype -> Element.prototype -> Node.prototype -> EventTarget.prototype -> Object.prototype -> null
click の置き場所 : HTMLElement
appendChild の置き場所 : Node
addEventListener の置き場所 : EventTarget
document.body.click() の click は body 自身ではなく HTMLElement.prototype に、addEventListener はさらに上の EventTarget.prototype にありました。どの要素でも addEventListener が使えるのは、すべての要素のチェーンが EventTarget.prototype を通るからです。
自分で確かめる②:ブラウザで開発者ツールのコンソールを開き、Object.getPrototypeOf(document.body).constructor.name と1行打ちます。手元の Chromium では 'HTMLBodyElement' と返りました。Object.hasOwn(document.body, 'click') は false で、click が借り物だと分かります。
prototypeと__proto__と[[Prototype]]の違い
この節では、名前の似た3つ(prototype・__proto__・[[Prototype]])が何を指すのかと、今どれを使うべきかが分かります。
[[Prototype]] は「このオブジェクトの借り先」を表す仕様上の名前、__proto__ はそれを読み書きする以前の書き方、prototype は関数(コンストラクター)が持つ「インスタンスの借り先にするオブジェクト」です。混ざりやすいのは、new で作ったインスタンスの [[Prototype]] が、コンストラクターの prototype と同じオブジェクトになるためです。
| 名前 | 何か | 持っているもの | 今の書き方 |
|---|---|---|---|
| [[Prototype]] | ECMAScript の仕様で使う内部の名前。借り先へのつながり | すべてのオブジェクト | 直接は書けない |
| __proto__ | [[Prototype]] を読み書きするアクセサー | Object.prototype を借りているオブジェクト | Object.getPrototypeOf・Object.setPrototypeOf |
| prototype | new で作るインスタンスの借り先になるオブジェクト | function で作った関数と class | class のメソッド定義 |
コンストラクター関数で実際に確かめました。最後の2行が、3つの関係をそのまま表しています。
// 1. コンストラクター関数と prototype
function Person(name){ this.name = name; }
Person.prototype.greet = function(){ return 'こんにちは、' + this.name + 'です'; };
const a = new Person('佐藤'), b = new Person('鈴木');
console.log(a.greet(), '|', b.greet());
console.log('共有している:', a.greet === b.greet);
console.log('自分のプロパティ:', Object.keys(a), Object.hasOwn(a,'greet'));
// 2. あとから prototype に足すと、作成済みのインスタンスにも効く
Person.prototype.bye = function(){ return this.name + ':さようなら'; };
console.log(a.bye());
// 3. シャドウ化
a.greet = function(){ return '上書きした greet'; };
console.log(a.greet(), '|', b.greet());
delete a.greet; console.log('delete後:', a.greet());
// 4. class の正体
class Task { constructor(t){ this.title = t; } done(){ return this.title + ' 完了'; } }
console.log('typeof Task =', typeof Task);
console.log('done は prototype にある:', Object.hasOwn(Task.prototype,'done'), Object.getOwnPropertyNames(Task.prototype));
console.log('列挙できるか(class):', Object.keys(Task.prototype), '(関数+prototype):', Object.keys(Person.prototype));
const t = new Task('資料');
console.log(Object.getPrototypeOf(t) === Task.prototype, t instanceof Task, Task.prototype.constructor === Task);
// 5. extends は prototype を2本つなぐ
class Sub extends Task {}
console.log(Object.getPrototypeOf(Sub.prototype) === Task.prototype, Object.getPrototypeOf(Sub) === Task);
// 6. Object.create
const proto = { hello(){ return 'hi from ' + this.n; } };
const o = Object.create(proto); o.n = 'o';
console.log(o.hello(), Object.getPrototypeOf(o) === proto);
// 7. アロー関数とメソッド記法には prototype が無い
console.log('arrow.prototype =', (()=>{}).prototype, ' method.prototype =', ({m(){}}).m.prototype, ' function.prototype =', typeof (function(){}).prototype);
// 8. prototype と __proto__ の違い
console.log('Person.prototype === a.__proto__ :', Person.prototype === a.__proto__);
console.log('Person.__proto__ === Function.prototype :', Person.__proto__ === Function.prototype);
こんにちは、佐藤です | こんにちは、鈴木です
共有している: true
自分のプロパティ: [ 'name' ] false
佐藤:さようなら
上書きした greet | こんにちは、鈴木です
delete後: こんにちは、佐藤です
typeof Task = function
done は prototype にある: true [ 'constructor', 'done' ]
列挙できるか(class): [] (関数+prototype): [ 'greet', 'bye' ]
true true true
true true
hi from o true
arrow.prototype = undefined method.prototype = undefined function.prototype = object
Person.prototype === a.__proto__ : true
Person.__proto__ === Function.prototype : true
Person.prototype === a.__proto__ が true で、インスタンス a の借り先は Person.prototype です。一方、Person.__proto__(関数 Person 自身の借り先)は Function.prototype で、Person.prototype とは別物です。MDN も「Constructor.prototype は、コンストラクターでインスタンスを作成するときにのみ有用」で、Constructor 自身の [[Prototype]] とは関係ないと書いています。
__proto__ について、MDN の Object.prototype.__proto__ のページは「非推奨」と示し、取得と設定には Object.getPrototypeOf() と Object.setPrototypeOf() を勧めています。ただし、オブジェクトリテラルの中に { __proto__: 親 } と書く形は標準の構文で、非推奨ではありません。
借り先を変えるとき
obj.__proto__ = 別のもの と代入する。非推奨のうえ、エンジンの最適化が無効になるObject.create(親) か class の extends で借り先を決める。読むときは Object.getPrototypeOfMDN:「可能であれば作成中にプロトタイプを設定すべき」
classとprototypeの関係(classの正体を確かめる)
この節では、class 構文が prototype とどうつながっているのかと、class だけ覚えていても prototype を知っておくとよい理由が分かります。
class は prototype の仕組みを書きやすくした構文で、新しい継承の仕組みではありません。MDN も、クラスは「コンストラクター関数の上の糖衣構文」で「新しい継承パターンをもたらすわけではありません」と説明しています。前の節の出力で、それを確かめた行を抜き出します。
| 確かめたこと | 出力 | 分かること |
|---|---|---|
| typeof Task | function | class の正体は関数 |
| Task.prototype の中身 | [ 'constructor', 'done' ] | class に書いたメソッドは prototype に入る |
| Object.keys(Task.prototype) | [] | class のメソッドは列挙されない(for...in に出ない) |
| Object.keys(Person.prototype) | [ 'greet', 'bye' ] | 代入で足したメソッドは列挙される |
| extends したあとの Sub.prototype の借り先 | Task.prototype(true) | extends は prototype どうしをつなぐ |
| アロー関数の prototype | undefined | アロー関数は new できない |
大きな違いは2つあります。class のメソッドは列挙されないこと、そして class は new を付けずに呼ぶとエラーになることです(エラー原文は後の節)。それ以外の、メソッドを共有する・あとから足すと作成済みのインスタンスにも効く・近い方が優先される、といった動きは、コンストラクター関数と同じです。
自分で確かめる③:ターミナルで次の1行を実行すると、class に書いたメソッドが prototype に入っていることが分かります。
node -e "class A{m(){}}; console.log(Object.getOwnPropertyNames(A.prototype))"
# 出力
[ 'constructor', 'm' ]
class の書き方(private・static・継承)は、関連記事「JavaScriptクラス入門|private・static・継承を比較表で解説」で扱っています。この記事では、その裏で動いている prototype を見ることに絞ります。
prototypeにメソッドを置く理由(メモリの実測)
この節では、メソッドを prototype に置く場合と、インスタンスごとに関数を持たせる場合で、メモリの使い方がどれだけ違うかが分かります。
prototype に置いたメソッドは、何個インスタンスを作っても1つを共有します。コンストラクターの中で this.a = function(){} と書いたり、class のフィールドにアロー関数を書いたりすると、インスタンスごとに関数が作られます。メソッド3つを持つオブジェクトを20万個作り、ガベージコレクションのあとのヒープの増え方を測りました(node --expose-gc、2回実行して同じ値)。
const N = 200000;
function measure(label, make){ global.gc(); const before = process.memoryUsage().heapUsed; const keep = []; for (let i=0;i<N;i++) keep.push(make(i)); global.gc(); const after = process.memoryUsage().heapUsed; console.log(label.padEnd(28), ((after-before)/1024/1024).toFixed(1)+' MB', ' 1個あたり', Math.round((after-before)/N)+' B'); return keep.length; }
function OnProto(n){ this.n = n; }
OnProto.prototype.a = function(){ return this.n+1; }; OnProto.prototype.b = function(){ return this.n+2; }; OnProto.prototype.c = function(){ return this.n+3; };
function PerInstance(n){ this.n = n; this.a = function(){ return this.n+1; }; this.b = function(){ return this.n+2; }; this.c = function(){ return this.n+3; }; }
class ClassArrow { n; a = () => this.n+1; b = () => this.n+2; c = () => this.n+3; constructor(n){ this.n = n; } }
class ClassMethod { constructor(n){ this.n = n; } a(){ return this.n+1; } b(){ return this.n+2; } c(){ return this.n+3; } }
measure('prototype にメソッド', i => new OnProto(i));
measure('class のメソッド', i => new ClassMethod(i));
measure('コンストラクター内で関数', i => new PerInstance(i));
measure('class フィールドにアロー関数', i => new ClassArrow(i));
| メソッドの置き方 | 20万個の増加 | 1個あたり | 関数の数 |
|---|---|---|---|
| prototype にメソッド | 8.1 MB | 42 B | 3個(共有) |
| class のメソッド | 8.1 MB | 42 B | 3個(共有) |
| コンストラクター内で関数 | 49.3 MB | 258 B | 60万個 |
| class フィールドにアロー関数 | 52.3 MB | 274 B | 60万個 |
アロー関数のフィールドは、イベントハンドラーに渡しても this がずれない便利さがあります。数個しか作らない画面の部品なら差は小さく、数万個作るデータのオブジェクトでは prototype(class のメソッド)に置く方が向いています。this がずれる理由と直し方は、関連記事「JavaScript thisとは?早見表とbind・call・apply」で扱っています。
参照の速さも測りました。自分のプロパティ、1段上、10段上、100段上の値を1億回読むと、1回目は 75・132・160・228 ミリ秒、2回目は 129・183・206・176 ミリ秒でした。実行ごとのばらつきの方が大きく、1回あたりにすると1ナノ秒前後の差です。MDN はチェーンの上の方にあるプロパティの参照や、存在しないプロパティの探索が性能に影響しうると書いていますが、ふつうの深さ(数段)のチェーンで気にするほどの差は、手元では出ませんでした。
prototypeでつまずきやすい点(組み込みの拡張・コピーで消える)
この節では、prototype を知らないと原因が分かりにくい3つの不具合と、その避け方が分かります。
組み込みの prototype にメソッドを足すと、for...in に出てくる
配列に「最後の要素を返す」メソッドを足したくなって Array.prototype.last = ... と書くと、すべての配列で使えるようになります。その代わり、for...in で配列を回すと、足したメソッド名まで出てきます。
const arr = ['a','b'];
Array.prototype.last = function(){ return this[this.length-1]; };
const forin = []; for (const i in arr) forin.push(i);
const forof = []; for (const v of arr) forof.push(v);
console.log('for...in :', forin, ' for...of :', forof, ' last():', arr.last());
delete Array.prototype.last;
Object.defineProperty(Array.prototype, 'last2', { value(){ return this[this.length-1]; }, enumerable:false, configurable:true });
const forin2 = []; for (const i in arr) forin2.push(i);
console.log('enumerable:false で足すと for...in :', forin2);
delete Array.prototype.last2;
// 辞書として使うときの違い
const d1 = {}; const d2 = Object.create(null); const m = new Map();
console.log('"toString" in {}:', 'toString' in d1, ' in Object.create(null):', 'toString' in d2, ' Map.has:', m.has('toString'));
console.log('JSON.parse の __proto__ :', Object.getPrototypeOf(JSON.parse('{"__proto__":{"x":1}}')) === Object.prototype, Object.hasOwn(JSON.parse('{"__proto__":{"x":1}}'), '__proto__'));
for...in : [ '0', '1', 'last' ] for...of : [ 'a', 'b' ] last(): b
enumerable:false で足すと for...in : [ '0', '1' ]
"toString" in {}: true in Object.create(null): false Map.has: false
JSON.parse の __proto__ : true true
MDN は、組み込みの prototype を拡張すること(モンキーパッチ)を、新しい機能を以前のエンジンで使えるようにする目的を除いて「決して」行わないよう書いています。将来、同じ名前のメソッドが言語に追加されたとき、動きがぶつかるためです。配列には for...of を使い、便利な処理は普通の関数として書きます。
コピーすると prototype が消える
class のインスタンスをコピーするとき、方法によってはメソッドが使えなくなります。4つの方法で試しました。
class Point { constructor(x,y){ this.x=x; this.y=y; } len(){ return Math.hypot(this.x,this.y); } }
const p = new Point(3,4);
const a = structuredClone(p), b = { ...p }, c = JSON.parse(JSON.stringify(p));
const d = Object.assign(Object.create(Object.getPrototypeOf(p)), p);
for (const [k,v] of Object.entries({structuredClone:a, 'スプレッド {...p}':b, 'JSON 往復':c, 'Object.create+assign':d}))
console.log(k.padEnd(22), 'instanceof Point:', v instanceof Point, ' len:', typeof v.len === 'function' ? v.len() : 'なし');
structuredClone instanceof Point: false len: なし
スプレッド {...p} instanceof Point: false len: なし
JSON 往復 instanceof Point: false len: なし
Object.create+assign instanceof Point: true len: 5
structuredClone・スプレッド構文・JSON の往復は、どれもデータだけを持つ普通のオブジェクトになり、len() が消えました。コピーされるのは自分のプロパティ(x と y)だけで、借り先のつながりは写されないためです。クラスとして使い続けたい場合は、借り先を指定して作る(Object.create+Object.assign)か、new Point(p.x, p.y) のように作り直します。API から受け取った JSON を class に戻すときも同じで、型を判定する前に作り直しが要ります。型の判定(typeof・instanceof)の使い分けは、関連記事「JavaScript型判定 typeof・instanceof・null早見表」で扱っています。
辞書として使うオブジェクトに、借り物のキーが混ざる
出力の3行目のとおり、{} には書いていない "toString" が in 演算子で見つかります。利用者の入力をキーにする辞書では、借り物のキーと区別するために Object.hasOwn を使うか、借り先の無い Object.create(null)、または Map を使います。外から来た JSON をそのままオブジェクトに混ぜ込む処理は、組み込みの prototype を書き換えてしまう不具合(プロトタイプ汚染と呼ばれます)の原因になるため、キーを決めて取り出す形にします。
- 1組み込みの prototype にメソッドを足さない便利な処理は普通の関数かユーティリティのモジュールにする
- 2配列は for...of・forEach で回すfor...in は借り物の列挙可能なプロパティまで回る
- 3class のインスタンスをコピーしたらメソッドを確かめるstructuredClone・スプレッド・JSON は prototype を写さない
- 4辞書には Map か Object.create(null)自分のキーかは Object.hasOwn で確かめる
- 5借り先は作るときに決めるあとから __proto__ や setPrototypeOf で変えない
よくあるエラー原文と直し方(prototype・class)
この節では、prototype や class のまわりで出るエラーの原文と、原因・直し方が分かります。
Node.js 22.23.4 で、10通りの書き間違いを実際に起こしました。エラーの文面は Chromium でも同じ V8 エンジンのため同じ形で出ます(Firefox・Safari では文面が異なります)。
const cases = {
'class を new なしで呼ぶ': () => { class Foo{}; Foo(); },
'アロー関数を new': () => { const F = () => {}; new F(); },
'prototype に無いメソッド': () => { const o = {}; o.greet(); },
'undefined の prototype': () => { let C; C.prototype.x = 1; },
'setPrototypeOf に数値': () => { Object.setPrototypeOf({}, 1); },
'Object.create に文字列': () => { Object.create('a'); },
'循環するチェーン': () => { const a = {}, b = Object.create(a); Object.setPrototypeOf(a, b); },
'extends に関数でない値': () => { const X = 1; class Y extends X {} },
'super() 前に this': () => { class A{}; class B extends A { constructor(){ this.x = 1; super(); } }; new B(); },
'freeze した prototype に代入(strict)': () => { 'use strict'; const p = Object.freeze({x:1}); const o = Object.create(p); o.x = 2; },
};
for (const [k, f] of Object.entries(cases)) { try { f(); console.log(k, '=> エラーなし'); } catch (e) { console.log(k, '=>', e.constructor.name + ': ' + e.message); } }
class を new なしで呼ぶ => TypeError: Class constructor Foo cannot be invoked without 'new'
アロー関数を new => TypeError: F is not a constructor
prototype に無いメソッド => TypeError: o.greet is not a function
undefined の prototype => TypeError: Cannot read properties of undefined (reading 'prototype')
setPrototypeOf に数値 => TypeError: Object prototype may only be an Object or null: 1
Object.create に文字列 => TypeError: Object prototype may only be an Object or null: a
循環するチェーン => TypeError: Cyclic __proto__ value
extends に関数でない値 => TypeError: Class extends value 1 is not a constructor or null
super() 前に this => ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
freeze した prototype に代入(strict) => TypeError: Cannot assign to read only property 'x' of object '#<Object>'
| エラー原文(要点) | 原因 | 直し方 |
|---|---|---|
| Class constructor Foo cannot be invoked without 'new' | class を関数のように呼んだ | new Foo() で呼ぶ |
| F is not a constructor | アロー関数・メソッド記法の関数を new した(prototype が無い) | function か class で定義する |
| o.greet is not a function | チェーンのどこにもそのメソッドが無い。コピーで prototype が消えた場合も | 綴りと、Object.getPrototypeOf(o) の中身を確かめる |
| Cannot read properties of undefined (reading 'prototype') | コンストラクターの変数が undefined(読み込み前・名前違い) | import・読み込み順・変数名を確かめる |
| Object prototype may only be an Object or null: 1 | setPrototypeOf・Object.create に数値や文字列を渡した | オブジェクトか null を渡す |
| Cyclic __proto__ value | チェーンが輪になる設定をした | 借り先の関係を見直す |
| Class extends value 1 is not a constructor or null | extends の右に class・関数でない値(読み込みの循環で undefined の場合も) | 親クラスの import を確かめる |
| Must call super constructor in derived class before accessing 'this' | 子クラスの constructor で super() の前に this を使った | super() を最初に書く |
| Cannot assign to read only property 'x' of object '#<Object>' | 借り先の x が書き込み禁止(凍結)で、strict モードで代入した | Object.defineProperty で自分のプロパティとして定義する |
最後の例は見落としやすい動きです。借り先のプロパティが書き込み禁止だと、借りている側に同じ名前で代入することもできません。strict モードでない場合はエラーにならず、代入が黙って無視されます(手元では o.x は 1 のままで、自分のプロパティにもなりませんでした)。
- 1エラー文の主語を読む「何が」関数でない・コンストラクターでないのかを確かめる
- 2その値を console.log で出すundefined なら読み込み・名前の問題
- 3Object.getPrototypeOf でチェーンを見るObject.prototype だけならコピーで class が外れている
- 4定義のしかたを見るアロー関数・メソッド記法は new できない
判断表:どのプロトタイプを作るか・prototypeを直接さわるか
この節では、試作品としてのプロトタイプをどこまで作るかと、JavaScript で prototype を直接さわるべき場面が、表で判断できます。
| 状況 | 選ぶもの | 理由 |
|---|---|---|
| 画面の数や順番がまだ決まっていない | 紙かローファイのプロトタイプ | 数分で何案も作れ、捨てやすい |
| 利用者が迷わず操作できるかを見たい | ハイファイのプロトタイプ | 見た目が粗いと、迷いの原因が見た目か流れか分からない |
| スマホの実機・入力の感触を見たい | HTML のプロトタイプ | 実際のブラウザ・キーボードで確かめられる |
| 技術的にできるかが分からない | PoC(プロトタイプではない) | 画面より、技術の核だけを先に試す |
| お客さまが使うか・払うかを知りたい | MVP | 触れる試作では「使い続けるか」は分からない |
| JavaScript でメソッドを共有したい | class のメソッド | prototype に入り、書き方も読みやすい |
| 借り先を指定した軽いオブジェクトを作りたい | Object.create | class を作るほどではない場面に向く |
| 組み込みの型に機能を足したい | 普通の関数(prototype は拡張しない) | 将来の言語機能とぶつかる |
どちらの意味でも、共通する考え方は「もとになるものを先に作り、そこから確かめる・借りる」です。試作品は確かめたら捨て、JavaScript の prototype は class に任せて直接は書き換えない、と覚えておくと迷いにくくなります。
よくある質問
この節では、プロトタイプについて検索でよく聞かれる疑問の答えが分かります。
Q. プロトタイプとは簡単に言うと何ですか?
本番の前に作って確かめる「試作品」です。JavaScript の話では、オブジェクトがメソッドを借りに行く先のオブジェクトを指します。
Q. プロトタイプとサンプルの違いは何ですか?
サンプルは完成品と同じものを見本として渡すことが多いのに対し、プロトタイプは完成前に問題を見つけるための試作で、本番とは作りが違ってもかまわないものです。
Q. プロトタイプとモックアップの違いは何ですか?
モックアップは見た目を確かめる動かない絵、プロトタイプは画面が切り替わるなど触れる試作です。
Q. プロトタイプは誰が作りますか?
画面の試作はデザイナーや企画の担当者がデザインツールで作ることが多く、入力や実機の感触を見たいときはエンジニアが HTML で作ります。紙のプロトタイプなら誰でも作れます。
Q. プロトタイプを作るのにどれくらい時間をかけますか?
確かめたい問いによります。画面の流れを見るだけなら紙で数十分、この記事の HTML の例は48行です。作り込むほど捨てにくくなるため、問いに答えられる最小の範囲にとどめます。
Q. JavaScript の prototype は今も使いますか?
直接書く機会は減りましたが、class もその中身は prototype です。エラーの読み解き、コピーでメソッドが消える不具合、ライブラリのコードを読むときに知識が役立ちます。
Q. prototype と __proto__ のどちらを使えばよいですか?
借り先を読むなら Object.getPrototypeOf を使います。__proto__ のアクセサーは MDN で非推奨とされています。メソッドを定義するなら class に書きます。
Q. JavaScript はクラスベースではないのですか?
JavaScript はプロトタイプで継承する言語で、class 構文はその上に作られた書き方です。Java のクラスとは仕組みが異なります。
Q. Object.create(null) は何に使いますか?
借り先の無いオブジェクトを作ります。toString なども持たないため、利用者の入力をキーにする辞書で、借り物のキーと混ざらないようにするときに使います。Map でも同じ目的を果たせます。
Q. 自分のオブジェクトのプロトタイプを確かめるには?
ブラウザの開発者ツールのコンソールか Node.js で Object.getPrototypeOf(値) を実行します。.constructor.name を付けると名前で表示されます。
まとめ
この節では、記事の要点を5つにまとめて振り返れます。
- プロトタイプとは、本番の前に作って確かめる試作品です。JavaScript では、オブジェクトがメソッドを借りに行く先のオブジェクトを指し、意味が分かれます。
- 画面のプロトタイプは忠実度で選びます。確かめたいことが大きいほど浅く作り、確かめたら捨てる前提にします。
- MVP・PoC・ワイヤーフレームとは「何を確かめるか」で分けます。プロトタイプは使い方・流れが伝わるかを確かめます。
- JavaScript の値は、どれもプロトタイプチェーンをたどって Object.prototype、null に着きます。class の中身も prototype で、メソッドを共有すると20万個で 49.3 MB と 8.1 MB の差が出ました。
- 組み込みの prototype を拡張しない、コピーでメソッドが消えることを知っておく、借り先は作るときに決める、の3つで多くの不具合を避けられます。
参考・出典
この節では、この記事で確かめた公式の資料が分かります(確認日 2026年10月29日)。
- MDN Web Docs「継承とプロトタイプチェーン」https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
- MDN Web Docs「オブジェクトのプロトタイプ」https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/Object_prototypes
- MDN Web Docs「Object.prototype.__proto__」https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
- ECMAScript Language Specification(TC39)https://tc39.es/ecma262/
- Figma「プロトタイピング」https://www.figma.com/ja-jp/prototyping/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「プロトタイプとは|試作品とJavaScriptのprototypeを実行で解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。