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

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

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

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

Q&A

解決済

5回答

7728閲覧

「コンストラクタ関数内でメソッドを定義せざるを得ないケースはほとんど無い」という認識はどうなんでしょう?

tchofu

総合スコア87

JavaScript

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

0グッド

6クリップ

投稿2015/08/14 01:32

JavaScriptにおいて、JavaやC++で言うところのメンバ関数を実現しようとすると、コンストラクタ関数内で定義する方法と、プロトタイプにメソッドを追加する方法があると思います。これについて、前者はオブジェクトが生成されるたびにメソッドが追加されるため効率が悪い、と聞きました。例えば末尾に記した例では、new Person()が呼ばれるたびにsay()が追加されることが冗長である、ということだと理解しています。

この話を聞いて以降、私はプロトタイプにメソッドを定義するようにしています。幸いなことにまだ困った状況に遭遇していません(Javaのクラス/オブジェクトのように使えている)。「コンストラクタ関数内でメソッドを定義せざるを得ないケースはほとんど無い」とさえ思っています。

しかし、ふと気付きました。これは勉強不足だ、単に経験不足だと。

そこで書籍を読んでいるところなのですが、まだ「コンストラクタ関数内でメソッドを定義したほうが良い」と思える状況に出会えていません。

コンストラクタ関数内でメソッドを定義した方が良い、せざるを得ないような状況というか、イディオムを紹介していただけないでしょうか?

コンストラクタ関数内にメンバ関数を定義:

javascript

