2016/5/31 タイトルを変更しました。
リテラルとコンストラクタ
オブジェクトリテラル
リテラルとは固定値を直接スクリプト中に記述する事を言います。
JacaScriptのオブジェクトは 値と キーのハッシュテーブルでとみなす事ができ、その値がプリミティブ型でもオブジェクトでも良いようです。どちらの場合も プロパティと呼び、関数の場合は メソッドと呼びます。
JavaScriptのカスタムオブジェクトはいつでも変更が出来るので、空のオブジェクトを作り、そこから自由に値とキー、関数などを付け加えてオブジェクトを都度変更させることが可能のようです。
空のオブジェクトといっても、Object.prototype
から継承したプロパティとメソッドを持っています。
//配列でギターの種類を定義(参照用で今回のオブジェクトリテラル記法には特に関係はない)
var gType = ["Strat","Les Poul", "Telecaster"];
// 空のオブジェクトを生成
var guitar = {};
// プロパティの追加
// 特に何の宣言もなく、いきなりキーと値を書くと作成される。
guitar.name = "Giita";
guitar.type = gType[0];
// オブジェクトの追加
guitar.knob = {
volume: 10,
rearTone: 10,
frontTone: 10
};
// メソッドの追加
guitar.play = function() {
var sound = this.type + ":Gy";
for (var i = 0; i < this.knob.volume; i++) {
sound += "a";
}
return sound + "n!";
}
// プロパティの削除
delete guitar.name;
// 消えたかチェック
console.log(guitar.name); //undefined
// メソッド呼び出し
console.log(guitar.play()); //Strat:Gyaaaaaaaaaan!
空のオブジェクトから作るのではなく、予めオブジェクトリテラルの書き方を用いれば、生成時点で初期値を入れておけます。
//配列でギターの種類を定義(参照用で今回のオブジェクトリテラル記法には特に関係はない)
var gType = ["Strat","Les Poul", "Telecaster"];
// オブジェクト生成と同時にプロパティの追加
var guitar = {
name: "Giita",
type: gType[0], //変数も入れられる
knob: {
volume: 10,
rearTone: 10,
frontTone: 10
},
play: function() {
var sound = this.type + ":Gy";
for (var i = 0; i < this.knob.volume; i++) {
sound += "a";
}
return sound + "n!";
}
};
// プロパティの削除
delete guitar.name;
// 消えたかチェック
console.log(guitar.name); //undefined
// メソッド呼び出し
console.log(guitar.play()); //Strat:Gyaaaaaaaaaan!
オブジェクトリテラルの構文
- オブジェクトを波括弧で囲む
- プロパティやメソッドの間はカンマで区切る。最後のカンマはIEでエラーが起きるので書かない
- プロパティの名前とプロパティの間はコロンで区切る
- オブジェクトに変数を代入するときは、閉じ括弧の後にセミコロンを忘れない
コンストラクタ
コンストラクタを使用してオブジェクトを生成する方法は以下の2種類があります
- 組み込みのコンストラクタを使用する方法
- カスタムのコンストラクタ関数を定義して使用する方法
組み込みのコンストラクタを使用する方法は、Java等で見られるオブジェクトの生成方法と似ていて、親しみやすいかもしれませんが、コンストラクタに渡す値が動的で実行時でないと分からない場合、予測できない事が起きかねるので、仕様上仕方がない時に慎重に使う方法以外では極力使わず、オブジェクトリテラルで書けるカスタムのコンストラクタ関数を定義して使用する方法を選ぶ方が安心のようです。
以下にものすごく極端な例で危ないケースを書いてみました。
//配列に文字列以外のものを入れてみる
var args = ["100", "252", "342", 124];
//配列の数のうちランダムでインデックスを決める
var rnd = Math.floor(Math.random() * args.length);
//組み込みのコンストラクタでオブジェクトを生成する
var o = new Object(args[rnd]);
//出た値と型をコンソールに出す
console.log("rnd:" + rnd + "ObjectType:" + o.constructor);
//string型にしかないメソッドを読んでみる(0文字目から2文字目の文字列を返す)
console.log(o.substring(0, 2));
結果は以下のようになりました。
文字列オブジェクトだった時
ちゃんと期待したとおりの文字が返って来ております。
数値オブジェクトだった時
数値型オブジェクトには存在しないメソッドなので、そのような関数は無いと怒られていますね。
というような事も起きかねるので、気をつけましょう。
カスタムのコンストラクタ関数
まず、コンストラクタ関数の定義の方法を見てみます。
var Dog = function(name, bark) {
this.name = name;
this.bark = bark;
this.cry = function() {
return this.name + ":" + this.bark;
}
}
//オブジェクト生成
var taro = new Dog("taro","bow-wow");
console.log(taro.cry()); //taro:bow-wow
this
はこの関数のプロトタイプを継承しており、this
が参照するオブジェクトにプロパティの追加ができます。このthis
は暗黙的に生成され、最後に暗黙的に返しています。
var Dog = function(name, bark) {
var this = Object.create(Dog.prototype);// <-- thisのオブジェクトが作られる
this.name = name;
this.bark = bark;
this.cry = function() {
return this.name + ":" + this.bark;
}
return this;// <-- thisが返される
}
this.cry
の部分は、オブジェクトが生成される度に同じように生成されてしまい、メモリをその分食ってしまいます。内容自体はすべて変数で行われているため、何個も同じものは生成され無いほうが効率的です。
なので、プロトタイプにこのメソッドを移動する事で、メモリの消費を抑える事ができます。なおプロトタイプは今後またしっかり勉強する予定です。
var Dog = function(name, bark) {
this.name = name;
this.bark = bark;
}
Dog.prototype.cry = function() {
return this.name + ":" + this.bark;
}
コンストラクタの戻り値は必ず オブジェクトが返されます。特に指定しなければ thisが参照するオブジェクトが返されます。先ほど、暗黙的な部分を明示させたものとして示しましたが、暗黙的にreturn
で返されます。
返すオブジェクトを自ら指定する事で、this
を返さなくさせる方法もあります。
function Dog(name, bark) {
var that = {}; // thisとは別のオブジェクトを作成
this.name = name;
that.bark = bark;
that.cry = function() {
return that.bark;
}
return that;// thatが返される(thisでの宣言は無かったことに)
}
//オブジェクト生成
var taro = new Dog("Taro","bow-wow");
console.log(taro.name); //undefined
console.log(taro.cry()); //bow-wow
newを強制するパターン
オブジェクトを新しく生成する時はnew
を使いますが、new
を忘れてしまうと予期せぬエラーが起こります。
new
を忘れると単純に関数が呼ばれただけという様な動きになり、内部で使用されているthis
はグローバルオブジェクトが参照されるので、グローバルオブジェクトにプロパティが作られる事となる結果となり、予期せぬエラーを生む事となります。
function Dog(name, bark) {
this.name = name;
this.bark = bark;
}
//関数をプロトタイプに宣言する
Dog.prototype.cry = function() {
return this.bark;
}
//オブジェクト生成
var taro = new Dog("Taro","bow-wow");
var jiro = Dog("Jiro","woon");
console.log(taro.name); //Taro
console.log(taro.cry()); //bow-wow
console.log(window.name); // Jiro <-- グローバルオブジェクトのnameプロパティが書き換わった
console.log(jiro.name); // <-- TypeError: undefined is not an object (evaluating 'jiro.name')
console.log(jiro.cry());// 未到達
では、先ほどの返すオブジェクトを指定する方法でthat
を利用してthis
を使わずにグローバルを汚染しない方法をとったとしても、今度はプロトタイプへのリンクが失われてしまうので、再利用性としてはよくありません。
function Dog(name, bark) {
var that = {}; // thisとは別のオブジェクトを作成
that.name = name;
that.bark = bark;
return that;// thatが返される
}
//関数をプロトタイプに宣言する
Dog.prototype.cry = function() {
return this.bark;
}
//オブジェクト生成
var taro = new Dog("Taro","bow-wow");
console.log(taro.name); //undefined
console.log(taro.cry()); // <-- TypeError: Taro.cry is not a function. (In Taro.cry(), Taro.cry is undefined)
プロトタイプを保ちつつ、this
を守りたいのであれば、自分自身を呼び出すタイプのコンストラクタの作り方をするのが好ましいでしょう。
function Dog(name, bark) {
//現在の this がDogオブジェクトでなければ、自らを呼び出して生成してその場で返す
if (!(this instanceof Dog)) {
return new Dog(name, bark);
}
this.name = name;
this.bark = bark;
}
//関数をプロトタイプに宣言する
Dog.prototype.cry = function() {
return this.name + ":" + this.bark;
}
//オブジェクト生成
var taro = Dog("Taro", "bow-wow");
console.log(taro.name); //Taro
console.log(taro.cry()); // Taro:bow-wow
感想
ここまで読み解いてきまして、Javaとの違いや、JavaScript独特の書き方や決まりに四苦八苦していますが、ちょっとずつ読めるようになってきて面白くなってまいりました。
本はどんどん難解な方向になってきましたので、理解までの速度が少し落ちてきましたが、めげずに読み進めていこうと思います。
JavaScriptのお作法もQiitaのお作法もまだまだわからないことだらけですが、頑張ります!
リンク
次回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「配列・JSON編」 〜JavaScriptパターン 学習4日目【逃げメモ】〜
前回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「変数の扱い編」 〜JavaScriptパターン 学習2日目【逃げメモ】〜