Objectクラスのcreateメソッドによるオブジェクトの生成

《 初回公開:2022/11/17 , 最終更新:2023/10/19 》

通常、新しいオブジェクトの生成はnewキーワードを使っておこなわれるが、
Objectクラスのcreateメソッドによってもオブジェクトを生成する事ができる。

空のオブジェクト,空のObjectクラスのインスタンスを作成するには、

let obj = Object.create(Object.prototype);

console.log(obj);   // {}
console.log(obj.__proto__===Object.prototype);  // true

メソッドの書式

createメソッドには引数を1つのものと2つのものがあって

書式1
Object.create(proto)
let obj = Object.create({ x: "aaa", y: "bbb" });

objは空の連想配列で、obj.__proto__には{ x: "aaa", y: "bbb" }が格納される。

console.log(JSON.stringify(obj), JSON.stringify(obj.__proto__));    // {} {"x":"aaa","y":"bbb"}

これは次のコードと等価

let obj = new Object();
obj.__proto__ = { x: "aaa", y: "bbb" };
書式2
Object.create(proto, propertiesObject)
let obj = Object.create({ x: "aaa", y: "bbb" },
  {
    foo: {
      value: "ffff",
      enumerable: true,
      configurable: true,
      writable: true,
    },
    bar: {
      value: "hhh",
      enumerable: true,
      configurable: true,
      writable: true,
    }
  });

console.log(JSON.stringify(obj), JSON.stringify(obj.__proto__));    // {"foo":"ffff","bar":"hhh"} {"x":"aaa","y":"bbb"}

propertiesObject引数の指定には見慣れないプロパティ記述子の構文がつかわれていて、Object.definePropertyメソッドでのプロパティの指定方法が使われているようだ。

プロパティの記述子については「プロパティの詳細な指定 - 愚鈍人」を参照。

これは次のコードと等価と思われる。

let obj = { foo: "ffff", bar: "hhh" };
obj.__proto__ = { x: "aaa", y: "bbb" };

console.log(JSON.stringify(obj), JSON.stringify(obj.__proto__));    // {"foo":"ffff","bar":"hhh"} {"x":"aaa","y":"bbb"}

上記のようなコードを以下のようにもっと簡潔に記述できないのかと思うが、それはできない。

// このような引数の指定はできない
// エラー Uncaught TypeError: Property description must be an object: ffff
// createメソッドの第2引数はプロパティの記述子を使って指定する必要がある。
let obj = Object.create({ x: "aaa", y: "bbb" }, { foo: "ffff", bar: "hhh" });

クラスのインスタンスの生成

次のコードはObject.create()メソッドによりPersonクラスのインスタンスを生成する例である。

Personクラスを関数として定義

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Personクラスのインスタンスを生成

let person = new Person("愚鈍人", 999);
console.log(person);

これと同様の事をObject.create()メソッドを使うと

let person = Object.create({}, {
  name: {
    value: "愚鈍人",
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 999,
    writable: true,
    enumerable: true,
    configurable: true
  }
});
person.__proto__.constructor = Person;
console.log(person);

Object.create() を用いた古典的な継承

Object.create()を用いたクラスの継承の例をMDNのページから引用すると

// Shape - スーパークラス
function Shape() {
  this.x = 0;
  this.y = 0;
}

// スーパークラスのメソッド
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - サブクラス
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// サブクラスはスーパークラスを拡張する
Rectangle.prototype = Object.create(Shape.prototype);

// Rectangle.prototype.constructor を Rectangle に設定しないと、
// Shape (親) の prototype.constructor を取ることになります。
// これを防ぐために、 prototype.constructor を Rectangle (子) に設定します。
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?', rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

しかし、このコードではクラスに属するプロパティやメソッドの継承は実現できていない。

Shape.clsField="Shape.clsMethod";
console.log(Rectangle.clsField); // undefined

これは、クラスのプロトタイプチェインができていないためで、 Rectangleクラスの__proto__Object.__proto__を指している。

console.log(Rectangle.__proto__===Object.__proto__); // true

これをサブクラスの__proto__がスーパークラスを指すように修正すると。

Rectangle.__proto__=Shape;
console.log(Rectangle.clsField); // true

