jQuery
この文章はjQuery 2.1.4を元に書かれています。
jQueryでは$.fnを拡張することで、$()の返り値となるjQueryオブジェクトにメソッドを追加できます。
次のgreenifyプラグインでは、$(document.body).greenify();というメソッド呼び出しが可能になります。
(function ($) {
$.fn.greenify = function () {
this.css("color", "green");
return this;
};
})(jQuery);
実際に利用する際は、jquery.jsを読み込んだ後にgreenify.jsを読み込ませる必要があります。
<script src="jquery.js"></script>
<script src="greenify.js"></script>
どのような仕組み?
このjQueryプラグインがどのような仕組みで動いているのかを見てみましょう。
jQueryプラグインはprototype拡張のように $.fn.greenify = function (){} と拡張するルールでした。
jQuery.fnの実装を見てみると、実態はjQuery.prototypeなので、prototype拡張していることがわかります。
// https://github.com/jquery/jquery/blob/2.1.4/src/core.js#L39
jQuery.fn = jQuery.prototype = {
// prototypeの実装
};
$()は内部的にnewをしてjQueryオブジェクトを返すので、このjQueryオブジェクトではprototypeに拡張したメソッドが利用できます。
$(document.body); // 返り値はjQueryのインスタンス
つまり、jQueryプラグインはJavaScriptのprototypeをそのまま利用しているだけに過ぎないということがわかります。
どのような用途に向いている?
jQueryプラグインの仕組みがわかったのでどのような用途に有効な仕組みなのか考えてみましょう。
単純なprototype拡張なので、利点はJavaScriptのprototypeと同様です。動的にメソッドを追加するだけではなく、既存の実装を上書きするmonkey patchのようなものもプラグインとして追加できます。
どのような用途に向いていない?
これもJavaScriptのprototypeと同様で、prototypeによる拡張は柔軟すぎるため、 jQuery自体がプラグインのコントロールをすることは難しいです。
また、プラグインが拡張するjQueryの実装に依存しやすいため、 jQueryのバージョンによって動かなくなるプラグインが発生しやすいです。
jQueryではドキュメント化されてないAPIを触っていけないというルールを設けていますが、これは必ずしも守られているわけではありません。
実装してみよう
calculatorという拡張可能な計算機をjQuery Pluginと同じ方法で作ってみたいと思います。
calculator は次のような形となります。
function calculator(value = 0) {
if (!(this instanceof calculator)) {
return new calculator(value);
}
this.value = value;
}
// alias
calculator.fn = calculator.prototype;
export default calculator;
$.fnと同様にprototypeへのaliasを貼っているだけのただのコンストラクタ関数です。
calculator.fn = calculator.prototype;
calculator(初期値)と書けるようにしているため、少し特殊なコンストラクタとなっていますが、この拡張の仕組みとは関係ないのでとりあえず置いておきましょう。
calculator.jsには何も実装が入ってないので、プラグインで四則演算の実装を追加してみます。
import calculator from "./calculator";
calculator.fn.add = function (x) {
this.value += x;
return this;
};
calculator.fn.sub = function (x) {
this.value -= x;
return this;
};
calculator.fn.multi = function (x) {
this.value *= x;
return this;
};
calculator.fn.div = function (x) {
this.value /= x;
return this;
};
calculator-plugin.jsでは、calculator.fn.addというようにaddというメソッドを追加しています。
また、モジュールで依存関係を示していますがやっていることはjQueryと同じで、calculator.jsを読み込んでからcalculator-plugin.jsを読み込んでいるだけですね。
<script src="calculator.js"></script>
<script src="calculator-plugin.js"></script>
これを使うとcalculator#addといったメソッドが利用できるようになるので、次のように書くことができます。
import assert from "assert";
import calculator from "./calculator";
import "./calculator-plugin"; // Extend
const resultValue = calculator(0).add(10).multi(10).value;
assert.equal(resultValue, 10 * 10);
実装をみてもらうと分かりますが、JavaScriptのprototypeの仕組みをそのまま利用しています。そのため、「拡張する時はcalculator.prototypeの代わりにcalculator.fnを拡張する」というルールがあるだけともいえます。
エコシステム
このプラグインの仕組みはあるグローバルオブジェクトに依存しており、これはスクリプトを<script>要素で読み込むだけで拡張することを前提とした作りです。
<script src="jquery.js"></script>
<script src="greenify.js"></script>
Node.jsで使われているCommonJSやES6 Modulesなどがなかった時代に作られた仕組みなので、それらと組み合わせる際には少し不向きな拡張の仕組みといえるかもしれません。
まとめ
ここではjQueryのプラグインアーキテクチャについて学びました。
- jQueryプラグインは
jQuery.fnを拡張する jQuery.fnはjQuery.prototypeと同じである- jQueryプラグインとは
jQuery.prototypeを拡張したものといえる - 何でもできるためプラグインが行うことを制御することは難しい