コンストラクタ 演算子 new

通常 {...} 構文で1つのオブジェクトを作ることができます。しかし、しばしば多くの似たようなオブジェクトを作る必要があります。例えば複数のユーザやメニューアイテムなどです。

それは、コンストラクタ関数と "new" 演算子を使うことで実現できます。

コンストラクタ 関数

コンストラクタ関数は技術的には通常の関数です。それには2つの慣習があります:

  1. 先頭は大文字で名前付けされます。
  2. "new" 演算子を使ってのみ実行されるべきです。

例:

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

new User(...) として関数が実行されたとき、次のようなステップになります:

  1. 新しい空のオブジェクトが作られ、 this に代入されます。
  2. 関数本体を実行します。通常は this を変更し、それに新しいプロパティを追加します。
  3. this の値が返却されます。

つまり、new User(...) は次のようなことをします:

function User(name) {
  // this = {};  (暗黙)

  // this へプロパティを追加
  this.name = name;
  this.isAdmin = false;

  // return this;  (暗黙)
}

なので、new User("Jack") の結果は次と同じオブジェクトです:

let user = {
  name: "Jack",
  isAdmin: false
};

さて、もし他のユーザを作りたい場合、new User("Ann"), new User("Alice") と言ったように呼ぶことができます。毎回リテラルを使うよりはるかに短く、また簡単で読みやすいです。

再利用可能なオブジェクト作成のコードを実装すること、それがコンストラクタの主な目的です。

改めて留意しましょう、技術的にはどんな関数もコンストラクタとして使うことができます。つまり、どの関数も new で実行することができ、上のアルゴリズムで実行されるでしょう。“先頭が大文字” は共通の合意であり、関数が new で実行されることを明確にするためです。

new function() { … }

1つの複雑なオブジェクトの作成に関する多くのコードがある場合、コンストラクタ関数でそれをラップすることができます。このようになります:

let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // ...ユーザ作成のための他のコード。
  // 複雑なロジック、文やローカル変数などを持つかもしれません。
};

コンストラクタはどこにも保存されず、単に作られて呼び出されただけなので2度は呼び出せません。なので、このやり方は将来再利用することなく、単一のオブジェクトを構成するコードをカプセル化することを目指しています。

二重構文コンストラクタ: new.target

関数の中では、new.target プロパティを使うことで、それが new で呼ばれたかそうでないかを確認することができます。

通常の呼び出しでは空であり、 new で呼び出された場合は関数と等しくなります:

function User() {
  alert(new.target);
}

// new なし:
User(); // undefined

// new あり:
new User(); // function User { ... }

これは、 new の場合と、通常呼び出し両方の構文が同じように動作するようにするために使用できます:

function User(name) {
  if (!new.target) { // new なしで実行した場合
    return new User(name); // ...new を追加します
  }

  this.name = name;
}

let john = User("John"); // new User へのリダイレクト
alert(john.name); // John

このアプローチは、構文をより柔軟にするためにライブラリの中で使われることがあります。が、恐らくどこへでもこれを使うのは良いことではありません。なぜなら、 new を省略すると、何をしているのかが少し分かりにくくなるからです。 new があれば、新しいオブジェクトが作られることを知ることができます。

コンストラクタからの返却

通常、コンストラクタは return 文を持ちません。コンストラクタの仕事は必要なものをすべて this の中に書くことです。それが自動的に結果になります。

しかし、もし return 文があった場合はどうなるでしょう。ルールはシンプルです:

  • もし return がオブジェクトと一緒に呼ばれた場合、this の代わりにそれを返します。
  • もし return がプリミティブと一緒に呼ばれた場合、それは無視されます。

言い換えると、オブエジェクトのreturn はそのオブジェクトを返し、それ以外のケースでは this が返却されます。

例えば、ここで return は オブジェクトを返却することで、this を上書きします:

function BigUser() {

  this.name = "John";

  return { name: "Godzilla" };  // <-- オブジェクトを返す
}

alert( new BigUser().name );  // Godzilla, オブジェクトを取得 ^^

また、これは空の return の例です(return の後に プリミティブを置いた場合も同じです)

function SmallUser() {

  this.name = "John";

  return; // 実行が終了し, this を返す

  // ...

}

alert( new SmallUser().name );  // John

通常、コンストラクタは return 文を持ちません。ここでは、主に完全性のためにオブジェクトを返す特殊な動作について説明しています。

丸括弧の省略

ところで、もし引数を取らない場合は、new の後の丸括弧を省略することもできます。

let user = new User; // <-- 括弧なし
// これと同じ
let user = new User();

丸括弧の省略は “良いスタイル” ではありませんが、仕様では許可されています。

コンストラクタの中のメソッド

オブジェクトを作るとき、コンストラクタ関数を使用することで高い柔軟性を得ることができます。コンストラクタ関数はオブジェクトがどのように組み立てられるか、その中に何を置くかを定義するパラメータを持っています。

もちろん、this にプロパティだけでなく、同様にメソッドも追加することができます。

