タグ「JavaScript」による検索結果 69

Search Form

回文作成補助フォーム

JavaScript: ビット反転とString#indexOf

if(~block.innerHTML.indexOf(this._key))
      return;
hatebu - ’ellaneous

これは使える。


// 含むとき
if (~str.indexOf("http://")) {}

// 含まないとき
if (!~str.indexOf("http://")) {}

+1だと後者に括弧が1組増えるとか、含む含まないの判定なのに+1?みたいな違和感があるけど、これだとけっこうすっきりする。

慣れがいるかもだけど。

JavaScript: Iterator関数をhasOwnProperty代わりに使う

いちいちhasOwnPropertyを使わなくてよくする(ジェネレータの使いかた) - 素人がプログラミングを勉強するブログ

多分Firefox2/3限定。JavaScript1.7相当。

Iterator関数を噛ませる方法もあります。

Object.prototype.extend = function(){};

var o = {a:1,b:2,c:3};
for (var i in o) console.log(i);
/* ->
a
b
c
extend
*/

for (var i in Iterator(o)) console.log(i);
/* ->
["a", 1]
["b", 2]
["c", 3]
*/

for each (var i in Iterator(o)) console.log(i);
/* ->
["a", 1]
["b", 2]
["c", 3]
*/

こんな具合になってるので

for (var [i,v] in Iterator(o)) console.log(i);
/* ->
a
b
c
*/

for (var [i,v] in Iterator(o)) console.log(v);
/* ->
1
2
3
*/

[p[0] for (p in Iterator(o))]
// -> ["a", "b", "c"] keys

[p[1] for (p in Iterator(o))]
// -> [1, 2, 3] values

とか。

微妙に形が変わるのが微妙といえば微妙。


yieldもletも使ってないので、Firefox2のFirebugコンソールでも使えるあたりが強み。

あとArrayでも順序が保証されている(はず)なのでbreakの効くforEachとしても使える。

関連リソース

Iterators and Generators - MDC

One advantage of using Iterator() to access the contents of an object is that custom properties that have been added to Object.prototype will not be included in the sequence.

Iterator() can be used with arrays as well:

JavaScript 1.8 では配列が自動的に Iterator に変換されなくなった? - やんばるもじら

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自身も値を返さない。

setTimeoutの代わりに(GM_)XMLHttpRequestを入れれば、onloadが呼ばれるまで次の関数の実行を遅延できる。

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そのものを引き渡しても良い。

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の場合は空になるようにしてある。

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さんの指摘がこれを指していたとは思わないけど、これを知らなかったために不正確な発言になっていたという点については訂正します。

JavaScript: コマンド文字列からC言語のargv相当を取得する

Core JavaScript 1.5 Reference:Global Objects:String:split - MDC

String.prototype.splitの罠 - 素人がプログラミングを勉強するブログ

String#splitの挙動と後方参照を利用すればできそう。

matchだとgフラグ立てたとき括弧の中身まで取れない。

正攻法だとRegExp#execでgフラグ立ててwhileなんだろうけど、while使いたくないし一時変数も用意したくないし1個1個pushとかもいや。

yフラグも同上。

