ビルトインオブジェクトに拡張したい10のメソッド

ビルトインオブジェクトをこねくり回すのはあまり好まれない方法だけど、僕は好きなので、割とやりたくなる。というか、やる。でも、やりはじめると際限なく拡張したくなってしまう。というわけで、最近は、とりあえず10と決めているので、それを書いてみることにした。

あ、IE5には対応していないよ!

Array.prototype.each(callback [, thisObject])、Array.prototype.map(callback [, thisObject])、Array.prototype.filter(callback [, thisObject])

いきなりそれかよw、と思われそうだが、シェアの大きなブラウザで実装されていないのだから、仕方ない。mapとfilterはあるだけで幸せだ。jQueryのようなチェーンで記述することができるのだから(どんだけ、富豪プログラミングになるかは、知ったこっちゃ無い)。あとforEachと入力するのは面倒だから、eachとする。手がつらないためにも重要。everyとsomeは知っているけど、ほとんど使ったことないから無くても別にいいや。

実装コード
Array.prototype.each = Array.prototype.forEach || (Array.prototype.forEach = function (x, y) {
	for (var i = 0, j = this.length; i < j; ++i)
		if (i in this)
			x.call(y, this[i], i, this);
});
Array.prototype.map || (Array.prototype.map = function (x, y) {
	var i = 0, j = this.length, r = new Array(j);
	for (; i < j; ++i)
		if (i in this)
			r[i] = x.call(y, this[i], i, this);
	return r;
});
Array.prototype.filter || (Array.prototype.filter = function (x, y) {
	var i = 0, j = this.length, r = [], c = -1;
	for (; i < j; ++i)
		if (i in this) {
			var v = this[i];
			if (x.call(y, v, i, this))
				r[++c] = v;
		}
	return r;
});

何気なくforEachを直しているから11個になるとか言わないで><;

Array.prototype.clone()

配列のコピーを作る。でも、thisの型をチェックするような実装にしている。何でかって言うと、

function $A(o) {
	return Array.prototype.clone.call(o);
};

こうするためw。sortが破壊的なので、sort前にクローンを作ってからソートするとかに使います。

実装コード
Array.prototype.clone = function () {
	var r = [], i = this ? this.length : null;
	if (i != null) {
		if (this instanceof Array)
			return this.concat();
		else if (this.callee)
			return Array.prototype.slice.call(this);
		else
			while (i)
				r[--i] = this[i];
	}
	return r;
};

Array.prototype.eachAsync(callback, first, step, msec [, thisObject])

関数名長いのは、普段使わない。使わないけど使う。そんな非同期each。

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].eachAsync(function (v) {
	document.writeln(v);
}, 5, 3, 250, window);

とすると、1、2、3、4、5がすぐに出力されて、実行にかかった時間+250ms後に6、7、8、さらに実行にかかった時間+250ms後に9、10を出力する。何度もDOMツリーに項目を追加するときなどはこれを使う。

実装コード
Array.prototype.eachAsync = function (x, y, z, w, v) {
	var a = this, i = 0, j = a.length, l = j > y ? y : j;
	for (; i < l; ++i)
		if (i in a)
			x.call(v, a[i], i, a);
	if (l < j)
		setTimeout(function () {
			l += z;
			if (j < l)
				l = j;
			for (; i < l; ++i)
				if (i in a)
					x.call(v, a[i], i, a);
			if (j > l)
				setTimeout(arguments.callee, w);
		}, w);
};

Date.prototype.format(format)

printfは+で文字列結合を簡単にできる言語だからいらないけど、strftimeは必要だと思います。getMonthとかメソッド名を長々書くのは面倒ですし。

実装コード
Date.prototype.format = function (x) {
	var
		d = this,
		p = function (x) { return ("0" + x).slice(-2); },
		e = d.getDate(),
		k = d.getHours(),
		m = d.getMonth(),
		w = d.getDay(),
		Y = d.getFullYear(),
		Z = d.getTimezoneOffset() * -1,
		f = {
			a: [" 1月", " 2月", " 3月", " 4月", " 5月", " 6月", " 7月", " 8月", " 9月", "10月", "11月", "12月"],
			A: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
			b: ["日", "月", "火", "水", "木", "金", "土"],
			B: ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"],
			c: "%Y年%m月%d日 %H時%M分%S秒",
			C: Math.floor((Y - 1) / 100),
			d: p(e),
			D: "%m/%d/%y",
			e: e,
			F: "%Y-%m-%d",
			G: "[not yet]",
			g: "[not yet]",
			H: p(k),
			I: p(k % 12 || 12),
			j: "[not yet]",
			k: k,
			l: k % 12 || 12,
			m: p(m),
			M: p(d.getMinutes()),
			n: "\n",
			p: ["AM", "PM"],
			P: ["午前", "午後"],
			r: "%I:%M:%S %p",
			R: "%H:%M",
			s: Math.floor(d.getTime() / 1000),
			S: p(d.getSeconds()),
			t: "\t",
			T: "%H:%M:%S",
			u: w || 7,
			U: "[not yet]",
			V: "[not yet]",
			w: w,
			W: "[not yet]",
			x: "%Y年%m月%d日",
			X: "%H時%M分%S秒",
			y: p(Y % 100),
			Y: Y,
			z: Z * -1,
			Z: (Z < 0 ? "" : "+") + p(Math.floor(Z / 60)) + ":" + p(Z % 60)
		};
	return x.replace(/%(.)/g, function (x, y) {
		if (y == "h") y = "b";
		switch (y) {
			case "a":
			case "A": return f[y][w];
			case "b":
			case "B": return f[y][m];
			case "c":
			case "D":
			case "F":
			case "r":
			case "R":
			case "x":
			case "X": return d.format(f[y]);
			case "p":
			case "P": return f[y][k < 12 ? 0 : 1];
		}
		return (y in f) ? f[y] : y;
	});
};

