はじめに
今回から、
想定している読者は、
今回の連載では執筆開始時点の最新版であるバージョン1.
Prototypeライブラリの構成
名前空間
このライブラリでは、
名前 | 実体 |
---|---|
Prototype | オブジェクト |
Class | オブジェクト |
Abstract | オブジェクト |
Try | オブジェクト |
PeriodicalExecuter | Prototypeライブラリ式クラス |
Template | Prototypeライブラリ式クラス |
$break | 空オブジェクト |
$continue | Error形のインスタンス、 |
Enumerable | オブジェクト |
$A | Array. |
$w | 関数オブジェクト |
Hash | コンストラクタとしての関数オブジェクト |
$H | 関数オブジェクト |
ObjectRange | Prototypeライブラリ式クラス |
$R | 関数オブジェクト |
Ajax | オブジェクト |
$ | 関数オブジェクト |
Element | オブジェクト |
Toggle | オブジェクト |
Insertion | オブジェクト |
Selector | Prototypeライブラリ式クラス |
$$ | 関数オブジェクト |
Form | オブジェクト |
Field | オブジェクト。Form. |
$F | 関数オブジェクト。Form. |
Event | オブジェクト |
Position | オブジェクト |
他のライブラリや、
オブジェクト、クラスの使われ方
ライブラリ内では、
- その下に別のオブジェクトを入れるための親名前空間として使う
- Class.
create()を使って Prototypeライブラリ風のクラスとして定義する - Object.
extend()を使って他のクラス、 オブジェクトから継承されることを前提とする関数を集める - コードを簡潔に記述する為に短い名前の関数として使う
特に、
コードを追っていく場合は、
既存オブジェクトへの拡張
Prototypeライブラリの特徴の一つとして、
大きくはElementやArray、
これらの追加メソッドは便利なものが多いのですが、
JavaScriptでコード上でプロパティとして追加されたメソッドには、
このことを忘れていると、
var a = new Array;
a.push(100);
a.push(200);
for (var i in a) {
console.log(i);
}
というコードでは、
この場合は、
先頭のコメント、著作権表示
0001: /* Prototype JavaScript framework, version 1.5.1.1
0002: * (c) 2005-2007 Sam Stephenson
0003: *
0004: * Prototype is freely distributable under the terms of an MIT-style license.
0005: * For details, see the Prototype web site: http://www.prototypejs.org/
0006: *
0007: /*--------------------------------------------------------------------------*/
0008:
7行目まではコメントです。PrototypeライブラリはMITスタイルのライセンスであることが宣言されています。 このライセンスであるおかげで、
Prototype オブジェクト
0009: var Prototype = {
0010: Version: '1.5.1.1',
0011:
0012: Browser: {
0013: IE: !!(window.attachEvent && !window.opera),
0014: Opera: !!window.opera,
0015: WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
0016: Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
0017: },
0018:
0019: BrowserFeatures: {
0020: XPath: !!document.evaluate,
0021: ElementExtensions: !!window.HTMLElement,
0022: SpecificElementExtensions:
0023: (document.createElement('div').__proto__ !==
0024: document.createElement('form').__proto__)
0025: },
0026:
0027: ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
0028: JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
0029:
0030: emptyFunction: function() { },
0031: K: function(x) { return x }
0032: }
0033:
まず、
以前はVersion、
ドキュメント化されていなかったこともあり、
9行目において、
10行目ではPrototype.
if (parseFloat(Prototype.Version.split(".")[0] + "." +
Prototype.Version.split(".")[1]) < 1.5) {
throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");
}
Prototypeライブラリの特定のバージョン以降を要求するような場合には、
12行目からはPrototype.
例えばif (Prototype.
Prototype.
Prototype.
19行目からはPrototype.
Prototype.
Prototype.
Prototype.
真の場合、
27行目では、
28行目では、
30行目は何もしない関数Prototype.
(this.options['on' + state] || Prototype.emptyFunction)(transport, json);
31行目も似たような関数で、
Classオブジェクト
0034: var Class = {
0035: create: function() {
0036: return function() {
0037: this.initialize.apply(this, arguments);
0038: }
0039: }
0040: }
0041:
Prototypeライブラリにおいて
一般的なクラスベースのオブジェクト指向型言語とは異なり、
JavaScriptのprototypeプロパティを用いたオブジェクトの継承は、
そこで、
まず、
var Foo = function(arg1, arg2) {
this.prop1 = "p1";
};
Foo.prototype.prop2 = "default value";
var instance1 = new Foo("bar", "baz");
alert(instance1.prop1); // "p1"が表示される
alert(instance1.prop2); // "default value"が表示される
このように、
準備ができたら、
そして、
次に、
var NewClass = Class.create();
NewClass.prototype = {
initialize: function(arg1, arg2) {
this.attr1 = 'value1';
this.attr2 = 'value2';
},
memberFunction: function(arg) {
},
prop1: null
}
var instance2 = new NewClass(1, 2);
Class.
C++やJavaなどの言語では、
ここで、
さて、
function() {
return function() {
this.initialize.apply(this, arguments);
}
}
となっています。そのため、
function() {
this.initialize.apply(this, arguments);
}
という関数オブジェクトを返すということになります。
先ほど、
その次の行から、
apply()は、
その結果、
Abstractオブジェクト
0042: var Abstract = new Object();
0043:
Abstractオブジェクトは、
Object.extend()
0044: Object.extend = function(destination, source) {
0045: for (var property in source) {
0046: destination[property] = source[property];
0047: }
0048: return destination;
0049: }
0050:
ECMA-262に準拠している環境においては、
45行目にあるfor文により、
これにより、
Object.extend(既存オブジェクト, {
プロパティ:値
})
この関数は、
Object オブジェクトへの拡張
0051: Object.extend(Object, {
0052: inspect: function(object) {
0053: try {
0054: if (object === undefined) return 'undefined';
0055: if (object === null) return 'null';
0056: return object.inspect ? object.inspect() : object.toString();
0057: } catch (e) {
0058: if (e instanceof RangeError) return '...';
0059: throw e;
0060: }
0061: },
0062:
0063: toJSON: function(object) {
0064: var type = typeof object;
0065: switch(type) {
0066: case 'undefined':
0067: case 'function':
0068: case 'unknown': return;
0069: case 'boolean': return object.toString();
0070: }
0071: if (object === null) return 'null';
0072: if (object.toJSON) return object.toJSON();
0073: if (object.ownerDocument === document) return;
0074: var results = [];
0075: for (var property in object) {
0076: var value = Object.toJSON(object[property]);
0077: if (value !== undefined)
0078: results.push(property.toJSON() + ': ' + value);
0079: }
0080: return '{' + results.join(', ') + '}';
0081: },
0082:
0083: keys: function(object) {
0084: var keys = [];
0085: for (var property in object)
0086: keys.push(property);
0087: return keys;
0088: },
0089:
0090: values: function(object) {
0091: var values = [];
0092: for (var property in object)
0093: values.push(object[property]);
0094: return values;
0095: },
0096:
0097: clone: function(object) {
0098: return Object.extend({}, object);
0099: }
0100: });
0101:
先程定義したObject.
ここではObjectオブジェクトに対して、
52行目からのinspect()は、
まず54行目で、
55行目ではnull値と比較しています。こちらも等しければ'null'という文字列を返します。
56行目では、
複雑な型の場合は、
これらの三行での挙動が、
- 公式APIドキュメント - Object.
inspect - prototype.
js v1. 5.0 の開発者向けメモ - Objectクラスに対する拡張 - ECMA-262 第三版 - 11.
9.4 === 演算子 - ECMA-262 第三版 - 4.
3.9 undefined 値 - ECMA-262 第三版 - 9.
8 toString
63行目からはオブジェクトをJSON化するためのtoJSON()メソッドの定義です。
まず、
ここで、
- ECMA-262 第三版 - 12.
9 The return statement
83行目からはkeys()メソッドです。オブジェクトが保持しているプロパティのうち、
コード上は特に癖もなく、
90行目から、
97行目からはclone()メソッドです。単純にオブジェクトの
JavaScriptにおいて、
Functionオブジェクトへの拡張
0102: Function.prototype.bind = function() {
0103: var __method = this, args = $A(arguments), object = args.shift();
0104: return function() {
0105: return __method.apply(object, args.concat($A(arguments)));
0106: }
0107: }
0108:
Functionオブジェクトのbind()メソッドです。これはFunction.
通常のオブジェクトに対するメソッド呼び出しでは、
ここでは、
まず、
104行目で関数が定義され、
bind()の呼び出し自体が終わると、
105行目で使われているargumentsは、
- thisはbind()で指定された最初の引数
- arguments
(引数の配列) は、 bind()で指定された2番目以降の引数に、 返された関数の呼出し時に渡された引数を加えたもの
となります。
- ECMA-262 第三版 - 15.
3.4. 3 Function. apply() - Wikipedia - クロージャ
0109: Function.prototype.bindAsEventListener = function(object) {
0110: var __method = this, args = $A(arguments), object = args.shift();
0111: return function(event) {
0112: return __method.apply(object, [event || window.event].concat(args));
0113: }
0114: }
0115:
やっていることはFunction.
違いは、
関数が呼び出される際には、
Number オブジェクトへの拡張
0116: Object.extend(Number.prototype, {
0117: toColorPart: function() {
0118: return this.toPaddedString(2, 16);
0119: },
0120:
0121: succ: function() {
0122: return this + 1;
0123: },
0124:
0125: times: function(iterator) {
0126: $R(0, this, true).each(iterator);
0127: return this;
0128: },
0129:
0130: toPaddedString: function(length, radix) {
0131: var string = this.toString(radix || 10);
0132: return '0'.times(length - string.length) + string;
0133: },
0134:
0135: toJSON: function() {
0136: return isFinite(this) ? this.toString() : 'null';
0137: }
0138: });
0139:
Object.
以下、
117行目からはNumber.
CSSなどで、
121行目からは succ()メソッドです。これはsuccessorの略のようで、
同名のメソッドがStringにも追加されています。
125行目からは times()メソッドです。これは$R()関数を使い、
130行目からはtoPaddingSring()メソッドです。数値を文字列に変換しますが、
まず131行目でnをradixを基とする数値表現文字列に変換しています。(radix || 10)としているので、
その文字列に対して、
最終的にこの'0'...に131行目で作ったstringを接続して返します。
135行目からはtoJSON()メソッドです。まずisFinite()を呼び出し、
JavaScriptに存在するNaNをわざわざ'null'に変換してJSON
- ECMA-262 第三版 - 15.
1.2. 5 The Global Object, isFinite() - RFC 4627 : The application/
json Media Type for JavaScript Object Notation (JSON)
Dateオブジェクトへの拡張
0140: Date.prototype.toJSON = function() {
0141: return '"' + this.getFullYear() + '-' +
0142: (this.getMonth() + 1).toPaddedString(2) + '-' +
0143: this.getDate().toPaddedString(2) + 'T' +
0144: this.getHours().toPaddedString(2) + ':' +
0145: this.getMinutes().toPaddedString(2) + ':' +
0146: this.getSeconds().toPaddedString(2) + '"';
0147: };
0148:
native objectであるDateを拡張して、
記述が長いのでわかりにくいのですが、
Tryオブジェクト
0149: var Try = {
0150: these: function() {
0151: var returnValue;
0152:
0153: for (var i = 0, length = arguments.length; i < length; i++) {
0154: var lambda = arguments[i];
0155: try {
0156: returnValue = lambda();
0157: break;
0158: } catch (e) {}
0159: }
0160:
0161: return returnValue;
0162: }
0163: }
0164:
Tryオブジェクトを定義していますが、
Try.
引数の先頭から処理していき、
例外が発生した場合、
今のところ、
PeriodicalExecuterクラス
0165: /*--------------------------------------------------------------------------*/
0166:
0167: var PeriodicalExecuter = Class.create();
0168: PeriodicalExecuter.prototype = {
0169: initialize: function(callback, frequency) {
0170: this.callback = callback;
0171: this.frequency = frequency;
0172: this.currentlyExecuting = false;
0173:
0174: this.registerCallback();
0175: },
0176:
0177: registerCallback: function() {
0178: this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
0179: },
0180:
0181: stop: function() {
0182: if (!this.timer) return;
0183: clearInterval(this.timer);
0184: this.timer = null;
0185: },
0186:
0187: onTimerEvent: function() {
0188: if (!this.currentlyExecuting) {
0189: try {
0190: this.currentlyExecuting = true;
0191: this.callback(this);
0192: } finally {
0193: this.currentlyExecuting = false;
0194: }
0195: }
0196: }
0197: }
PeriodicalExecuterクラスです。Class.
169行目からがコンストラクタです。メンバー変数としてcallback、
177行目からがそのregisterCallback()メソッドで、
先に 187行目のonTimerEvent()メソッドを確認しましょう。ここでは、
そして、
こうすることによって、
逆にいうと、
最後に181行目のstop()メソッドで、