JavaScript: defer関数でいろいろ

JavaScript: 普通に同期処理 - mayokara note

var defer = function(/* f1, f2, ... */){
    var self = this, fn = Array.prototype.slice.call(arguments);
    (function(/* arg2, arg3, ... */){
        var args = Array.prototype.slice.call(arguments);
        (fn.shift()).apply(self, [arguments.callee].concat(args));
    })();
};

この関数の説明をきちんと書いてなかったので補足。

bind

deferのthisが、引数に与えた関数すべてのthisになる。

defer.call(this, F1, F2, F3);

のように呼ぶことで、F1、F2、F3すべてにbind(this)(Prototype.js)するのと同じ効果が得られる。

wait(あるいはsleep、pause)

1秒のwaitを置く例。

defer(function(next){
    alert("1000ms wait");
    window.setTimeout(function(){ next("finished"); }, 1000);
}, function(next, str){
    alert(str); // -> (alert "finished")
});

deferもnextも、いくつでも引数を取れる。

nextに与えた第n引数は次の関数の第n+1引数になる。

第1引数はnext(次の関数を呼ぶ関数)で固定。

各関数の返り値は意味を持たない。defer自身も値を返さない。

loop

defer(function loop(next, i){
    i = i || 0;
    if (!(i < 5)) return next();
    console.log("count " + i);
    window.setTimeout(function(){ loop(next, i+1); }, 1000);
}, function(next){
    console.log("5 times loop finished");
});
count 0
count 1
count 2
count 3
count 4
5 times loop finished

nextをきちんと引き継げばloopもできる。

setTimeoutに与えた関数の中ではarguments.calleeが使えないので、ループさせる関数にloopという名前をつけ、それを使う。

前述のdefer.callでthisをbindしている場合は、それに合わせてloopにthisをセットする必要がある。

defer(function(next){
    next(0);
}, function loop(next, i){
    if (!(i < 5)) return next();
    console.log("count " + i);
    window.setTimeout(function(){ loop(next, i+1); }, 1000);
}, function(next){
    console.log("5 times loop finished");
});

毎回i = i || 0;を実行するのが無駄に感じる場合はこうする。

同様にして、特定の条件を満たすまで次の関数の実行を引き伸ばしたりできる。

parallel

defer(function(next){
    var obj = {},
        stock = function(key, value){
            obj[key] = value;
            // 全部揃ったらnextを呼ぶ
            if (obj.a && obj.b && obj.c) next(obj.a, obj.b, obj.c);
        };
    window.setTimeout(function(){ stock("a", "AAAAA"); }, 3000);
    window.setTimeout(function(){ stock("b", "BBBBB"); }, 2000);
    window.setTimeout(function(){ stock("c", "CCCCC"); }, 5000);
}, function(next, a, b, c){
    alert(a + b + c); // (alert "AAAAABBBBBCCCCC")
});

setTimeoutの代わりに、(GM_)XMLHttpRequestなどが入る。

objそのものを引き渡しても良い。

「ads-display-none」というデータベースをwedataに作った

アイテム - データベース: ads-display-none - wedata

ads-display-none

御察し下さい。


どなたでも自由に編集していただいて構いませんが、データベース名の頭3文字を良くお読みになった上、余計なものが含まれないようお願いします。

ブログパーツや単なるサイドバー、フッターなどは対象外です。

Fw: GNU Core Utilities Project Page

GNU Core Utilities - 概要 [Savannah]

各種コマンドのC実装がブラウザで見れる。

gitで管理されていて、srcディレクトリにコードがある。


static char const rfc_2822_format[] = "%a, %d %b %Y %H:%M:%S %z";
static char const rfc_3339_format[][32] =
  {
"%Y-%m-%d",
"%Y-%m-%d %H:%M:%S%:z",
"%Y-%m-%d %H:%M:%S.%N%:z"
  };
static char const iso_8601_format[][32] =
  {
"%Y-%m-%d",
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S,%N%z",
"%Y-%m-%dT%H%z",
"%Y-%m-%dT%H:%M%z"
  };

date.cより。メモ。

はてなブックマークのエントリページで特定ユーザのコメントを非表示にするUserCSS

長音表記のガイドライン無視>タイトル


@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document url-prefix(http://b.hatena.ne.jp/entry/) {

#bookmark-user-mayokara384,
#bookmark-user-mayokara384,
#bookmark-user-mayokara384,
#bookmark-user-mayokara384,
#bookmark-user-mayokara384
{
    display: none !important;
    /* あるいは -moz-opacity: 0.5 !important; など */
}

}

userstyles.orgとかにあるのかもしれないけど、メモ。

mayokara384の部分を対象とするユーザ名に変える。

フィードをオートディスカバリーするXPath、Firefox互換

フィードをオートディスカバリーする XPath - IT戦記


http://mxr.mozilla.org/mozilla/source/browser/base/content/pageinfo/feeds.js#39

http://mxr.mozilla.org/mozilla/source/browser/base/content/utilityOverlay.js#639