var str = "grep \"ho\\\"ge\" 'fu\\'ga' piyo \"hello work!\"";
str
// -> "grep "ho\"ge" 'fu\'ga' piyo "hello work!""
str.split(/(["']?)((?:\\.|[^\\])*?)\1(?:\s+|$)/)
   .filter(function(v,i){ return (i%3 === 2); })
   .map(function(v){ return v.replace(/\\(["'\\])/g, "$1"); })
// -> ["grep", "ho"ge", "fu'ga", "piyo", "hello work!"]

追記

$ echo he\llo
hello
$ echo "he\llo"
he\llo
$ echo he\"llo
he"llo
$ echo 'he\"llo'
he\"llo

Cygwinでやってみたんだけど、何か上のじゃだめっぽい……

JavaScript: 継承(attach、from)

JavaScript: Sub.prototype = Super.prototype; が駄目な理由 - mayokara note

以前書いたattach関数が何度もSuperClassって書かなきゃいけなくて微妙だったので手直し。

var attach = function(b,p){
    var F = function(){};
    F.prototype = b;
    var o = new F();
    for (var i in p) {
        var g = p.__lookupGetter__(i), s = p.__lookupSetter__(i);
        if (g || s) {
            if (g) o.__defineGetter__(i, g);
            if (s) o.__defineSetter__(i, s);
        } else {
            o[i] = p[i];
        }
    }
    return o;
};

var from = function(b,c,p){
    c.prototype = attach(b.prototype, p);
    c.prototype.__super__ = b;
    return c;
};
var Animal = function(){
    console.log("Animal: called");
};
Animal.prototype = {
    bark: function(){
        console.log("Animal#bark: ...");
    },
};

var Dog = from(Animal, function(){
    this.__super__.apply(this, arguments);
    console.log("Dog: called");
}, {
    bark: function(){
        console.log("Dog#bark: bowwow");
    },
});
var animal = new Animal();
// -> Animal: called
animal.bark();
// -> Animal#bark: ...

var dog = new Dog();
// -> Animal: called
// -> Dog: called
dog.bark();
// -> Dog#bark: bowwow
dog instanceof Dog // -> true
dog instanceof Animal // -> true
animal instanceof Dog // -> false
dog.__proto__ === Dog.prototype // -> true
dog.__proto__.__proto__ === Animal.prototype // -> true
dog.__proto__.__proto__.__proto__ === Object.prototype // -> true
dog.__proto__.__proto__.__proto__.__proto__ === null // -> true

単に引数増やしてthis.__super__を用意しただけ。

まあ、this.__super__潰さなくてもAnimalって直接呼べばいいんだけど、毎回書き方変わるの気持ち悪いな、ってことで。

this.__super__.prototype.barkで親クラスのbarkが呼べたりとか、そんな感じ。

追記

致命的な欠陥があったので修正。

JavaScript: Firefox2でも配列内包・分割代入が使えることを知った

The features that do not introduce new keywords (destructuring assignment and array comprehensions) can be used without specifying the JavaScript version.

New in JavaScript 1.7 - MDC

今さらな話だけど、yieldキーワードとletキーワードを使わなければ、Firefox2でも何の断りもなしに1.7の配列内包・分割代入が使えることを知った。

てっきりFirefox3からだと思ってたので使ってなかったのだけど、そもそも気にする必要がなかったんですね……

というわけで、使えそうなコードのメモ。

分割代入・値の交換

var [a,b]=[4,9];
[b,a]=[a,b];
console.log([a,b]);
// -> [9, 4]

プロパティの部分取得

ObjectからObject?への分割代入 - 0x廃棄階層 - 統治局

window.addEventListener("click", function(e){
    var {pageX: x, pageY: y} = e;
    console.log([x, y]);
    this.removeEventListener("click", arguments.callee, false);
}, false);

(click) // -> [289, 115]

parseurl

DOM:window.location - MDC # Location object - Properties

const SAMPLE_URL = "http://www.google.com:80/search?q=devmo#test";
const RE_URL = /^([a-z]+:)\/\/(([\u0021-\u007e]+?)(?:\:(\d+))?)(\/[\u0021-\u007e]*?)(\?[\u0021-\u007e]+?)?(#[\u0021-\u007e]+)?$/;
var [href, protocol, host, hostname, port, pathname, search, hash] = SAMPLE_URL.match(RE_URL);
[href, protocol, host, hostname, port, pathname, search, hash]
// -> ["http://www.google.com:80/search?q=devmo#test", "http:", "www.google.com:80", "www.google.com", "80", "/search", "?q=devmo", "#test"]

正規表現はRFCに従った厳密なものではありません。手抜きです。

せめて非ASCII文字にはマッチしないようにちょっと修正。

配列内包

var range = function(start, end){
    var o = {};
    for (var i=start; i<end; i++) o[i] = i;
    return o;
};
[i*i for each (i in range(0,10))]
// -> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

range関数はジェネレータ(yieldキーワード)が使えないので、普通のオブジェクトで代用していますが、一般にfor/for each ~ in文において参照される順序は不定なのでちょっと微妙。

この場合は数値順にプロパティを与えているのでうまくいくようですが……

ハッシュから配列への変換

逆に言えば順序が不定でも問題なければ良いわけで、ハッシュから最も値の大きいキーを調べるコードは次のように書ける。

var o = {
    a: 5,
    b: 7,
    c: 0,
    d: 2,
    e: 9,
    f: 3,
};
[[i, o[i]] for (i in o)].sort(function(a,b){return b[1]-a[1];})[0][0]
// -> "e"

これがFirefox2やGreasemonkey上でも問題なく動くというところが新発見。