Objectクラスのcreateメソッドによるオブジェクトの生成
《 初回公開:2022/11/17 , 最終更新:未 》
通常、新しいオブジェクトの生成は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(obj, 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" }, bar: { value: "hhh" } });
console.log(obj, obj.__proto__); // {},{x: 'aaa', y: 'bbb'}
propertiesObject引数の指定には見慣れないプロパティ記述子の構文がつかわれていて、Object.definePropertyメソッドでのプロパティの指定方法が使われているようだ。
プロパティの記述子については「プロパティの詳細な指定 - 愚鈍人」を参照。
これは次のコードと等価と思われる。
let obj = { foo: "ffff", bar: "hhh" };
obj.__proto__ = { x: "aaa", y: "bbb" };
console.log(obj, obj.__proto__); // {},{x: 'aaa', y: 'bbb'}
クラスのインスタンスの生成
次のコードは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メソッドを使うようにした方が良いようだ。