'/x:html/x:head/x:link[@href and (contains(concat(" ",translate(normalize-space(@rel),"DEF","def")," ")," feed ") or (contains(concat(" ",translate(normalize-space(@rel),"AELNRT","aelnrt")," ")," alternate ") and not(contains(concat(" ",translate(normalize-space(@rel),"EHLSTY","ehlsty")," ")," stylesheet ")) and (starts-with(normalize-space(@type),"application/rss+xml") or starts-with(normalize-space(@type),"application/atom+xml") or ((starts-with(normalize-space(@type),"text/xml") or starts-with(normalize-space(@type),"application/rdf+xml") or starts-with(normalize-space(@type),"application/xml")) and contains(concat(" ",translate(normalize-space(@title),"RS","rs")," ")," rss ")))))]'

x:というのはデフォルト名前空間を取るためのprefixなので、resolver使わないなら消してもOK。

JavaScript: createDocumentFromJSONString

(GM_)XMLHttpRequestなどで取ってきたJSON文字列をDOMツリーに変換する関数を書いたのでメモ。

var createDocumentFromJSONString = function(str){
    var json = (new Function('return (' + str + ');'))(),
        jsonDoc = document.implementation.createDocument("", "json", null);
    if (json instanceof Array) {
        jsonDoc.documentElement.setAttribute("type", "array");
        json = { jsonarray: json };
    } else if (typeof(json) === "object") {
        if (json === null) {
            jsonDoc.documentElement.setAttribute("type", "null");
            return jsonDoc;
        } else {
            jsonDoc.documentElement.setAttribute("type", "object");
        }
    } else {
        jsonDoc.documentElement.setAttribute("type", ((typeof(json) === "boolean") ? String(json) : typeof(json)));
        jsonDoc.documentElement.textContent = String(json);
        return jsonDoc;
    }
    (function(o, elem){
        for (var i in o) {
            if (o[i] instanceof Array) {
                for (var j=0,l=o[i].length; j<l; j++) {
                    var tmp = {};
                    tmp[i] = o[i][j];
                    arguments.callee(tmp, elem);
                };
            } else if (typeof(o[i]) === "object") {
                var child = jsonDoc.createElement(i);
                if (o[i] === null) {
                   child.setAttribute("type", "null");
                } else {
                   child.setAttribute("type", "object");
                   arguments.callee(o[i], child);
                }
                elem.appendChild(child);
            } else {
                var child = jsonDoc.createElement(i);
                child.setAttribute("type", ((typeof(o[i]) === "boolean") ? String(o[i]) : typeof(o[i])));
                child.textContent = String(o[i]);
                elem.appendChild(child);
            }
        }
    })(json, jsonDoc.documentElement);
    return jsonDoc;
};

({
    count: "",
    title: "",
    bookmarks: [{
        comment: "",
        tags: ["", ""],
        timestamp: "",
        user: "",
    }, {
        comment: "",
        tags: ["", ""],
        timestamp: "",
        user: "",
    },
        ...
    ],
    url: "",
    ...
})

こんな構造のJSONが

<json type="object">
    <count type=""></count>
    <title type=""></title>
    <bookmarks type="object">
        <comment type=""></comment>
        <tags type=""></tags>
        <tags type=""></tags>
        <timestamp type=""></timestamp>
        <user type=""></user>
    </bookmarks>
    <bookmarks type="object">
        <comment type=""></comment>
        <tags type=""></tags>
        <tags type=""></tags>
        <timestamp type=""></timestamp>
        <user type=""></user>
    </bookmarks>
    ...
    <url type=""></url>
    ...
</json>

こんな構造のツリーに変換される(値は邪魔なので省略)。


JavaScriptの構文だとJSONからjson.bookmarks[*].userみたいな抽出がしたいとき、for文で回さないといけない(はず)。

DOMツリーに変換すれば、XPathで//bookmarks/userとできる。

追記

booleanのtrueとstringの"true"などの区別ができるように、type属性にobject/string/number/true/false/null(ルートのみarrayの場合あり・後述)のいずれかの文字列をセットするよう修正。

JSONでは真偽値がbooleanではなくtrue/falseと別々に定義されているようなので、それに従った。


また、ルートがobjectではなくarrayやstringだったときのための前処理を付け加えた。

ルートがarrayだった場合はdocumentElementを複数作れないので

<json type="array">
    <jsonarray type="object">
    </jsonarray>
    <jsonarray type="object">
    </jsonarray>
</json>

のようにjsonarray要素が作られ、documentElementのtype属性の値がarrayとなる。

XPathで参照する場合は//jsonarray/user/json/*/userのようにする(苦肉の策)。


true/falseの場合はtextContentにもtrue/falseという文字列をセットするが、nullの場合は空になるようにしてある。

Fw: 2churl.net

2ちゃんねるタイトルURLジェネレータ

2chの板名とキーワードを元に、最新スレッドに転送するURLを生成するサービス。

Fw: eroga.aurifero.us

エロゲー画像をスライドショーするサイト作った/楽

eroga.aurifero.us

えろげサンプル画像ローダー。18禁。

読み込み待ちのストレスをjQueryアニメーションで軽減というのが良い。


