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

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

ただいまの
回答率

89.08%

prototypeに関数を定義する価値は?

解決済

回答 2

投稿

  • 評価
  • クリップ 9
  • VIEW 2,436

i50

score 219

メインがPHPで最近javascriptを勉強し始めた者です。
私の理解がそもそも違うということであれば、
指摘頂けるととても嬉しいです!

prototypeに関数を定義する価値ってどのような場合にあるのでしょうか。
以下の例でいうqux関数です。

var Hoge = function(){
    var foo = null;
    this.bar = null;
    this.baz = function(){};
};
Hoge.prototype = {
    qux:function(){}
};
Hoge.xyzzy = function(self){}
まずオブジェクト指向的に、隠すべき変数は隠すべきと思っています。
その為、bar変数ではなくて、foo変数を使うはずです。
(getter/setterが対であるような場合だけbarでいいかもしれませんが)
そして、そのfooを参照し処理を行うので、baz関数が必要です。
qux関数ではfooを参照できないので使用できないです。

オブジェクト指向は置いておいて…ということであれば、qux関数の価値は分かります。
fooを使わなければqux関数で事足りますし、
newするときにbazの生成にかかるコストが不要という価値があると思っています。
しかし、newを考えている時点でオブジェクト指向を意識していると思いますし、
そうなるとやはりfooを使うのでしょうから、やはりquxの使いどころがなさそうに思います。

qux関数の性質を考えてみますと、
quxは継承してもプロトタイプチェーンで呼び出せ、
qux関数内部で、thisで自身のインスタンスを参照できます。
しかし、qux関数を呼び出せる時点でthis相当の値を渡す事が出来るはずですし、
そもそもqux内部のみで呼び出せる変数は外部からも参照できるため、
quxの内部構造はいわゆるユーティリティ関数的であるはずで、インスタンスに結びついている必要はないように思います。
それであればxyzzyに必要な引数を渡せばいいのではないかと思います。


ここでいうqux関数はどういう時に定義し、使用するのでしょうか…。
よろしくおねがいします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

まずオブジェクト指向的に、隠すべき変数は隠すべきと思っています。 
「隠すべき変数は隠すべき」ですが、「車は車です」といっているような違和感が…。

まず、なぜインスタンスプロパティを隠すべきか?を考えてみてはどうでしょうか?
私としては「隠すべき理由」に「意図せずに書き換えられて他のプロパティとの整合性が取れなくなる」があげられると考えます。
ただし、全てのプロパティを隠すべきとは思いませんし、ECMAScript ではいくつかの解が設けられています。


Array#length は公開されていますが、length プロパティを書き換えると配列の要素数も書き換わる(setter)為、問題なく機能します。

var array = [1, 2, 3, 4, 5];
array.length = 1;
console.log(array); // [1]

Function#length は公開されていますが、length プロパティは読み取り専用の為、不正に書き換わることはありません。

function sum (a, b) { return a + b; }
sum.length = 3; // TypeError: Cannot assign to read only property 'length' of function sum(a, b) { return a + b; }
console.log(sum.length); // 2
console.log(Object.getOwnPropertyDescriptor(sum, 'length')); // Object {value: 2, writable: false, enumerable: false, configurable: true}

Array.prototype を拡張すると for-in で拡張した Array.prototype 上のプロパティが列挙される(いわゆる Array.prototype 汚染)がありますが、既存の Array.prototype.slice 等は for-in で列挙されません。
なぜなら、Array.prototype.slice は列挙不可能(enumerable === false)だからです。

var array = [], keys = [];
for (key in array) {  // enumerable なプロパティは列挙しない
  keys.push(key);
}
console.log(keys); // []
console.log(Object.getOwnPropertyDescriptor(Array.prototype, 'slice')); // {writable: true, enumerable: false, configurable: true}

いずれも、Object.defineProperty を使うことで実装可能です。
Object.defineProperty は IE7 で使えず、IE8 で DOM ノードにしか使えない機能制約がある為、事実上、IE9+ が対象ブラウザでなければ使えません。
制作ポリシーによっては2016/01/12までは使えない判断もあると思います。
Internet Explorer のサポート ライフサイクル ポリシーに関する FAQ


どうしても、プライベートプロパティが必要であれば、ES6 の WeakMap を使用する事で実装可能です。
WeakMap は ES3 の範囲内でも polyfill を書くことが出来る為、現在でも十分に実用的です。

var Hoge = (function () {
  var privateMap = new WeakMap;

  function Hoge (x, y) {
    privateMap.set(this, {x: Number(x), y: Number(y)});
  }

  Object.defineProperty(Hoge.prototype, 'sum', {writable: true, configurable: true, enumerable: false, value: function sum () {
    var obj = privateMap.get(this);

    return obj.x + obj.y;
  }});

  return Hoge;
})();

var hoge = new Hoge(2, 4);

