読者です 読者をやめる 読者になる 読者になる

latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

prototypeを拡張することで得られるもの。prototype拡張指向へのスイッチ

(ε・◇・)з mofmof.js や新しい uupaa.js では prototype拡張を活用しています
(ε・◇・)з 長所がわかりづらいみたいなので、言い出しっぺのボクの中の人がメリットを列挙してみよー というエントリです
(ε・◇・)з でも、長文になりそうなので、思いついたらちょっとずつ書き足していって、後で再編するよー というノリです
(ε・◇・)з タイトルも適当です

かわいい子には旅をさせるよ ( HTMLElement#cut )

子ノード(div)を親ノードからパージする処理を考えてみましょう。

---✂-------------------✂---
if (div.parentNode) {
    div.parentNode.removeChild(div); // div.parentNode = null
}
---✂-------------------✂---
52 bytes

prototype拡張ならシンプルに書けます。

---✂-------------------✂---
div.cut(); // div.parentNode = null
---✂-------------------✂---
10 bytes

子供ほしいよ ( HTMLElement#add, HTMLElement#top )

自分が親となり子供を追加する処理を考えてみましょう。死んだと思っていた長男がいきなり登場するケースにも備えましょう

---✂-------------------✂---
div.insertBefore(child, div.firstChild); // 先頭に追加
div.appendChild(child); // 末尾に追加
---✂-------------------✂---
62 bytes

prototype拡張ならシンプルに書けます。

---✂-------------------✂---
div.top(child);
div.add(child);
---✂-------------------✂---
30 bytes

HTMLElement.prototype.cut, add, top の実装です。
IE8 には HTMLElement は存在しませんが、Element を使うとモダンブラウザと同様に実装できます。

HTMLElement.prototype.cut = HTMLElement_cut;
HTMLElement.prototype.add = HTMLElement_add;
HTMLElement.prototype.top = HTMLElement_top;

// HTMLElement.prototype.cut
function HTMLElement_cut() { // @ret this: this node
                             // @desc: to cut a parent-child connection
    if (this.parentNode) {
        this.parentNode.removeChild(this);
    }
    return this;
}

// HTMLElement.prototype.add
function HTMLElement_add(node) { // @arg Node:
                                 // @return this:
                                 // @desc: appendChild
    node && this.appendChild(node);
    return this;
}

// HTMLElement.prototype.top
function HTMLElement_top(node) { // @return this:
                                 // @desc: insert to top
    node && this.insertBefore(node, this.firstChild);
    return this;
}

(ε・◇・)з DOM はフルスペルの長い名前を使う傾向があるので、短い名前は割とセーフな気がするー って言ってました(中の人が)

数値の配列が欲しいよ (Number#to)

1から100までの数値の配列を作成する処理を考えてみましょう。

function createNumberArray(begin, end) {
    var rv = [], i = begin;

    for (; i <= end; ++i) {
        rv.push(i);
    }
    return rv;
}
createNumberArray(1, 100); // -> [1, .. 100]

JavaScript中級者の方は、以下のようなテクニカルなコードを好むかもしれませんね。

function createNumberArray(begin, end) {
    return Array(end - begin + 1).join(",").split(",").map(function(value, index) {
        return begin + index;
    });
}
createNumberArray(1, 100); // -> [1, .. 100]

では、100から1までの数値の配列や、1つ飛ばしの数値の配列、フィルター関数を適用した配列を考えてみましょう。毎回このような配列を生成するコードを書くのは面倒ですし、createNumberArray という関数名も、もうちょっとこう… ってなりますよね。

そこで、Number.prototype.to を拡張し、1..to(100) とするだけで、簡単に 1から100までの数値の配列を作れるようにしました。

function isX5(n) { // Multiples of 5 filter
  return n % 5 === 0;
}

1..to(100);       // -> [1, 2 .. 99, 100]
100..to(1);       // -> [100, 99 .. 2, 1]
1..to(100, 2);    // -> [1, 3 .. 97, 99] (skip 2)
1..to(100, isX5); // -> [5, 10, .. 95, 100] (filter)

Number#to の実装です。

Number.prototype.to = Number_to;

// Number#to
function Number_to(end,      // @arg Number: end number
                             //      `` 終了番号を数値で指定します。filterが1ならendで指定した終了番号を含みます
                   filter) { // @arg Function/Number(= 1): filter or skip number
                             //      `` フィルター関数またはスキップ数を指定します。省略可能です
                             // @ret Array: [begne, ... end]
                             // @this: begin number`` 開始番号です
                             // @raise: Error("BAD_ARG") `` 引数が不正です
                             // @see: Array.range, Number#to
                             // @help: Number#Number.prototype.to
                             // @desc: create number array `` 数値の配列を作成します
//{@assert
    mm.allow(end,    "Number");
    mm.allow(filter, "Function/Number/undefined");
//}@assert

    var rv = [], ri = 0, begin = +this, i = begin, iz = end, skip = 1,
        type = typeof filter,
        reverse = false;

    if (begin > end) { // 100..to(0) -> [100, ... 0]
        i = end;
        iz = begin;
        reverse = true;
    }

    if (type === "function") {
        for (; i <= iz; ++i) {
            if (filter(i) === true) { // `` filter が true が返すと index を採用します
                rv[ri++] = i;
            }
        }
        return reverse ? rv.reverse() : rv;
    }
    if (type === "number") {
        skip = filter;
    }
    if (skip <= 0 || end >= 0x7FFFFFFF) { // [!] array index over flow
        // new Array(2147483647) in [IE6][IE7][IE8] problem
        // http://msdn.microsoft.com/en-us/library/gg622936(v=VS.85).aspx
        //  - Internet Explorer 9 Handles Array Elements
        //    with a Large Index Differently
        //
        throw new Error("BAD_ARG");
    }
    for (; i <= iz; i += skip) {
        rv[ri++] = i;
    }
    return reverse ? rv.reverse() : rv;
}

for in ガード

なお、mofmof.js や uupaa.js の中ではprototype拡張部分が for in で列挙されないように mm.wiz 関数を使って拡張しています。

mm.wiz(Number.prototype, {
  to:  Number_to
});


// mm.wiz
function mm_wiz(base,       // @arg Object/Function/ObjectOrFunctionArray: base object
                extend,     // @arg Object: extends object. { method: function, ... }
                override) { // @arg Boolean(= false): override
                            // @ret Object/Function: base
                            // @help: mm#mm.wiz
                            // @desc: prototype extend without enumerability, mixin with "invisible" magic

    // [IE8] Object.defineProperty: Supports DOM Node but not user-defined objects.
    // [IE8] Object.defineProperties: Not supported
    var defineProperty = Object.defineProperty && Object.defineProperties,
        keys = (Object.keys || Object_keys)(extend), obj,
        ary = (Array_isArray(base) && base[0]) ? base : [base],
        key, method, i = 0, iz = keys.length;

    while (obj = ary.shift()) {
        for (i = 0; i < iz; ++i) {
            key = keys[i];
            method = extend[key];

            if (override || !(key in obj)) {
                if (defineProperty) {
                    Object.defineProperty(obj, key, { // DataDescriptor
                        configurable: true,
                        enumerable: false, // invisibility
                        writable: true,
                        value: method
                    });
                } else {
                    obj[key] = extend[key];
                }
            }
        }
    }
    return base;
}