一回も披露したことのない豆知識

jQuery-1.6.2のソースコードを見ているのですが質問させてください。


25行目の
varjQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
},
そして100行めの
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
の意味が分かりません。

PHP等のclassになれているとJSのクラスは戸惑うことが多いのですが、この例は最たるもので、自分の中のJSでのclassは
(1)スーパークラスのプロパティをcall()、apply()で継承
(2)スーパークラスのメソッドをprototypeオブジェクトで継承
(3)インスタンスのデータ型判定に必要なプロパティをconstructorで調整
だったのですが、スーパークラスがどの行のどれなのかも分かりません。
型やメソッド名からこれらがclassを意味しているのは分かるのですが、いったいどの部分がclass定義、継承を行っているのか教えていただけませんでしょうか?
なぜこれらがclassになっているのかが理解できません。

分るのは、jQuery.prototype.constructor = jQueryで、それをjQuery.fnに代入((3))している事です。
329行目の、jQuery.fn.init.prototype = jQuery.fn;の右辺がnew {$class}の形であれば(2)のプロトタイプチェーンの形だと分るのですが。
(1)は、init内でメンバ変数を定義したりしているので、classなのかな、という程度しか分っておりません。
jQuery.fnがコンストラクタで、jQueryがインスタンスで、var jQueryは親クラスをinitしたもの?・・・と混乱しております。
質問内容も文章が混乱していて申し訳ないです。

A 回答 (2件)

次のように書き換えてみましょう。



//
function jQuery (selector, context) {
 return new init(selector, context, rootjQuery);
}

jQuery.prototype = jQuery.fn;

//
function init (selector, context, rootjQuery) {
 /* ......
  this.length = 0;
 ...... */
}

init.prototype = jQuery.fn;

jQuery.fn.init = init;

本質的にはこういうことです(私が書くなら、こんな感じにすると思います)。したがって、new jQuery と new init は、どちらも jQuery.fn をプロトタイプとする新規オブジェクトを生成します。

---
さて、No.1 には書きませんでしたが、JavaScript のコンストラクタには 1 つの珍しい特徴があります。それは、コンストラクタがオブジェクトを return すると、new の戻り値がそのオブジェクトになってしまう、ということです。

上記の jQuery コンストラクタを見て下さい。普通、new jQuery して得られる結果は『jQuery.prototype をプロトタイプとする新規オブジェクト』だと思うでしょう。ところが、jQuery コンストラクタ内では new init の結果を return しています。だから、new jQuery の結果は、new init の結果になるのです。

var o = new jQuery; // 実は o = new init と同じ!

この o は init.prototype(= jQuery.fn)をプロトタイプとするオブジェクトですから、ちゃんと目的のものではあります。なら、jQuery.prototype をセットしているのは何のため? とお思いかもしれません。それは、instanceof の結果を調節するためです。

// o instanceof C は、o のプロトタイプが C.prototype なら true を返す
o instanceof init; // true
o instanceof jQuery; // false

これではちょっと格好悪い。そこで jQuery.prototype をセットすれば、

jQuery.prototype = init.prototype;
o instanceof jQuery; // true

はい、この通り。こんな感じで、JavaScript では instanceof の結果を後でいくらでも変更できる、というわけです。まあ、小手先の話です。

---
それと、No.1 では見落としておましたが、この書き方にはもう一つの目的があると思います。それは、new の付け忘れへの対処(あるいは new を付けなくても良いようにすること)です。

もう一度、上記の init コンストラクタを見て下さい。new init すれば、新規オブジェクトの固有プロパティとして length がセットされます。

var o = new init(...); // o.length = 0

ここで、うっかり new を付け忘れたとしたらどうなるでしょう? init() という関数呼び出しでの this はグローバルオブジェクト(ブラウザ上では Window)になり、length というグローバル変数が作られることになってしまいます。

var o = init(...); // window.length = 0

そこで、上記の jQuery コンストラクタのように書けば、new の有無に関わらず、等しく new init の結果が得られる、というわけです。

var o1 = new jQuery(...); // new init(...) の結果
var o2 = jQuery(...); // new init(...) の結果

利用者に公開するのは jQuery の方であり、init は隠し(?)メンバですから、まずはこれで十分と言えます。これも小手先と言えば小手先の話です。
    • good
    • 1
この回答へのお礼

Chaireさん、本当に本当にありがとうございました。
だいぶJSの事が分かったように思います。

サイ本、独習、マスター、他数冊本は読んである程度JSは理解していたつもりだったのですが(まぁ結構JSからは離れていて忘れている部分も多いでしょうが)、prototypeの部分などどの本にも載っていないような事を教えていただき非常に分かりやすかったです。
コンストラクタ内のreturn文は禁止と独習、マスター共にハッキリと書かれていましたが、そのような利用法があるとは驚きました。

