JavaScriptのObject.createの使い方!プロトタイプ継承を柔軟に制御する

JavaScriptのObject.createメソッドは、新しいオブジェクトを作成するときに、そのプロトタイプオブジェクトを指定するために使用されます。

これにより、既存のオブジェクトを新しいオブジェクトのプロトタイプとして設定することで、プロトタイプ継承の仕組みを柔軟に制御できます。
new演算子を使ったオブジェクト作成とは異なり、コンストラクタ関数を介さずに直接プロトタイプチェーンを構築できるため、特定の継承パターンや純粋な辞書オブジェクトを作成する際に非常に強力です。

この記事では、Object.createメソッドの基本的な使い方、主要な使用例、そして使用する際の重要な注意点について解説します。

Object.createメソッドの基本的な構文

Object.createメソッドの基本的な構文は以下の通りです。

Object.create(proto, [propertiesObject])
  • proto: 必須。新しく作成されるオブジェクトのプロトタイプとなるオブジェクトです。
    • オブジェクトであるか、null でなければなりません。
    • null を指定した場合、プロトタイプチェーンを持たない(Object.prototypeを継承しない)純粋なオブジェクトが作成されます。
  • propertiesObject: オプション。新しく作成されるオブジェクトに直接追加されるプロパティを定義するオブジェクトです。
    • この引数は、Object.defineProperties() の第2引数と同じ形式を取ります。つまり、プロパティディスクリプタ(value, writable, enumerable, configurable)を含むオブジェクトです。

proto:プロトタイプを指定する

  • 新しく作成されるオブジェクトが継承するプロトタイプを指定します。
  • null を指定すると、プロトタイプチェーンの終端となり、Object.prototype のメソッド(例: toString(), hasOwnProperty() など)を継承しないオブジェクトが作成されます。このようなオブジェクトは、純粋なキー/値のペアを格納する辞書として使用されることがあります。

propertiesObject:プロパティを定義する

  • 新しいオブジェクトに直接追加したいプロパティを定義します。
  • 各プロパティは、キーと、そのプロパティの属性を定義するディスクリプタオブジェクトのペアとして指定されます。
    • value: プロパティの値。
    • writable: true の場合、プロパティの値を変更できる。デフォルトは false
    • enumerable: true の場合、for...in ループや Object.keys() で列挙できる。デフォルトは false
    • configurable: true の場合、プロパティのディスクリプタを変更したり、プロパティを削除したりできる。デフォルトは false
    • get: ゲッター関数。プロパティが読み取られたときに呼び出される。
    • set: セッター関数。プロパティが設定されたときに呼び出される。

Object.createの戻り値

指定されたプロトタイプとプロパティを持つ新しいオブジェクトが返されます。

Object.createメソッドを使ってみる

実際にObject.createを使って、動作を確認していきます。

例1:基本的なプロトタイプ継承

既存のオブジェクトをプロトタイプとして新しいオブジェクトを作成します。

const animal = {
  eats: true,
  walk() {
    console.log("動物が歩きます。");
  }
};

// animal をプロトタイプとして新しいオブジェクト rabbit を作成
const rabbit = Object.create(animal);

rabbit.jumps = true; // rabbit 自身のプロパティを追加

console.log(rabbit.eats);  // 出力: true (animal から継承)
rabbit.walk();             // 出力: 動物が歩きます。(animal から継承)
console.log(rabbit.jumps); // 出力: true (rabbit 自身のプロパティ)

console.log(Object.getPrototypeOf(rabbit) === animal); // 出力: true

rabbitanimalのプロパティとメソッドを継承していますが、jumpsrabbit自身のプロパティです。
このように既存のオブジェクトを継承しつつ、新しいプロパティやメソッドを追加できます。

例2:プロパティディスクリプタを使用してプロパティを追加する

新しいオブジェクトを作成する際に、同時にプロパティとその属性を定義します。

const baseUser = {
  role: 'guest',
  greet() {
    console.log(`Hello, I am a ${this.role}.`);
  }
};

// baseUser をプロトタイプとし、name と age プロパティを定義
const adminUser = Object.create(baseUser, {
  name: {
    value: 'Admin User',
    writable: true, // 変更可能
    enumerable: true, // 列挙可能
    configurable: true // 削除・再設定可能
  },
  age: {
    value: 40,
    writable: false, // 変更不可
    enumerable: true,
    configurable: false
  },
  role: { // プロトタイプのroleを上書きする自身のプロパティ
    value: 'admin',
    writable: true,
    enumerable: true,
    configurable: true
  }
});

adminUser.greet(); // 出力: Hello, I am a admin. (adminUser自身のroleが優先)
console.log(adminUser.name); // 出力: Admin User

adminUser.name = 'Super Admin'; // writable: true なので変更可能
console.log(adminUser.name); // 出力: Super Admin

// adminUser.age = 41; // writable: false なのでエラー (厳格モードの場合)
// console.log(adminUser.age);

console.log(Object.getPrototypeOf(adminUser) === baseUser); // 出力: true

adminUserbaseUserを継承しつつ、nameage、そしてroleという自身のプロパティを持っています。
roleを加えることで、baseUserroleをシャドウ(上書き)しています。

