12日 七月 2020

オブジェクトメソッド, "this"

オブジェクトは通常、ユーザや注文などのような、実世界のエンティティを表現するために作られます。:

let user = {
  name: "John",
  age: 30
};

そして、実世界ではユーザは アクション することができます: ショッピングカードから何かを選んだり、ログイン、ログアウトなど。

アクションは、JavaScriptではプロパティの中で関数で表現されます。

メソッド例

スタートとして、user が Hello と言うようにしましょう:

let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("Hello!");
};

user.sayHi(); // Hello!

ここでは関数を作るために関数式を使い、それをオブジェクトの user.sayHi プロパティに代入しました。

その後、関数を呼ぶことができます。ユーザは今話すことができます!

オブジェクトのプロパティの関数は、メソッド と呼ばれます。

従って、ここではオブジェクト user のメソッド sayHi を作りました。

もちろん、次のように、宣言済みの関数をメソッドとして使うこともできます:

let user = {
  // ...
};

// 最初、宣言
function sayHi() {
  alert("Hello!");
};

// その後、メソッドを追加
user.sayHi = sayHi;

user.sayHi(); // Hello!
オブジェクト指向プログラミング

エンティティを表現するためにオブジェクトを使ってコードを書くとき、それは、object-oriented programming, 略すと “OOP” とばれます。

OOPは大きなものであり、それ自体の興味深い科学です。 正しいエンティティを選択するにはどうすればいいですか? どのようにそれらの間の相互作用を整理しますか?それはアーキテクチャーであり、それらは E.Gamma, R.Helm, R.Johnson, J.Vissides による"Design Patterns: Elements of Reusable Object-Oriented Software" または G.Booch による “Object-Oriented Analysis and Design with Applications” などのような、そのトピックについての素晴らしい本があります。私たちは、チャプター 記事 "object-oriented-programming" が見つかりません の後半でそのトピックの表面について触れます。

メソッドの短縮表現

オブジェクトリテラルでは、メソッドのための短縮構文があります:

// これらのオブジェクトは同じことをします

let user = {
  sayHi: function() {
    alert("Hello");
  }
};

// メソッド簡略化はスッキリ見えますね
let user = {
  sayHi() { // "sayHi: function()" と同じです
    alert("Hello");
  }
};

上の通り、"function" を除き、単に sayHi() と書くことができます。

実を言うと、この表記は完全に同一ではありません。オブジェクトの継承(後で説明します)に関して微妙な違いがあります。が、今のところは問題ありません。ほぼ全てのケースでこの短縮構文は好まれます。

メソッド中の “this”

オブジェクトメソッドが処理をするために、オブジェクトに格納されている情報にアクセスする必要があることは一般的です。

例えば、user.sayHi() の内側のコードが user の名前を必要とするかもしれません。

オブジェクトにアクセスするために、メソッドは this キーワードを使うことができます。

this の値はメソッドを呼び出すのに使われた “ドットの前” のオブジェクトです。

例:

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(this.name);
  }

};

user.sayHi(); // John

ここで user.sayHi() の実行中、this の値は user になります。

技術的には、this なしでもオブジェクトへのアクセスも可能です – 外部変数経由での参照によって:

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(user.name); // "this" の代わりに "user"
  }

};

…しかし、このようなコードは信頼できません。もし useradmin = user のように別の変数にコピーすることにし、何かでuserを上書きすると, 間違ったオブジェクトへアクセスすることになります。

次のような感じです:

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert( user.name ); // エラーにつながる
  }

};


let admin = user;
user = null; // 明らかにするために上書きします

admin.sayHi(); // Whoops! sayHi() の中で古い名前が使われました! エラーです!

もし alert の内側で、user.name の代わりに this.name を使うと、コードは動作します。

“this” はバインドされていません

JavaScriptでは、 “this” キーワードは他のほとんどのプログラミング言語とは異なる振る舞いをします。まず、どの関数にでも使えます。

このようなコードも構文エラーにはなりません:

function sayHi() {
  alert( this.name );
}

this の値は実行時に評価されます。そしてそれは何にでもなれます。

例えば、異なるオブジェクトから呼ばれた場合、同じ関数でも異なる “this” を持つ可能性があります:

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 2つのオブジェクトで同じ関数を使う
user.f = sayHi;
admin.f = sayHi;

// これらの呼び出しは異なる this を持ちます
// 関数の中の "this" は "ドット" の前のオブジェクトです
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // Admin (ドットでも角括弧でも問題なくメソッドにアクセスできます)

実際、オブジェクトまったくなしで関数を呼び出すこともできます:

function sayHi() {
  alert(this);
}

sayHi(); // undefined

このケースでは、 strict モードでは thisundefined になります。もし this.name にアクセスしようとするとエラーになります。

非 strict モード(誰かが use strict を忘れた場合)では、このようなケースでは this の値は グローバルオブジェクト (ブラウザでは window, 後ほど学びます)になります。これは "use strict" が修正した歴史的な振る舞いです。

一般的に、オブジェクトなしで this を使う関数の呼び出しは、普通ではなくプログラム上の誤りであることに注意してください。もし関数が this を持っていたら、それは通常オブジェクトコンテキストで呼ばれることを意味しています。