Prototypoe.jsはザッと見た感じ、素直な書き方だと思いましたが、jQueryは自分には理解できない記述が多く困っておりました。
確か何かに、jQueryは芸術的な美しいコードとか書かれていたのを思い出しましたが、その分自分には難しかったです。

Chaireさんに出会えて本当に良かったと思っております。
親切丁寧にお答えいただいて、本当にありがとうございました。

お礼日時:2011/08/13 20:34

クラスベース言語の「クラス」「継承」という概念をいったん捨てて下さい。

とりあえず、個人的な見解ですが:

(a). プロトタイプは「デフォルト値を集約したオブジェクト」である。各オブジェクトはどのオブジェクトを「デフォルト値の集合」とするか選ぶ。

(b). デフォルト値以外が必要なときはオブジェクト固有のプロパティを生やす。オブジェクトが固有プロパティを持たなければ「デフォルト値」を探す。

JavaScript の「オブジェクト指向」とは、これだけの話です。new C(p, q) は、C.prototype を「デフォルト値集合」とし、p と q を固有プロパティの値とするオブジェクトを生成します。Object.create(O, props) は、O を「デフォルト値集合」とし、props を固有プロパティとするオブジェクトを生成します。

---
> (2)スーパークラスのメソッドをprototypeオブジェクトで継承

「デフォルト値」はメソッドに限った話ではありません。単に、メソッドは「デフォルト値」のままで十分なことが多い、というだけです。

> (1)スーパークラスのプロパティをcall()、apply()で継承

メソッドでも「デフォルト値」のままで困る場合は固有のメソッドを生やせば良い。call()、apply() を使えば他のコンストラクタから固有プロパティを輸入できますが、他のオブジェクトのプロパティ群を for...in で回して移植しても構いません。固有プロパティを生やすことが重要なわけで、何を使うかは問題ではありません。

> (3)インスタンスのデータ型判定に必要なプロパティをconstructorで調整

上書き自由な constructor でインスタンス判定することは、まずありません。この場合は単に constructor の「デフォルト値」をセットしただけです。そうすれば、new (this.constructor) のように多態的なオブジェクト複製に使われる、かもしれない。とにかく、これ自体は本質的な話ではありません。

---
前置きが長くなりました。今ダウンロードした jquery-1.6.2.js の行数で書きますが、

99 行目:
jQuery.fn = jQuery.prototype = ...

323 行目:
jQuery.fn.init.prototype = jQuery.fn;

両者から、次の関係が分かります。
jQuery.fn.init.prototype = jQuery.prototype

ゆえに、obj = new (jQuery.fn.init) によって jQuery.prototype(== jQuery.fn)をプロトタイプとするオブジェクト obj が生成されます。また、obj の固有プロパティも init() 内でセットされます。

JavaScript の instanceof 演算子は単純にプロトタイプチェーンを辿るので、jQuery.prototype を書き換えない限り、obj instanceof jQuery も true になります。表面的には「obj は jQuery クラスのインスタンス」のように見えるでしょう。しかし、それはあくまで見かけの話に過ぎません。

繰り返しますが、クラスのない JavaScript で「どれがクラスか」を考えても仕方ありません。重要なのは「どれが obj のプロトタイプか」と、「どれが obj の固有プロパティか」という 2 点だけです。

---
何でこんな書き方になっているか。想像するに、ひとつは単なるショートカット。ひとつは jQuery.fn に基本機能を集約させ、jQuery コンストラクタにぺたぺた貼付けられるプロパティから保護すること。ひとつは、jQuery の振る舞いを変更したいときに、jQuery コンストラクタそのものを書き換えるのでなく、init を切り替えるだけで済むこと。

今ぱっと思い付いたテキトーな理由ですが、それだけにしては大仰な書き方なので、他の目的もあるでしょう。

この回答への補足

Chaireさん、極めて分かりやすいご説明ありがとうございました。
JSのクラスに関して、考え方が変わりました。
ありがとうございました。


しかし、
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
},
の部分の、
>new C(p, q) は、C.prototype を「デフォルト値集合」とし、p と q を固有プロパティの値とするオブジェクトを生成
により、jQuery.fn.init.prototype= jQuery.fn=jQuery.prototypeをデフォルト値集合とするオブジェクトがjQuery自身というのまだよく分かりません。
この構図だとjQuery自身が自分を・・・ってことになっているように思えるのですが、これはどういう意味なのでしょうか?

申し訳ございません。

補足日時:2011/08/11 22:44
    • good
    • 1
この回答へのお礼

ごめんなさい、"お礼入力"で文章を書くべきでした。

お礼日時:2011/08/11 23:11

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!