not yetも全部実装したバージョンがあったはずだけど、どっかいったorz ただ、strftime覚えにくいからそこまで好きじゃない……。そのうち新しいフォーマット構文みたいなの作ってみたい。

Function.prototype.wait(msec)

setTimeoutって書くのが面倒だよね。うん、面倒だ。というわけで、それで囲ってくれる関数を書いた。

(function (a, b) {
	document.writeln(a + b);
}).wait(200)(1, 2);

setTimeoutで囲われた関数を返します。

実装コード
Function.prototype.wait = function (x) {
	var f = this;
	return function () {
		var a = arguments, q = this;
		return setTimeout(function () {
			f.apply(q, a);
		}, x);
	};
};

Function.prototype.curry([length])

カリー化、って名前はつけたけど、カリー化を理解していないせいで違うかもしれない。
curryメソッドを実行すると常に引数が足りなかったら、部分適用された関数が返り、引数が足りたら結果を返す関数が返る。可変長配列や引数の省略に対応するためにメソッドの引数に関数が要求する引数の数が書くことが出来る。あと、カリー化された関数も引数無しで実行すると不十分な数の引数で実行することが出来る。まぁ、JavaScriptだとそういう関数多そうだし。

// sum言う割りに引数三つまでwでも、全部、省略可
function sum(a, b, c) { return (a || 0) + (b || 0) + (c || 0); }
// curryしてみる。
var tmp = sum.curry();
print(tmp(1, 2)(3)); // 6
print(tmp(1)()); // 1

// avg
function avg() {
	var a = arguments, s = 0, n = a.length;
	for (var i = 0; i < n; ++i) s += a[i];
	return s / n;
}
var tmp = avg.curry(4);
print(tmp(4)(3)(2, 1)); // 2.5
print(tmp(1, 2, 3, 4, 5, 6)); // 3.5
print(tmp(4)()); // 4

// 文字列を入れて返す
function abc(a, b, c) {
	return "A:" + a + ", B: " + b + ", C: " + c;
}
// 束縛する順序も自由自在
var tmp = abc.curry();
print(tmp(undefined, "B")("A", "C")); // A: A, B: B, C: C

カリー化とは違うような気がするけど、マイカリーって感じで気に入ってはいる。

実装コード
Function.prototype.curry = function (x) {
	var f = this, g = function (x, y, z) {
		return function () {
			var a = x.concat(), o = arguments;
			if (o.length === 0)
				return f.apply(this, a.slice(0, z));
			var k = -1, l = o.length, c = true, s, e;
			for (var i = y, j = a.length; i < j; ++i) {
				if (a[i] === undefined) {
					if (o[++k] !== undefined) {
						a[i] = o[k];
						e = i + 1;
					}
					else {
						c = false;
						if (s === undefined)
							s = i;
					}
				}
				else
					e = i + 1;
			}
			if (++k < l) {
				for (var i = a.length; k < l; ++k)
					a[i++] = o[k];
				e = a.length;
			}
			if (c)
				return f.apply(this, a);
			return g(a, s || y, e || z);
		};
	};
	return g(new Array(typeof x === "number" ? x : f.length), 0, 0);
};

正直言うと、バグが怖くて、自分が管理できる場所でしか、まだ、使ったことがない。

Function.prototype.init

Class.init()はnew Class()と同じ、つまり、インスタンスを作る関数。

Date.init().getTime();

こういう風に書けるので好き。

実装コード
Function.prototype.init = function () {
	var a = arguments, f = this;
	switch (f) {
		case Array:
		case Boolean:
		case Date:
		case Function:
		case Number:
		case Object:
		case RegExp:
		case String:
			var t = [];
			for (var i = 0, j = a.length; i < j; ++i)
				t[i] = "a[" + i + "]";
			return eval("new f(" + t.join(",") + ")");
		default:
			var c = function () {};
			c.prototype = f.prototype;
			var t = new c(), r = f.apply(t, a);
			return (r instanceof Object) ? r : t;
	}
};

組み込みだと挙動がおかしいので、evalでやる。

String.prototype.h

最後、10個目はcakePHPから名前を取ったhtmlspecialcharsを行う関数。

実装コード
String.prototype.h = function () {
	return this
		.replace(/&/g, "&amp;")
		.replace(/"/g, "&quot;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;");
};

という感じで10個のメソッドは割と使ったりしています。もっと便利なの、より良い実装、バグがあったら教えてください。