バインドしていない this の結果

もしあなたが別のプログラミング言語から来ていたら、恐らく "this のバインド" の考えに慣れているでしょう。それは、オブジェクトに定義されたメソッドは常にそのオブジェクトを参照する this を持っている、と言うものです。

JavaScriptでは、 this は “自由” です。その値は実行時に評価され、メソッドが宣言されている場所には依存せず、 “ドットの前の” オブジェクトが何であるか、に依存します。

実行時に評価される this の概念はプラスとマイナス両方を持っています。一方では、関数は異なるオブジェクトで再利用することができます。他方では、より大きな柔軟性はミスを導きやすいです。

ここで、我々のポジションはこの言語が決めたことが良いか悪いかを判断するものではありません。我々は、それをどうやって使うか、どうやって利益を得るか/問題を回避するかを理解することです。

アロー関数は “this” を持ちません

アロー関数は特別です: それらは “自身の” this を持ちません。もしこのような関数で this を参照した場合、外部の “通常の” 関数から取得されます。

例えば、ここで arrow() は外部の user.sayHi() メソッドから this を使います:

let user = {
  firstName: "Ilya",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // Ilya

これはアロー関数の特別な機能です。別の this ではなく、外側のコンテキストから取り出したい場合に便利です。アロー関数ふたたびのセクションの後半では、より多くのアロー関数を扱います。

サマリ

  • オブジェクトのプロパティに格納されている関数は “メソッド” と呼ばれます。
  • メソッドを使うと、オブジェクトは object.doSomething() のように “振る舞う” ことができます。
  • メソッドはオブジェクトを this で参照することができます。

this の値は実行時に定義されます。

  • 関数が宣言されている場合、this を使うことができますが、その this は関数が呼び出されるまで値を持っていません。
  • その関数はオブジェクト間でコピーできます。
  • 関数が “メソッド” 構文で呼び出されたとき: object.method(), 呼び出し中の this の値は、object です。

アロー関数は特別であることに注意してください: それは this を持っていません。this がアロー関数の中でアクセスされるとき、それは外側から取得されます。

タスク

重要性: 5

ここにオブジェクトを返す makeUser 関数があります。

その ref へのアクセス結果なんでしょう?それはなぜでしょう?

function makeUser() {
  return {
    name: "John",
    ref: this
  };
};

let user = makeUser();

alert( user.ref.name ); // 結果はなに?

答え: エラーです

やってみましょう:

function makeUser() {
  return {
    name: "John",
    ref: this
  };
};

let user = makeUser();

alert( user.ref.name ); // Error: Cannot read property 'name' of undefined

これは this をセットするルールがオブジェクトリテラルを見ないためです。

ここで makeUser() の中の this 値は undefined です。なぜなら、関数として呼ばれており、メソッドではないためです。

また、オブジェクトリテラル自身は this に影響しません。this の値は関数全体で、コードブロックやオブジェクトリテラルはそれに影響しません。

従って、ref: this は実際にはその関数の現在の this を取ります。

これは反対のケースです:

function makeUser() {
  return {
    name: "John",
    ref() {
      return this;
    }
  };
};

let user = makeUser();

alert( user.ref().name ); // John

これは動作します。なぜなら user.ref() はメソッドだからです。そして this の値はドット . の前のオブジェクトがセットされます。

重要性: 5

3つのメソッドをもつ calculator オブジェクトを作りなさい。:

  • read() は2つの値を聞き、オブジェクトプロパティとしてそれらを格納します。
  • sum() は保存した値の合計を返します。
  • mul() は保存した値を掛け、その結果を返します。
let calculator = {
  // ... your code ...
};

calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );

デモを実行

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

let calculator = {
  sum() {
    return this.a + this.b;
  },

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

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

calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );

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

重要性: 2

上下に移動できる ladder オブジェクトがあります。:

let ladder = {
  step: 0,
  up() {
    this.step++;
  },
  down() {
    this.step--;
  },
  showStep: function() { // 現在の段を表示します
    alert( this.step );
  }
};

さて、順番にいくつかの呼び出しをする場合、このようにできます:

ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1

updown のコードを修正して、連鎖可能な呼び出しができるようにしてください。:

ladder.up().up().down().showStep(); // 1

このようなアプローチはJavaScriptライブラリの中で広く使われています。

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

解決策はすべての呼び出しでオブジェクト自身を返すことです。

let ladder = {
  step: 0,
  up() {
    this.step++;
    return this;
  },
  down() {
    this.step--;
    return this;
  },
  showStep() {
    alert( this.step );
    return this;
  }
}

ladder.up().up().down().up().down().showStep(); // 1

また、1行毎に1つの呼び出しで書くこともできます。長い連鎖の場合はより読みやすいです。:

ladder
  .up()
  .up()
  .down()
  .up()
  .down()
  .showStep(); // 1
let ladder = {
  step: 0,
  up: function() {
    this.step++;
    return this;
  },
  down: function() {
    this.step--;
    return this;
  },
  showStep: function() {
    alert(this.step);
  }
};

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

チュートリアルマップ

コメント

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