質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.38%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

3725閲覧

ES6 Classのメソッドについて

yoppy0066

総合スコア293

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

2クリップ

投稿2018/03/03 05:52

class Hoge { method1() { ・・・ } method2 = () => { ・・・ } }

method1とmethod2の書き方の意味は違うのでしょうか?どのように使いわけるのでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

method1は通常の書き方です。method2は、2018年3月3日現在、仕様候補(stage 3)として提案中であるClass field declarations(クラスフィールド宣言)を用いた書き方です。**まだ、仕様には含まれておらず、また、このまま仕様に含まれることが確定されたわけではありません。**Babelでのトランスパイルは対応していますが、ブラウザでは対応している物が一つもありません。

さて、method2ですが、これはインスタンスメソッドの定義と言うより、インスタンス変数の定義です。現在のECMAScriptではclassの内側にメソッド以外は直接書くことができません。そこで、Javaのような他の言語のように、インスタンス変数も宣言できるようにしたのがクラスフィールド宣言になります。このクラスフィールド宣言により、インスタンス作成時に、そのインスタンス変数がプロパティとして作成されます。また、宣言と共に初期化子(代入演算子=と右辺のこと、これ自体はオプション)があれば、その値で初期化されるとなります。

つまり、文法的には

JavaScript

1class Hoge { 2 x = 0; 3 y = () => { return this.x; }; 4}

でのxyはどちらもインスタンス変数であり、Hogeのインスタンス作成時に、それぞれ0() => { return this.x; }に初期化されます。ただ、ECMAScriptではインスタンス変数はプロパティです。値が関数であるプロパティはメソッドです。ですので、y自体がメソッドであると言っても問題ありません。

ここで重要なのは、それぞれ代入される右辺が評価されるタイミングがそれぞれのインスタンス作成時であり、thisがそのインスタンスであると言うことです。つまり、このタイミングで評価される() => { return this.x; }においてのthisはそのインスタンス自身に束縛されることになります。


うん、(自分で書いたのに)なんかよくわからない。ということで、ここまで踏まえて、これがどのようなときに役に立つかをみながら、その動きを理解したいと思います。まず、次のようなコードをあったとします。

JavaScript

1class CountSec { 2 constructor(count) { 3 this.count = count; 4 } 5 countDown() { 6 console.log(this.count); 7 if (this.count > 0) { 8 this.count--; 9 setTimeout(this.countDown, 1000); 10 } 11 } 12} 13const ct = new CountSec(10); 14ct.countDown();

指定された回数分だけ1秒毎(あまり正確ではない)にカウントダウンするという意図のコードです。**このコードはこのままでは正常に動作しません。**二回目のcountDownの呼び出しではthisがそのインスタンスではなくなるからです。これはアロー関数やbind等でthisが束縛されていない関数がコールバック等で呼び出されるとき、thisは呼び出し側の関数に依存することに起因します。いわゆるthis問題というものです。これを現在のECMAScript 2017で修正する方法は三つです。

一つ目は、アロー関数で囲むことです。アロー関数は評価時にthisを束縛することができますので、コールバックでの呼び出し時もthisが変わることはありません。

JavaScript

1class CountSec { 2 constructor(count) { 3 this.count = count; 4 } 5 countDown() { 6 console.log(this.count); 7 if (this.count > 0) { 8 this.count--; 9 setTimeout(() => { 10 this.countDown(); 11 }, 1000); 12 } 13 } 14} 15const ct = new CountSec(10); 16ct.countDown();

二つ目は、bindを用いてthisに束縛してしまうと言う方法です。こちらも、コールバックでの呼び出し時にthisは変わりません。

JavaScript

1class CountSec { 2 constructor(count) { 3 this.count = count; 4 } 5 countDown() { 6 console.log(this.count); 7 if (this.count > 0) { 8 this.count--; 9 setTimeout(this.countDown.bind(this), 1000); 10 } 11 } 12} 13const ct = new CountSec(10); 14ct.countDown();

上二つはsetTimeout()が呼び出される度に新たな関数を作成しているのと同じです。ですが、初めからthisが束縛されているcountDownが一つあれば十分です。そこで、三つ目がコンストラクタでbindした関数をプロパティ(それは即ちそのインスタンス専用のメソッドです)として登録してしまうという方法です。

JavaScript

1class CountSec { 2 constructor(count) { 3 this.count = count; 4 this.countDown = this.countDown.bind(this); 5 } 6 countDown() { 7 console.log(this.count); 8 if (this.count > 0) { 9 this.count--; 10 setTimeout(this.countDown, 1000); 11 } 12 } 13} 14const ct = new CountSec(10); 15ct.countDown();

これらの方法は、いずれも初めのコードよりは冗長です。よく読めば意味がわからないわけではありませんが、なぜそんなことをするのかという意図がつかみにくいです。ましてや、一つ目と二つ目は毎回関数が生成されること、三つ目はbindの位置とメソッド定義位置が離れてしまうことを考えると、あまり良い方法とは言えません。

そこで考えられたのがクラスフィールド宣言を使う方法です。これを使った場合は次のように書けます。

JavaScript

1class CountSec { 2 constructor(count) { 3 this.count = count; 4 } 5 countDown = () => { 6 console.log(this.count); 7 if (this.count > 0) { 8 this.count--; 9 setTimeout(this.countDown, 1000); 10 } 11 } 12} 13const ct = new CountSec(10); 14ct.countDown();

countDownは親であるprototypeに登録されるようなメソッドではなく、インスタンス変数ではありますが、thisを束縛すると言うことはそのインスタンス専用になっている事であり、そこは問題にはなりません。thisの束縛があるという違い以外は通常のメソッドと同様に使えます。最初の三つのコードのような問題もありません。

さて、この方法を使う機会が思いつかないという人が居るかも知れませんが、使う機会が多いものを知っていますので、紹介します。それはReactです。Handling Events - Reactにその実例が載っています。Reactではクリックなどのイベント発生時に呼び出す関数を登録するのですが、そのままthis.handelEvent等と書いてしまうと同じ問題が発生します。それを防ぐ手段として、クラスフィールド宣言は有効とされています。Reactを使う場合、いずれにしてもJSXのためにBabelをつかいますから、Babelが対応しているクラスフィールド宣言を使ってもさほど問題が無いと考えられます。

最後に、通常とのメソッドとの違いです。一番大きいのは、このクラスフィールド宣言ではインスタンスが生成させる度にそのインスタンス変数としての関数が生成されると言うことです。そのため、本当に必要であるという場合以外は使用すべきではないでしょう。


【おまけ】

CoffeeScriptならもっとすっきり書けるよ!

CoffeeScript

1class CountSec 2 constructor: (count) -> 3 this.count = count 4 countDown: => 5 console.log(this.count) 6 if this.count > 0 7 this.count-- 8 setTimeout(this.countDown, 1000) 9ct = new CountSec(10) 10ct.countDown()

投稿2018/03/03 11:23

編集2018/03/03 11:34
raccy

総合スコア21737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoppy0066

2018/03/03 13:23

細かく教えていただきありがとうございます。納得できました。
guest

0

いまのところ、正式に取り入れられているのはmethod1の書き方のみです。

method2で書くと、メソッドがプロトタイプに乗っからない形となりますので、特殊な使い方をしたい場合以外にはメリットはありません(Babel)。

投稿2018/03/03 05:55

編集2018/03/03 06:01
maisumakun

総合スコア145876

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.38%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問