1var Person = function(name) { 2 this.name = name; 3 this.say = function() { // こうやってメソッドを定義することはあるのだろうか? 4 return "I am " + this.name; 5 }; 6};

プロトタイプにメソッドを追加する:

javascript

1var Person = function(name) { 2 this.name = name; 3}; 4Person.prototype.say = function() { 5 return "I am " + this.name; 6};

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

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

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

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

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

guest

回答5

0

プライベートメンバーを実現したい場合なんかどうでしょうか。

javascript

1var Person = function(name) { 2 var _name = name; 3 this.say = function() { 4 return "I am " + _name; 5 }; 6};

この例だと、クロージャ内に閉じ込められた変数_namesayメソッド以外からアクセスする方法はありません。

(実行時にはブレークポイントでいくらでも覗けますが、開発時に限ってはプライベートです。)

開発時にプライベートメンバーに間違ってアクセスすることを防ぐことには成功していますが、そもそも分かりづらい本末転倒感があります。
this._nameみたいに_から始まるものはプライベートというコーディング規約を決めておくほうが現実的でしょうし、ある程度市民権も得ています。

そもそも、「JSのプロトタイプの仕組みを使用した継承機構」と、「プライベートメンバー」は両立できないという結論が定説のようです。
というのも、JSでプライベートを実現するにはクロージャによる閉じ込みくらいしか方法がありません。
つまり、インスタンスごとに異なるクロージャを用意する必要があるため、ひとつのプロトタイプメンバーを使いまわしていては実現不可能というわけです。

上の例では、メソッドsayPersonをインスタンス化する度に別々に定義されてしまい無駄がありますが、別々だからこそ、それぞれが異なる_name変数にアクセスできるというわけです。

投稿2015/08/14 08:33

tozjp

総合スコア790

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

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

tchofu

2015/08/14 11:02

回答ありがとうございました。 自分の理解を確実にするため復唱させてください。 プライベートメンバーの中でも、変更する予定の無いプライベートメンバーと考えてよろしいでしょうか?コンストラクタ関数の中で this.setName = function (newName) { name_ = newName; } と定義してしまうと外部から変更できてしまうと思います。つまり、こういう(setNameのような)メソッドは作らない設計を前提とするようなものに適用するって理解で大きく外していないと思ってもいいでしょうか? ここまで書いていて思ったのですが、コンストラクタ内でメソッドを定義するのは、「クラスに固定できる定数」(例えばMathクラスのPIとか)のようなimmutableなもののために使うという理解でもいいのでしょうか?
退会済みユーザー

退会済みユーザー

2015/08/14 15:56

このパターンはDIのように、オブジェクト間の関係を固定させるような用途にも利用できそうですね。
tozjp

2015/08/21 02:47

すみません、遅くなりました。 > プライベートメンバーの中でも、変更する予定の無いプライベートメンバーと考えてよろしいでしょうか? いいえ。"外部から直接"変更する予定の無いプライベートメンバーです。(というよりプライベートメンバーとはそういうものです。) つまり内部的にはいくらでも変更していいし、外部からの操作によって結果的に(間接的に)書き換わる分には構わないということです。 > コンストラクタ関数の中で(略)と定義してしまうと外部から変更できてしまう 結果的には変更できていますが、 _name 変数そのものにはアクセスできていません。 メソッド経由のみでアクセスできるということは、クラスが変数へのアクセスを完全に管理できるということです。 詳しくはこちらの質問が参考になるかと。(Javaでの話ですが。) https://teratail.com/questions/8078 > コンストラクタ内でメソッドを定義するのは、「クラスに固定できる定数」(例えばMathクラスのPIとか)のようなimmutableなもののために使う 確かに、getter だけを定義して setter を定義しなければ、外部から見た時のみ immutable にすることができますね。 ただし Math.PI の場合は Math.getPI() になっちゃうので、ちょっと例としては違いますかね。(しかもこれやるなら`Math.getPI = function(){return 3.14;};`で十分ですね。) 実際のMathに近い動きを実現する場合、 descriptor という機能を使用します。 話が大幅に逸れる上に長い解説記事がかけちゃう濃い内容なので中身は割愛しますが、 上の別質問で maisumakun さんが書かれている「外から見れば変数代入に見えるけど、内部的にはアクセサを動作させる」みたいなことがJSでもできます。
guest

0

ベストアンサー

コンストラクタ内でメソッドを定義するという方法を初めて知りました。
逆に目から鱗です。
今まで知らなかったということは、私自身は一度も必要になる場面に
出会ったことがないのだと思います。

一応、こういう場面なら必要ではというのを考えてみました。
なお、functionとかprototypeとかの綴りが面倒だったのでCoffeeScriptで書いています。
http://coffeescript.org/ の TRY COFFEESCRIPT で変換して下さい。
classで書いていますが、JavaScriptで変換したのを見ていただければ、
意味は同じであるとわかると思います。

  1. コンストラクタ内のローカル変数を束縛しておきたい!

CoffeeScript

1class Counter 2 constructor: -> 3 i = 0 4 @count = -> 5 return ++i 6 7a = new Counter() 8console.log a.count() # => 1 9console.log a.count() # => 2 10console.log a.count() # => 3

呼び出すたびにカウントが増えるというよくあるやつです。
コンストラクタ内のローカル変数iを束縛するには上のようにするしかありません。
ただ、このパターンですと、そもそもiをインスタンス変数にすればいいだけです。

CoffeeScript

1class Counter2 2 constructor: -> 3 @i = 0 4 count: -> 5 return ++@i
  1. コンストラクタの引数によってメソッドの動作を変えたい!

下記はコラッツの問題の変換処理で次を求めていく整数のラッパクラスです。

CoffeeScript

1class Collatz 2 constructor: (@num) -> 3 if @num % 2 == 0 4 @next = -> new Collatz(@num / 2) 5 else 6 @next = -> new Collatz(3 * @num + 1) 7 8c = new Collatz(6) 9console.log c.num # => 6 10console.log c.next().num # => 3 11console.log c.next().next().num # => 10 12console.log c.next().next().next().num # => 5 13console.log c.next().next().next().next().num # => 16

数字が偶数か奇数かで次を求める式が変わるようにして、
効率よく?計算しているような気だけします。
ただこれも、単に毎回場合分けしたほうが、スッキリとかけます。

CoffeeScript

1class Collatz2 2 constructor: (@num) -> 3 next: -> 4 if @num % 2 == 0 5 new Collatz(@num / 2) 6 else 7 new Collatz(3 * @num + 1)

もし、@numがインスタンス変数でなかったとしても、1.と同じで、
インスタンス変数にすればいいだけの話です。

以上の2つが考えつきましたが、どちらも適当なインスタンス変数を持てばいいだけですし、
動作も複雑になって、かえって見通しが悪いプログラムになってしまいました。
必須になるような場面は無いのではないでしょうか?

投稿2015/08/14 08:29

raccy

総合スコア21735

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

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

tchofu

2015/08/14 11:24 編集

回答ありがとうございます。 とてもわかりやすい例をありがとうございました。100%理解できたわけではありませんが、ご提示いただいたコードを眺めていてなんとなく見えたような気がします。ただ、もうちょっと修行を積んでからあらめて考えることにします。 現時点の私の考えは、 「コンストラクタ内のメソッドでローカル変数を束縛することはあまり無いが、その性質を利用したテクニックとして利用する場合がある。 換言すれば、私のような素人は積極的に使う必要は無いけど、熟練者になってくるとこの性質を生かしたくなる場面が出てくる可能性がある」 です。
guest

0

物によって処理が違う場合とかなら使いそうですがコンストラクタ内で定義と言うのかというと微妙。
クロージャ作る時が一番使う可能性あるのかな。

javascript

1function A(){ 2 A.prototype.getName=function (){ 3 return this.name; 4 }; 5 A.prototype.setMethod=function (_funcName,_func){ 6 this[_funcName]=_func; 7 }; 8 A.prototype.name="Jon"; 9}; 10 11var c=new A(); 12c.setMethod( 13 "getName", 14 function (){ 15 return this.name+" : nice name"; 16 } 17) 18c.getName();//Jon : nice name 19 20/* 21c.getName=function (){ 22 return this.name+" : nice name"; 23}; 24と同じなのでsetMethod()なんて作らなくてもいい気もする 25*/

投稿2015/08/14 15:36

Cf_cwd

総合スコア730

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

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

tchofu

2015/08/16 00:34

回答ありがとうございました。 本筋じゃないかもしれませんが、 A.prototype.name="Jon"; という書き方は始めてみました。クラス共有変数っぽく使えるんですね。勉強になりました。 全てを理解するのは今の私にとって難しいので、もうちょっと勉強してから読み返したいと思います。
guest

0

まず、プロパティとメソッドを区別する必要はありません。
コンストラクタ処理の中で決定されるプロパティ、メソッドはコンストラクタ内で定義します。

JavaScript

1function Person (name, callbackfn) { 2 this.name = name; // nameプロパティはコンストラクタ処理中に値が決定される 3 this.hoge = callbackfn; // hogeメソッドはコンストラクタ処理中に値が決定される 4}

インスタンスが生成される度に値が動的に変化するプロパティ、メソッドはコンストラクタ内で定義してやり、常時同じ処理、値になるプロパティ、メソッドは prototype 上で定義してやります。


ECMAScript 5 には private 変数がありません。
従って、private 変数と同様の処理をする場合にはコンストラクタ内のローカル変数にクロージャで値を閉じ込める必要があります。

JavaScript

1function Person (name) { 2 this.getName = function getName () { return name; }; // private変数 name への getter 関数 3} 4 5new Person('Ken').getName(); // "Ken"

getName メソッドは常時同じ処理を行うメソッドですが、ローカル変数 name を参照する為にはコンストラクタ内で定義してやる必要があります。
インスタンスが生成される度にメモリを消費するので効率がよくありませんが、ECMAScript 5 の範囲内ではどうしようもありません。

ECMAScript 6 にも private 変数はありませんが、WeakMap を使うことで private 変数のようなものを定義できるようになりました。

JavaScript

1var Person = (function () { 2 var privateMap = new WeakMap; 3 4 function Person (name) { 5 privateMap.set(this, name); 6 } 7 8 Person.prototype.getName = function getName () { 9 return privateMap.get(this); 10 } 11 12 return Person; 13}()); 14 15console.log(new Person('Ken').getName()); // "Ken" 16console.log(new Person('Alice').getName()); // "Alice"

投稿2015/08/14 08:43

編集2015/08/14 08:48
think49

総合スコア18162

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

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

tchofu

2015/08/14 10:49

回答ありがとうございます。 もしもう少しお付き合いいただけるなら、『getName メソッドは常時同じ処理を行うメソッドですが、ローカル変数 name を参照する為にはコンストラクタ内で定義してやる必要があります』という箇所について確認させてください。 「常時同じ処理を行うメソッド」と表現されているメソッドですが、これは”nameはimmutableに使うこととする”設計を前提にされていると考えてよろしいでしょうか? つまり、 function Person (name) { this.getName = function getName () { return name; }; this.setName = function setName (newName) { name = newName; } // コンストラクタ関数外部からnameにアクセス可能になってしまうのでやらない } とsetNameは定義しない前提ということでしょうか?
think49

2015/08/14 11:55 編集

「常時同じ処理を行うメソッド」は name が immutable という意味ではなくて「メソッドの処理を後で作りかえる必要がない」という意味です。 ご掲示の例でいえば、getName, setName は共にメソッドの処理は動的に変化しないので本来は prototype 上に存在して良いものですが、name を参照する為にはコンストラクタ内で定義するしかありません。 つまり、ES5 の範囲内で処理するならば、setName もコンストラクタ内で定義する必要があります。 最も、WeakMap は Polyfill を作りやすい仕様なので、Polyfill を適用して WeakMap で実装するコードが個人的にはお勧めです。 (20:54追記) 言い換えるなら「コンストラクタ処理が始まる前に処理が決定しているメソッド」でしょうか。 getName, setName は共に private 変数 name を操作する処理が予め決定しているのでコンストラクタ内に収める必要はなく、prototype 上にあるべきメソッドです。。 親記事の callbackfn はコンストラクタが呼び出されるまではメソッドの処理が決定しないので prototype 上に定義することが出来ません。
tchofu

2015/08/16 00:39

回答ありがとうございます。 「コンストラクタ処理が始まる前に処理を決定しておきたい(変更したくない)」がポイントであることはなんとなく判りました。 コンストラクタ関数内でメソッドを定義する意義は掴めたと思うので、次は用途について気長に考えることとします。
guest

0

2019年時点では、「イベントハンドラなど、クラス外部へ引き回せるように、thisをインスタンスに固定したメソッドを作りたい」という場合に、コンストラクタ内での生成(あるいはそれに相当するclass property + arrow function)がよく行われています。

jsx

1class SomeComponent extends React.Component{ 2 constructor(props) { 3 super(props); 4 // 別なところに引っ張り出しても、thisはこのインスタンスから切れない 5 this.handler1 = () => console.log(this); 6 } 7 8 // まだ提案中だけど、こんな書き方もある 9 handler2 = () => console.log(this); 10}

投稿2019/03/03 04:17

maisumakun

総合スコア145183

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問