サブクラスからスーパークラスのフィールドを参照できるようになる。

特殊なオブジェクトの作成

Object.create()メソッドを使うとnewキーワードを使ったオブジェクトでは生成できない特殊なオブジェクトを作成できる。

__proto__プロパティが無いオブジェクト

通常、newキーワードを使ったオブジェクトでは生成では、必ず__proto__プロパティを持っていて__proto__プロパティの値は継承元クラスのprototypeプロパティを指しているが、 以下のようにcreateメソッドの引数にnullを指定すると__proto__プロパティを持たない特殊なオブジェクトを作成できる。

const o = Object.create(null);
console.log(o.__proto__);   // undefined

このオブジェクトは__proto__プロパティを持たないオブジェクトは、Objectクラスを継承しないオブジェクトなのですべてのオブジェクトが持つはずであるtoString等のObject.prototypeが持つメソッドを使う事ができない。

console.log(o.toString());  // Uncaught TypeError: o.toString is not a function

ただし、上で「全てのオブジェクト」の述べましたが、ひとつ例外があります。実はプロトタイプが無いオブジェクトを作ることができるのです。JavaScriptプログラム上では、そのようなオブジェクトはプロトタイプがnullのオブジェクトとして現れます。例えば、プロトタイプが無いオブジェクトを新規に作るにはObject.create(null)とすればよいことが分かります。実際にやってみると、そのようなオブジェクトは確かにObjectのインスタンスではないことが分かります。

const obj = Object.create(null);

console.log(obj instanceof Object); // false
console.log(Object.getPrototypeOf(obj)); // null

実はObject.prototypeもまた、プロトタイプが無いオブジェクトです。Object.prototypeはその意味でプロトタイプチェーンの終端であるといえるのです。

__proto__プロパティが無いオブジェクトは次のようなコードでも実現可能。

let obj = new Object();
obj.__proto__ = null

console.log(obj instanceof Object); // false
console.log(Object.getPrototypeOf(obj)); // null

任意のオブジェクトを継承するオブジェクト - クラス(function)を伴わない継承

Object.create()メソッドを使って任意のオブジェクトを継承するオブジェクトの生成ができる。

let anyObject = {
  anyField: "anyField",
  anyMethod: function () {
    console.log("anyMethodの実行")
  }
}
let anyObject1 = Object.create(anyObject);
let anyObject2 = Object.create(anyObject);

console.log(anyObject1.anyField);   // anyField
anyObject1.anyMethod();             // anyMethodの実行

console.log(anyObject2.anyField);   // anyField
anyObject2.anyMethod();             // anyMethodの実行

console.log(anyObject1.__proto__ === anyObject);    // true

この場合、anyObject1とanyObject2はオブジェクトanyObjectを継承したオブジェクトという見方もできて、anyObject1とanyObject2はanyObjectのインスタンスとも言える。

これによって、classキーワードやfunctionコンストラクタによるクラスとはまた別の、もっと柔軟な継承関係を構築できるようだ。

オブジェクトは Object.create() メソッドを使用して作成することもできます。このメソッドは、コンストラクター関数の定義なしに作りたいオブジェクトのプロトタイプを選べるため、とても便利です。

これは、クラスを定義しないで安直に継承が実現できるというメリットはあるのだが、 しかし一般的なclassによる継承と異なる継承は混乱を招く危険もあって、

クラス(function)を伴わない継承は__proto__プロパティに直接オブジェクトを代入する事でも実現できて。

let anyObject1 = new Object();
anyObject1.__proto__ = anyObject
let anyObject2 = new Object();
anyObject2.__proto__ = anyObject

protoプロパティに直接値を代入するのは避けてObject.createメソッドを使った方が良い。

これまで、Object.createメソッドを使った方法とprotoプロパティに直接値を代入する方法の両方を紹介してきたが、protoプロパティは非推奨になっていてprotoプロパティに値を代入するにはObject.setPrototypeOfメソッドを使う事になる。
しかし、このメソッドはJavaScriptの処理系によってはパフォーマンスの低下の可能性がありるため、Object.createメソッドを使うようにした方が良いようだ。

ページのトップへ戻る