@okayuの大して技術的ではないブログ

はじめに

JavaScriptでもformat()がしたい!

したくないですか?
したいですよね?
私はしたいです!
あなたもしたいはずです!

こんなの

'{}! {}!! {}!!!'.format('hoge', 'huga', 'piyo');  
'{}! {}!! {}!!!'.format(['foo', 'bar', 'baz']);  
'{normal}! {more}!! {most}!!!'.format({  
    more: 'Better',  
    most: 'Best',  
    normal: 'Good',  
});

'文字列'.format(引数s)みたいな形で。
引数を複数渡したりだとか、配列で渡したりだとか、オブジェクトを渡すことで名前付きのformat()したりだとか!

したい!

できたもの

Object.prototype.type = function () {  
    return Object.prototype.toString.call(this).slice(8, -1).toLowerCase();  
};  

String.prototype.format = function (...input) {  
    input =  
        input[0].type() === 'object' ? input[0] :  
            input[0].type() === 'array' ? input[0] :  
                input  
        ;  
    const isArray = Array.isArray(input);  
    return this.replace(/{(.*?)}/g, (match, p, offset, string) => {  
        return (  
            input[p]  
            ||  
            (isArray ? input.shift() : undefined)  
            ||  
            match  
        );  
    });  
};

(Object.prototype.typeは型チェック用の拡張です)。

formatString.prototypeを拡張して実装します。
thisを用いる都合上、関数をfunctionで宣言しています。
アロー関数を用いるとthisが束縛され呼び出し元にかかわらず不変のものとなってしまいうまくいきません(自分でも何を言っているのかよく分からない)。

引数はスプレッド演算子でinput配列一つにまとめてしまっています。
argumentsみたいなものだと思います(argumentsをよく知らない)。

そして、input配列の長さが1であった場合(つまり引数が一つしか渡されなかった場合)はそれを、input配列の長さが1でない(つまり引数が複数渡された場合)はinput配列をそのまま使います(見てもらったほうがわかりやすい)。

あらかじめ、Array.isArray()で配列かどうかを確認しておきます。

this.replace()で文字列の置換を行います。
replace()は第2引数に関数を渡すことができ、returnした文字列に置換されます。

/{(.*?)}/gという正規表現で{hoge}を取得、hogeを記憶します(それは関数のpに入っています)。

関数では、input[p]があればそれを、なければinputが配列だった場合は先頭の要素を返します。
どうしても返せるものがない場合はマッチした文字列を返します(つまり何もしません)。

使い方

console.log(  
    [  
        ['hoge', 'hogera', 'hogeraccho'],  
        ['huga', 'hugara', 'hugaraccho'],  
        ['piyo', 'piyora', 'piyoraccho'],  
    ]  
        .map(v => '{0}! {1}!! {2}!!! …{2}ってなんだよ'.format(v))  
        .join('\n')  
);

hogeraccho(ほげらっちょ):

今なぜか思いついたhogeraの発展形。
先人様がいた。しかも2004年。
世界は広いなぁ。

const loop = 10;  
const hoge =  
    [...Array(loop)]  
        .map((v, i) => {  
            return (  
                i === 0  
                    ? 'これはイタズラです(と宣言することで『予期しない動作』フラグを回収する)'  
                    : '{}回閉じても無駄ですよ〜'.format(i.toString())  
            );  
        })  
    ;  
hoge.push(`${loop}回閉じたら有効です`);  
hoge  
    .forEach(v => {  
        alert(v);  
    })  
    ;

最近流行りの無限アラート。Qiitaのトレンドがすごいことに。

これは無限ではありませし、予期しない動作でもありません。
なのでリンクしても警察には捕まりません!
リンクフリーです! じゃんじゃんリンク貼りましょう! ついでにいいねしろ

壊し方

// 動かない  
'{}'.format(0);  
// 動く  
'{}'.format([0].toString());  

'{}-{}'.format([['hoge','huga'], ['piyo', 'hogera']]);  
// => "hoge,huga-piyo,hogera"  

'{}'.format([{}]);  
// => "[object Object]"  

'{0}, {}'.format(['unu', 'du'])  
// => "unu, unu"  
'{}, {0}'.format(['unu', 'du'])  
// => "unu, du"

いじわるしないで。

おわりに

prototypeの拡張って楽しいですね。

この記事へのコメント

まだコメントはありません