原画スタッフの名前で検索できなかったのがちょっと残念ではあるけど、まあ、それはそれ。

というかDMMのラインナップ、偏ってるなあ。

JavaScript: document.evaluate、nodeValue、デフォルト名前空間

$gを更新した。

それ絡みで気付いたことのメモ。

nodeValueとtextContent

tmp = xp.snapshotItem(i);
ret[i] = tmp.nodeValue || tmp.textContent;

第3引数にtypeofがobjectとなる引数を与えた場合の挙動を改良。


DOM:element.nodeValue - MDC

//a/@hrefのようにしてAttribute Nodeを集めたとき、それらのnodeValueプロパティを参照すると属性値(href属性の値)が得られる。

Element Nodeのときはnullになるので、この場合はtextContentをみる。

$g('//a', document, {})
// -> アンカーテキスト
//    ["mayokara note", "Older >", ...]
$g('//a/@href', document, {})
// -> href属性値
//    ["http://mayokara.info/note/", "http://mayokara.info/note/1", ...]

で、こういうことができる。

ただしa要素のhrefプロパティから取得した場合とは違い、Absolute URIにはならない。

Relative URIで書かれているものは、そのまま。

デフォルト名前空間について

var doc = context.ownerDocument || context, defaultNamespaceURI = (/xml$/).test(doc.contentType) ? doc.documentElement.getAttribute("xmlns") : "";
var resolver = function(prefix){
    return doc.createNSResolver(context).lookupNamespaceURI(prefix) || defaultNamespaceURI;
};

XHTMLのみを対象とするのならhttp://www.w3.org/1999/xhtml決め打ちで良いのだけど、RSS 1.0を(GM_)XMLHttpRequestで取得してDOMParserに食わせたものにXPathを使いたいときはhttp://purl.org/rss/1.0/を返す必要がある。

Atomであればhttp://www.w3.org/2005/Atom

そういうときのための修正。


デフォルトリゾルバは XML 文書のデフォルト名前空間を処理しません。

Introduction to using XPath in JavaScript - MDC # XML 文書のデフォルト名前空間を実装する

createNSResolverはdcやらrdfやらに引っ掛らなかったときにデフォルト名前空間のURIを返さない。nullが返る。

なので、documentElementのxmlns属性を見て、それを返すようにする。

namespaceURIプロパティは、RSS 1.0のようにdocumentElementのnodeNameがrdf:RDFのときxmlns:rdfのURIを返してくるので使えない。


デフォルト名前空間を含め、名前空間の接頭辞は宣言している要素とその子孫で有効ですが、子孫要素で同じ接頭辞(あるいはデフォルト)の名前空間が宣言されると、その内部では新しい宣言によって結びつきが上書きされます。

XML名前空間の簡単な説明 # デフォルト名前空間

厳密にはcontextから(取得されるノードから?)遡って調べなければならないのだけど、手を抜いた。

やるとすれば、contextの兄弟ノードとして適当なノードをinsertして、それのnamespaceURIプロパティを取るのが楽そう。


ついでに、きちんとcreateNSResolverを使うようにした(dc:dateなどのため)。

余録:Firefox2のDOMParser

RSS 1.0を食わせたとき、documentElementの子要素以下のノードが取れない。

Bugzillaにあたってみたけど、探すのに不得手なせいで見つけられなかった。

Firefox3だとちゃんと取れる模様。

JavaScript: appendChild, cloneNode, extractContents, cloneContentsとイベントリスナ

DocumentFragmentとdisplay:none、documentに直接追加する場合の速度比較 - 素人がプログラミングを勉強するブログ

Array.forEach(document.getElementsByTagName("a"), function(v){
    v.addEventListener("mouseover", function(){alert(1);}, false);
});
var df = document.createDocumentFragment();
df.appendChild(document.body);
document.documentElement.appendChild(df);

// (mouseover) -> alert
Array.forEach(document.getElementsByTagName("a"), function(v){
    v.addEventListener("mouseover", function(){alert(1);}, false);
});
var df = document.createDocumentFragment();
df.appendChild(document.body.cloneNode(true));
document.documentElement.replaceChild(df, document.body);

// (mouseover) -> none
Array.forEach(document.getElementsByTagName("a"), function(v){
    v.addEventListener("mouseover", function(){alert(1);}, false);
});
var r = document.createRange();
r.selectNode(document.body);
document.documentElement.appendChild(r.extractContents());

// (mouseover) -> none
Array.forEach(document.getElementsByTagName("a"), function(v){
    v.addEventListener("mouseover", function(){alert(1);}, false);
});
var r = document.createRange();
r.selectNode(document.body);
document.documentElement.replaceChild(r.cloneContents(), document.body);

// (mouseover) -> none

Firefox2(Win)でのみの結果。

Range#extractContentsだとイベントリスナが消えるが、df.appendChildで一旦移して戻すやり方なら消えず、問題なく使えるということらしい。


これは知らなかった。

このやり方と比較してどうこう言っていたわけではないし、Jintrickさんの指摘がこれを指していたとは思わないけど、これを知らなかったために不正確な発言になっていたという点については訂正します。