console.log(hoge.sum()); // 6

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/20 12:18

    回答ありがとうございます!
    Object.definePropertyというものすごい機能があることに驚いています…。
    悩みは解消しました!

    レスしてまいります。

    >「隠すべき変数は隠すべき」ですが、「車は車です」といっているような違和感が…。
    表現が変で申し訳ありません…。隠すべき変数と思っているのは、
    ・ユーザに変更させたくない(責任範囲を明確にする)
    ・ユーザに見せるには実装寄りすぎる(処理内容を抽象的にする)
    といった変数で、当然全てを隠す必要はないと思っています。
    とはいえ、Object.definePropertyでのコントロールで、多くはなんとかなりそうな気がしています。
    また、後述のメモリリークの問題を考えると、
    javascriptという言語とプライベート変数は相性が悪そうですね…。



    >どうしても、プライベートプロパティが必要であれば、ES6 の WeakMap を使用する事で実装可能です。
    追加で2点ほど質問という形になってしまって申し訳ないです…。

    あえてWeakMapを使用されているように見受けられますが、
    弱参照でないとメモリリークの危険性があるという事でしょうか。
    例えば、privateMapの実装を強参照で自作した場合、
    hoge変数にnullを入れて参照を切ったつもりでいても、
    キーであるthis(実体はhoge)を、
    Hogeが保持しているprivateMapが強参照で保持している為、
    hogeの実体がGCに回収されずメモリリークする、という理解で良いのでしょうか。

    もう1点ですが、私の様な書き方ではなくて、例に挙げられたような
    var Hoge = (function () {
    //プライベートプロパティ
    var privateMap = new WeakMap;

    //コンストラクタ
    function Hoge (x, y) {}

    //メソッド
    Object.defineProperty(Hoge.prototype, 'sum', {writable: true, configurable: true, enumerable: false, value: function sum () {
    }});

    return Hoge;
    })();
    という書き方が一般的なのでしょうか。
    どうにも、javascriptでのオブジェクトの定義の書き方が色々あるように思えて
    これだというのがつかめておりません…。

    よろしくお願い致します。

    キャンセル

  • 2015/10/20 12:59 編集

    > ・ユーザに変更させたくない(責任範囲を明確にする)
    > ・ユーザに見せるには実装寄りすぎる(処理内容を抽象的にする)
    - なぜ「ユーザに変更させたくない」のか。責任分界点の判断基準はどこか。
    - 具体的にはどのような基準で「実装寄り」になるのか。
    を明確にすると更に具体的な考えに繋がると思います。

    > あえてWeakMapを使用されているように見受けられますが、弱参照でないとメモリリークの危険性があるという事でしょうか。
    WeakMap を使用している理由にメモリリークは関係なく、他のインスタンスのプライベートプロパティに参照不可能にする為です。
    仮に WeakMap を Map で代替した場合、Map.prototype.entries で全ての Iterator を参照出来る為、不正に書き換わる可能性があります。

    > 例に挙げられたような ... という書き方が一般的なのでしょうか。
    一般的かどうかは知りませんが、他人が使っているかどうかを気にする必要はないと思います。
    重要なのは自分が書いたコードが期待通りに動作する事です。

    # Function#length のコードを修正しました。

    キャンセル

  • 2015/10/20 14:11

    レスありがとうございます!

    >明確にすると更に具体的な考えに繋がると思います。
    おっしゃる通り、基準が明確ではないですね…。
    このあたりはjavascriptに限らない話だと思っていますので、良く考えてみます!

    >WeakMap を使用している理由にメモリリークは関係なく、他のインスタンスのプライベートプロパティに参照不可能にする為です。
    なるほど。意図が分かりました。
    弱参照という性質を保つため、iterate出来ないことを利用しているのですね。

    >重要なのは自分が書いたコードが期待通りに動作する事です。
    ありがとうございます。おっしゃる通り期待通りの動作をしなくては意味が無いですね…。
    私事ではありますが、今は一人でコードを書いているのですが、
    後任の方が来られた場合に「なんでこんな書き方をしているんだ?」
    という疑問が発生しないようにと、一般的な書き方があれば近づけたいとの思いからでした。

    キャンセル

+1

PHPはよく分からないのですが、こういうことでは。

var Hoge = function(){
    var foo = null;
    this.bar = null;
    this.baz = function(){return Hoge.corge(foo)};
    this.quux = function(){return this.qux(foo)};    
};
Hoge.prototype = {
    qux:function(x){return x}
};
Hoge.xyzzy = function(self){return self.foo}
Hoge.corge = function(x){return x}

var instance = new Hoge();
Hoge.xyzzy(instance); //undefined
instance.baz(); //null
instance.quux(); //null
クラスメソッド的な定義をするとインスタンスのprivateなメンバを参照できませんし、インスタンスメソッドから呼ぶのもなんか気持ちが悪いです←
もちろん、quxの処理をコンストラクタに書いても同じことができますが、インスタンスごとに作成されてしまいますから、効率が悪い場合もあるのかなあ、と。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/20 08:54

    回答ありがとうございます!

    >クラスメソッド的な定義をするとインスタンスのprivateなメンバを参照できません
    >インスタンスごとに作成されてしまいますから、効率が悪い場合もある
    ありがとうございます!このあたりは承知しております。

    ポイントは
    >インスタンスメソッドから呼ぶのもなんか気持ちが悪い
    >this.baz = function(){return Hoge.corge(foo)};
    あたりとなるのでしょうか。
    私はこの書き方も、場合によってはありえると思っています。
    corgeの処理がHogeの責任範囲と十分関係があり、かつインスタンスの特定が不要な場合
    このような書き方も発生しえるかなと考えています。

    キャンセル

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

  • ただいまの回答率 89.08%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る