ここで多くの人がオブジェクトの作成に最善の方法はないと言いますが、2019年のようにJavaScriptでオブジェクトを作成する方法が非常に多い理由には根拠があり、これはさまざまな反復でのJavaScriptの進行に関係していますEcmaScriptリリースの1997年までさかのぼります。
ECMAScript 5以前は、オブジェクトを作成する方法は2つしかありませんでした。コンストラクター関数またはリテラル表記(new Object()のより良い代替手段)です。コンストラクター関数表記を使用すると、(新しいキーワードを使用して)複数のインスタンスにインスタンス化できるオブジェクトを作成しますが、リテラル表記は、シングルトンのような単一のオブジェクトを提供します。
// constructor function
function Person() {};
// literal notation
var Person = {};
使用するメソッドに関係なく、JavaScriptオブジェクトはキーと値のペアの単なるプロパティです。
// Method 1: dot notation
obj.firstName = 'Bob';
// Method 2: bracket notation. With bracket notation, you can use invalid characters for a javascript identifier.
obj['lastName'] = 'Smith';
// Method 3: Object.defineProperty
Object.defineProperty(obj, 'firstName', {
value: 'Bob',
writable: true,
configurable: true,
enumerable: false
})
// Method 4: Object.defineProperties
Object.defineProperties(obj, {
firstName: {
value: 'Bob',
writable: true
},
lastName: {
value: 'Smith',
writable: false
}
});
JavaScriptの初期バージョンでは、クラスベースの継承を模倣する唯一の実際の方法は、コンストラクター関数を使用することでした。コンストラクター関数は、「new」キーワードで呼び出される特別な関数です。慣例により、関数識別子は大文字で表記されますが、必須ではありません。コンストラクターの内部では、 'this'キーワードを参照して、コンストラクター関数が暗黙的に作成しているオブジェクトにプロパティを追加します。明示的にreturnキーワードを使用して何かを返さない限り、コンストラクター関数は、プロパティが設定された新しいオブジェクトを暗黙的に呼び出し元の関数に返します。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.sayName = function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var bob = new Person("Bob", "Smith");
bob instanceOf Person // true
sayNameメソッドに問題があります。通常、オブジェクト指向クラスベースのプログラミング言語では、クラスをファクトリとして使用してオブジェクトを作成します。各オブジェクトには独自のインスタンス変数がありますが、クラスブループリントで定義されているメソッドへのポインターがあります。残念ながら、JavaScriptのコンストラクター関数を使用すると、呼び出されるたびに、新しく作成されたオブジェクトに新しいsayNameプロパティが定義されます。したがって、各オブジェクトには独自のsayNameプロパティがあります。これは、より多くのメモリリソースを消費します。
メモリリソースの増加に加えて、コンストラクタ関数内でメソッドを定義すると、継承の可能性がなくなります。この場合も、メソッドは新しく作成されたオブジェクトのプロパティとして定義され、他のオブジェクトは定義されないため、継承は同様に機能しません。したがって、JavaScriptはプロトタイプチェーンを継承の形式として提供し、JavaScriptをプロトタイプ言語にします。
親がいて、親が子の多くのプロパティを共有している場合、子はそれらのプロパティを継承する必要があります。ES5以前は、次のようにして実現されていました。
function Parent(eyeColor, hairColor) {
this.eyeColor = eyeColor;
this.hairColor = hairColor;
}
Parent.prototype.getEyeColor = function() {
console.log('has ' + this.eyeColor);
}
Parent.prototype.getHairColor = function() {
console.log('has ' + this.hairColor);
}
function Child(firstName, lastName) {
Parent.call(this, arguments[2], arguments[3]);
this.firstName = firstName;
this.lastName = lastName;
}
Child.prototype = Parent.prototype;
var child = new Child('Bob', 'Smith', 'blue', 'blonde');
child.getEyeColor(); // has blue eyes
child.getHairColor(); // has blonde hair
上記のプロトタイプチェーンの利用方法には、癖があります。プロトタイプはライブリンクであるため、プロトタイプチェーン内の1つのオブジェクトのプロパティを変更すると、別のオブジェクトの同じプロパティも変更されます。明らかに、子の継承されたメソッドを変更しても、親のメソッドは変更されません。Object.createは、ポリフィルを使用してこの問題を解決しました。したがって、Object.createを使用すると、プロトタイプチェーン内の親の同じプロパティに影響を与えることなく、プロトタイプチェーン内の子のプロパティを安全に変更できます。
ECMAScript 5ではObject.createが導入され、前述のオブジェクト作成のコンストラクター関数のバグが解決されました。Object.create()メソッドは、新しく作成されたオブジェクトのプロトタイプとして既存のオブジェクトを使用して、新しいオブジェクトを作成します。新しいオブジェクトが作成されるため、プロトタイプチェーンの子プロパティを変更すると、チェーン内のそのプロパティへの親の参照が変更されるという問題はなくなります。
var bobSmith = {
firstName: "Bob",
lastName: "Smith",
sayName: function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var janeSmith = Object.create(bobSmith, {
firstName : { value: "Jane" }
})
console.log(bobSmith.sayName()); // My name is Bob Smith
console.log(janeSmith.sayName()); // My name is Jane Smith
janeSmith.__proto__ == bobSmith; // true
janeSmith instanceof bobSmith; // Uncaught TypeError: Right-hand side of 'instanceof' is not callable. Error occurs because bobSmith is not a constructor function.
ES6より前は、関数コンストラクターとObject.createを利用するための一般的な作成パターンは次のとおりです。
const View = function(element){
this.element = element;
}
View.prototype = {
getElement: function(){
this.element
}
}
const SubView = function(element){
View.call(this, element);
}
SubView.prototype = Object.create(View.prototype);
現在、コンストラクター関数と組み合わせたObject.createは、JavaScriptでのオブジェクトの作成と継承に広く使用されています。ただし、ES6はクラスの概念を導入しました。これは主に、JavaScriptの既存のプロトタイプベースの継承に対する構文上の砂糖です。クラス構文は、JavaScriptに新しいオブジェクト指向の継承モデルを導入しません。したがって、JavaScriptは原型言語のままです。
ES6クラスは継承をはるかに簡単にします。親クラスのプロトタイプ関数を手動でコピーして子クラスのコンストラクターをリセットする必要がなくなりました。
// create parent class
class Person {
constructor (name) {
this.name = name;
}
}
// create child class and extend our parent class
class Boy extends Person {
constructor (name, color) {
// invoke our parent constructor function passing in any required parameters
super(name);
this.favoriteColor = color;
}
}
const boy = new Boy('bob', 'blue')
boy.favoriteColor; // blue
全体として、JavaScriptでのオブジェクト作成のこれら5つの異なる戦略は、EcmaScript標準の進化と一致しました。