例えば、下の new User(name)name と メソッド sayHi を持つオブジェクトを作ります:

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "My name is: " + this.name );
  };
}

let john = new User("John");

john.sayHi(); // My name is: John

/*
john = {
   name: "John",
   sayHi: function() { ... }
}
*/

サマリ

  • コンストラクタ関数、もしくは手短にコンストラクタ、は通常の関数ですが大文字から始まる名前を持つと言う共通の合意があります。
  • コンストラクタ関数は new を使ってのみ呼び出されるべきです。この呼び出しは、最初に空の this を作成し、最後に追加された this を返すことを意味します。

複数の似たようなオブジェクトを作るときにコンストラクタ関数を使うことができます。

JavaScript は多くの組み込みのオブジェクトでコンストラクタを提供しています: 日付のための Date, セットのための Set、そしてその他私たちが学ぶ予定のものなどです。

オブジェクト, 我々は戻ってきます!

このチャプターでは、オブジェクトとコンストラクタについての基礎のみを説明しています。これらは、次のチャプターでデータ型と関数についてより深く学ぶために不可欠です。

それを学んだ後、チャプター 記事 "object-oriented-programming" が見つかりません では、オブジェクトに戻り、継承やクラスを含めそれらを詳細に説明します。

タスク

重要性: 2

new A()==new B() のような関数 AB を作ることはできるでしょうか?

function A() { ... }
function B() { ... }

let a = new A;
let b = new B;

alert( a == b ); // true

もしできるなら、そのコード例を書いてみてください。

はい、可能です。

もし関数がオブジェクトを返す場合、newthis の代わりにそれを返します。

なので、例えば、同じ外部で定義されたオブジェクト obj を返すことで実現できます。:

let obj = {};

function A() { return obj; }
function B() { return obj; }

alert( new A() == new B() ); // true
重要性: 5

3つのメソッドをもつオブジェクトを作るコンストラクタ関数 Calculator を作りなさい:

  • read()prompt を使って2つの値を訪ね、オブジェクトプロパティの中でそれを覚えます。
  • sum() はそれらのプロパティの合計を返します。
  • mul() はそれらのプロパティの掛け算結果を返します。

例:

let calculator = new Calculator();
calculator.read();

alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );

デモを実行

テストと一緒にサンドボックスを開く

function Calculator() {

  this.read = function() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  };

  this.sum = function() {
    return this.a + this.b;
  };

  this.mul = function() {
    return this.a * this.b;
  };
}

let calculator = new Calculator();
calculator.read();

alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );

サンドボックスでテストと一緒に解答を開く

重要性: 5

コンストラクタ関数 Accumulator(startingValue) を作りなさい。

作成するオブジェクトは:

  • “現在の値” をプロパティ value に格納します。開始値はコンストラクタ startingValue の引数がセットされます。
  • read() メソッドは prompt を使って新しい値を読み込み、value に加算します。

つまり、value プロパティーはユーザーが入力したすべての値と初期値 startingValue の合計です。

これはそのコードのデモです:

let accumulator = new Accumulator(1); // 初期値 1
accumulator.read(); // ユーザの入力値の加算
accumulator.read(); // ユーザの入力値の加算
alert(accumulator.value); // それらの値の合計を表示

デモを実行

テストと一緒にサンドボックスを開く

function Accumulator(startingValue) {
  this.value = startingValue;

  this.read = function() {
    this.value += +prompt('How much to add?', 0);
  };

}

let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert(accumulator.value);

サンドボックスでテストと一緒に解答を開く

重要性: 5

“拡張可能な” 計算機オブジェクトを作るコンストラクタ関数 Calculator を作りなさい。

タスクは2つのパートから構成されます。

  1. 最初に フォーマット “数値 演算子 数値” (スペース区切り) で、"1 + 2" のような文字列を取り、結果を返すメソッド calculate(str) メソッドを実装します。それはプラス + と マイナス - を理解できるようにしてください。

    使い方の例:

    let calc = new Calculator;
    
    alert( calc.calculate("3 + 7") ); // 10
  2. 次に、計算機に新しい操作を教えるメソッド addOperator(name, func) を追加します。操作 name と、それを実装する2つの引数を持つ関数 func(a,b) を取ります。

    例えば、乗算 *, 除算 / やべき乗 **:

    let powerCalc = new Calculator;
    powerCalc.addMethod("*", (a, b) => a * b);
    powerCalc.addMethod("/", (a, b) => a / b);
    powerCalc.addMethod("**", (a, b) => a ** b);
    
    let result = powerCalc.calculate("2 ** 3");
    alert( result ); // 8
  • このタスクではかっこや複雑な表現は不要です。
  • 数字と演算子は、正確に1つのスペースで区切られます。
  • 追加したい場合にエラー処理があるかもしれません。

テストと一緒にサンドボックスを開く

  • メソッドの格納方法に注意してください。それらは単に内部オブジェクトに追加されています。
  • すべてのテストと数値変換は calculate メソッドで行われます。将来より複雑な式をサポートするために拡張されるかもしれません。

サンドボックスでテストと一緒に解答を開く

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。