8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[オブジェクト指向初心者の私がJavaScriptにも再入門 「オブジェクトとコンストラクタ編」 〜JavaScriptパターン 学習3日目【逃げメモ】〜

Last updated at Posted at 2016-05-29

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等で見られるオブジェクトの生成方法と似ていて、親しみやすいかもしれませんが、コンストラクタに渡す値が動的で実行時でないと分からない場合、予測できない事が起きかねるので、仕様上仕方がない時に慎重に使う方法以外では極力使わず、オブジェクトリテラルで書けるカスタムのコンストラクタ関数を定義して使用する方法を選ぶ方が安心のようです。

以下にものすごく極端な例で危ないケースを書いてみました。

Object()コンストラクタで起こる危険
//配列に文字列以外のものを入れてみる
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));

結果は以下のようになりました。

文字列オブジェクトだった時

スクリーンショット 2016-05-28 11.22.53.png

ちゃんと期待したとおりの文字が返って来ております。

数値オブジェクトだった時

スクリーンショット 2016-05-28 11.22.30.png

数値型オブジェクトには存在しないメソッドなので、そのような関数は無いと怒られていますね。

というような事も起きかねるので、気をつけましょう。

カスタムのコンストラクタ関数

まず、コンストラクタ関数の定義の方法を見てみます。

コンストラクタ関数
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はグローバルオブジェクトが参照されるので、グローバルオブジェクトにプロパティが作られる事となる結果となり、予期せぬエラーを生む事となります。

newを忘れると
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日目【逃げメモ】〜

8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?