adminUser.agewritable: falseなので変更できません。
出力結果を見るとroleが上書きされていること、nameを追加して上書きできることが確認できました。

Hello, I am a admin.
Admin User
Super Admin
true

例3:nullをプロトタイプとして使用する(純粋な辞書オブジェクト)

プロトタイプチェーンを持たない、完全に空のオブジェクト(Object.prototypeのプロパティを継承しない)を作成します。

// プロトタイプチェーンを持たない純粋な辞書オブジェクトを作成
const pureDictionary = Object.create(null);

pureDictionary.key1 = 'value1';
pureDictionary.key2 = 123;

console.log(pureDictionary.key1); // 出力: value1

// Object.prototype のメソッドは継承しないため、存在しない
// console.log(pureDictionary.toString()); // エラー: pureDictionary.toString is not a function

// hasOwnProperty は存在しないが、Object.prototypeから直接呼び出すことは可能
console.log(Object.prototype.hasOwnProperty.call(pureDictionary, 'key1')); // 出力: true

console.log(Object.getPrototypeOf(pureDictionary)); // 出力: null

pureDictionaryObject.prototypeを継承しないため、toString()hasOwnProperty()などの標準メソッドを持ちません。
これは、JSONデータのように純粋なキーと値のペアを扱う場合に、予期せぬプロトタイププロパティによる干渉を防ぐのに役立ちます。

例4:既存のオブジェクトをクローンする(シャローコピー)

Object.assign({}, original)と同様に、Object.createもオブジェクトのシャローコピーを作成するために使用できます。

const originalObj = {
  a: 1,
  b: { c: 2 },
  d: [3, 4]
};

// originalObj をプロトタイプとして新しいオブジェクトを作成し、
// originalObj の自身のプロパティを新しいオブジェクトにコピー
const clonedObj = Object.create(Object.getPrototypeOf(originalObj), Object.getOwnPropertyDescriptors(originalObj));

console.log(clonedObj);
// 出力: { a: 1, b: { c: 2 }, d: [ 3, 4 ] }

// シャローコピーの挙動を確認
clonedObj.a = 10;
clonedObj.b.c = 20; // ネストされたオブジェクトは参照がコピーされるため、元のオブジェクトも変更される
clonedObj.d.push(5); // ネストされた配列も参照がコピーされるため、元のオブジェクトも変更される

console.log(originalObj.a); // 出力: 1 (変更なし)
console.log(originalObj.b.c); // 出力: 20 (変更された!)
console.log(originalObj.d); // 出力: [ 3, 4, 5 ] (変更された!)

この例では、Object.getPrototypeOf(originalObj)で元のプロトタイプを取得し、Object.getOwnPropertyDescriptors(originalObj)で元のオブジェクトの全プロパティディスクリプタを取得して新しいオブジェクトにコピーしています。
これにより、originalObjのプロパティがclonedObj自身のプロパティとしてコピーされます。

しかし、Object.assignと同様に、これはシャローコピーであるため、ネストされたオブジェクトや配列の変更は元のオブジェクトにも影響します。

Object.createを使う際の重要な注意点

Object.createを使うときの注意点です。

シャローコピーであること

Object.createは、propertiesObjectを使ってプロパティを直接定義する場合も、プロトタイプとしてオブジェクトを継承する場合も、シャローコピーの原則に従います。
ネストされたオブジェクトや配列は参照渡しでコピーされるため、深いコピー(ディープコピー)が必要な場合は、別の方法(JSON.parse(JSON.stringify(obj)) やライブラリなど)を検討してください。

コンストラクタは呼び出されない

new演算子とは異なり、Object.createはプロトタイプとして指定されたオブジェクトのコンストラクタ関数を呼び出しません。
これは、コンストラクタ内で初期化処理が行われるオブジェクトを扱う場合に注意が必要です。

プロトタイプチェーンの理解が必須

Object.createを効果的に使うには、JavaScriptのプロトタイプ継承の仕組みを深く理解している必要があります。

nullをプロトタイプにする場合

Object.create(null)で作成されたオブジェクトは、Object.prototypeからのメソッドを一切継承しません。
これは、hasOwnPropertytoString などの便利なメソッドも利用できないことを意味します。

純粋な辞書として使う場合以外は、通常は既存のオブジェクトをプロトタイプとして指定するか、リテラル構文 ({}) でオブジェクトを作成する方が一般的です。

まとめ

JavaScriptのObject.createメソッドは、新しいオブジェクトを作成し、そのプロトタイプを柔軟に指定するための強力なツールです。
コンストラクタ関数を介さずにプロトタイプ継承を直接制御できるため、特定の継承パターンを実装したり、Object.prototypeを継承しない純粋な辞書オブジェクトを作成したりする際に非常に便利です。

しかし、シャローコピーであること、コンストラクタが呼び出されないこと、そしてプロトタイプ継承の仕組みを深く理解している必要があるという重要な注意点を踏まえて
Object.createを効果的に活用し、JavaScriptアプリケーションにおけるプログラミングをより柔軟に行いましょう。

コメント