LoginSignup
82
94

More than 5 years have passed since last update.

JavaScriptのナイスな文法テクニック集

Last updated at Posted at 2017-07-20

JavaScriptのとってもナイスな文法テクニック集を発見したので紹介してみます。
以下はByte saving techniquesの日本語訳です。

なお、イタリックは訳者(私だ)による追加です。

Byte-saving Techniques

これはJavaScriptのコードから文字数を減らすための魔法のコレクションです。
主にTwitterチャレンジを作成するための参考資料として書かれています。
新たなテクニックを発見したときは@140bytesにフィードバックください。

Arguments

Use one-letter positional arguments, in alphabetical order

引数はできるだけ短くする必要があり、かつ再利用される可能性も高いので、意味で表すのではなく順序で表すのが最善です。
頭文字を使用すると、ひとつの関数の読みやすさが僅かに向上しますが、一貫して順序で表すことで全ての関数の読みやすさが向上します。

    function(t,d,v,i,f){...} // before
    function(a,b,c,d,e){...} // after

Test argument presence instead of length

指定個数の引数が渡されたかをチェックするにはinを使います。

    arguments.length>1||(cb=alert) // before
    1 in arguments||(cb=alert)     // after

引数がtrueっぽい値であると決まっているなら、もう少し縮められます。

    arguments[0]&&(cb=alert)       // after arguments[0]がtrueっぽい値ならOK

Embed functionality within arguments

引数内で直接計算することで、デリミタを削減することができます。

    a+=b<<1;x(a,1); // before
    x(a+=b<<1,1);   // after

Reuse parenthesis of the function call

引数を取らない関数もありますが、その関数呼び出しの括弧を他の用途に流用できることは確定的に明らかです。
使用例は@snowlord逆ポーランド記法を参照してください。

    ((a=b.pop(),b.pop())+c+a); // before
    (b.pop(a=b.pop())+c+a);    // after

関数が実際に引数を取るかわからない場合は、その.lengthが0であるかを確認しましょう。

setInterval and setTimeout hacks

setIntervalとsetTimeoutには、関数ではなく文字列を渡しましょう。

    setInterval(function(){console.log("z")},100) // before
    setInterval('console.log("z")',100)           // after

第2引数を省略した場合、デフォルト値はブラウザの設定可能な最速間隔(最近のブラウザでは1ms)になります。

    setInterval('console.log("z")',1) // before
    setInterval('console.log("z")')   // after

Variables

Use placeholder arguments instead of var

varを宣言するかわりに、引数で変数を定義しましょう。

    function(a){var b=1;...} // before
    function(a,b){b=1;...}   // after

場合によってはvarのほうが短くなることもあるので注意しましょう。各場合で正しい判断を下す必要があります。

    function(a,b,c,d){b=1;c=2;d=3;...} // before
    function(a){var b=1,c=2,d=3;...}   // after

Re-use variables where possible

一見不要になった変数を再利用することで、文字数を節約できます。

    setTimeout(function(){for(var i=10;i--;)... }, a) // before
    setTimeout(function(){for(a=10;a--;)... }, a)     // after

Assign wherever possible

変数割り当てはその値を返すので、割り当てと評価を同時に実行できます。
よい例が@jedJSONPで、createElementに文字列"script"が渡されています。

    a=this.localStorage;if(a){...} // before
    if(a=this.localStorage){...}   // after

Use an array to swap variables

無駄な変数宣言を避けるために、一時的な配列を使うことができます。

    var a=1,b=2,c;c=a;a=b;b=c // before
    var a=1,b=2;a=[b,b=a][0]  // after
    var a=1,b=2;a=b^a^(b=a)   // after 役に立たないが役に立つ

数値の場合はさらに2バイト減らせます。

    var a=1,b=2;a=[b,b=a][0]  // before
    var a=1,b=2;a+=b-(b=a)    // after

Exploit coercion

暗黙の型変換はJavaScriptにとっての祝福であり呪いでもありますが、時には非常に便利です。
@jedpubsubでは、sub時に負数をデクリメントし、その結果を文字列と結合して"someString-123"のような文字列を生成します。
pub時はマイナス記号を分割文字のハイフンとして使用し、元々の文字を復元しています。

Choose small data format

必要なデータはしばしば配列やObjectで与えられますが、多くの場合これらの無駄はstringで置き換えることができます。
Date.parse polyfillは、普通はObjectで扱う変換テーブルをstringで扱う見事な例です。

Loops

Omit loop bodies

必要な処理を全てループの条件部分で実行できる場合、ループの本体は必要ありません。
@jedtimeAgo関数を参照してください。

Use for over while

forとwhileは同じバイト数ですが、forのほうがより多くの機能を使えます。

    while(i--){...} // before
    for(;i--;){...} // after

    i=10;while(i--){...} // before
    for(i=10;i--;){...}  // after

forループでは2番目の引数も省略できます。falseっぽい値が入ってきたらループが修了します。

Use index presence to iterate arrays of truthy items

配列の値が全てtrueっぽいとわかっているのであれば、直接値を参照することで文字数を節約できます。

    for(a=[1,2,3,4,5],l=a.length,i=0;i<l;i++){b=a[i];...}
    for(a=[1,2,3,4,5],i=0;b=a[i++];){...}

Use for..in with assignment to get the keys of an object

for..inでキーを取得する。

    a=[];i=0;for(b in window)a[i++]=b // before
    a=[];i=0;for(a[i++]in window)     // after
    a=Object.keys(window)             // ES6 prototypeの値は返さない

さらに配列から数値に強制型変換することもできます。i=a=[];for(a[i++]in window);

Use reverse loops where possible

列挙の順番が逆でもいいのであれば、文字数を多少減らすことができます。

    for(a=0;a<x.length;a++)...     // before
    for(a=x.length;a--;)...        // after

Use both for body and counting expression for multiple operations

演算が複数ある場合、forの本体とカウンタ部分に分けて書く。

    for(i=3;i--;foo(),bar());   // before
    for(i=3;i--;)foo(),bar();   // before
    for(i=3;i--;bar())foo();    // after

for..in will not iterate over false - use this to trigger iteration

for..inはObjectとstring以外の値、0やfalseっぽい値が渡されると、エラーを出さずループもせず次に進みます。

    if(c)for(a in b)x(b[a]); // before
    for(a in c&&b)x(b[a]);   // after

Operators

Understand operator precedence

演算子の優先順位は熟読しておきましょう。

Understand bitwise operator hacks

Use ~ with indexOf to test presence

存在チェックは~とindexOfをセットで使う。

    hasAnF="This sentence has an f.".indexOf("f")>=0 // before
    hasAnF=~"This sentence has an f.".indexOf("f")   // after

Use , to chain expressions on one conditional line

,を使えば1文で色々書ける。

    with(document){open();write("hello");close()} // before
    with(document)open(),write("hello"),close()   // after

Use []._instead of undefined

""._1.._0[0]も動作しますが、動作は遅くなります。
void 0undefinedより早いですが文字数が長くなります。

Remove unnecessary space after an operator

演算子の後には空白が必ずしも必要というわけではなく、しばしば削除可能です。

    typeof [] // before
    typeof[]  // after

Numbers

Use ~~ and 0| instead of Math.floor for positive numbers

~~0|はともに切り捨ての計算となります。
ただし~|より優先順位が低いので、同じ結果になるとは限りません。

    rand10=Math.floor(Math.random()*10) // before
    rand10=0|Math.random()*10           // after

2の累乗で割った商を求めたい場合は、ビットシフトだけで表現できます。

    Math.floor(a/2) // before
    a>>1            // after

    Math.floor(a/4) // before
    a>>2            // after

Use A + 0.5 | 0 instead of Math.round for positive numbers

四捨五入するより+0.5して切り捨てた方が短い。

    Math.round(a) // before
    a+.5|0        // after

負数の四捨五入には+0.5ではなく-0.5とします。

    Math.round(-a) // before
    -a-.5|0        // after

Use AeB format for large denary numbers

AeBA*Math.pow(10,B)と同じです。

    million=1000000 // before
    million=1e6     // after

Use A<<B format for large binary numbers

A<<BA*Math.pow(2,B)と同じです。
@jedrgb2hexがよい例となります。

    color=0x100000 // before
    color=1<<20    // after

Use 1/0 instead of Infinity

Infinityと書くより短くなります。

    [Infinity,-Infinity] // before
    [1/0,-1/0]           // after

Use division instead of isFinite()

有限の数であれば、1を割ったらtrueっぽい数値が返ってきます。

    if(isFinite(a)) // before
    if(1/a)         // after

Exploit the "falsiness" of 0

数値を比較する場合、0にしてから比較した方が短くなることがよくあります。

    a==1||console.log("not one") // before
    ~-a&&console.log("not one")  // after

Use ~ to coerce any non-number to -1,

非数値に~を使うと-1になる。
-と一緒に使えば、初期化されているいないに関わらずインクリメントすることができます。
これは@jedJSONPの実装で使われています。

    i=i||0;i++ // before
    i=-~i      // after

-~を逆にすることで、デクリメントすることもできます。

    i=i||0;i-- // before
    i=~-i      // after

Use ^ to check if numbers are not equal

数値の場合、!=^は結果が同じになる。

    if(a!=123) // before
    if(a^123)  // after

Use number base for character to number conversion

parseInt(n, 36)は、大文字小文字を区別せずにアルファベットを数字にする最も短い書き方です。
これは@subzeyparseRoman関数で使われています。

Use current date to generate random integers

@aemkeiBinary Tetrisで使われています。

    i=0|Math.random()*100 // before
    i=new Date%100        // after

注意:ミリ秒が変わらない可能性があるため、高速なループ中では使用禁止!

Strings

Prefer slice over substr over substring

substring(start,stop)よりもsubstr(start,length)よりもslice(start,stop)を使いましょう。
文字を最後まで取得するときは第二引数を省略します。
負数は使わないようにしましょう。
s.substr(-n)は後ろn文字を取得しますが、IE9では動作しません。
IE11では動いたので、もう使ってもいいだろう

Split using ''

文字列から配列にするにはs.split('')を使います。
残念ながらs[i]はIE9では動作しません。
これもIE11では動いたので、使ってもいいだろう

Split using 0

区切り文字には数値を使ったほうが2バイト節約できます。
@jedtimeAgo関数で使われています。

    "alpha,bravo,charlie".split(",") // before
    "alpha0bravo0charlie".split(0)   // after

Use the little-known .link method

Stringは実はlinkというビルトインメソッドを持っています。
@jedlinkify関数で使われています。

    html="<a href='"+url+"'>"+text+"</a>" // before
    html=text.link(url)                   // after

Stringには、他にもHTMLラッパーメソッドが幾つか存在します

Use .search instead of .indexOf

1バイト短くなります。
正規表現は''で囲むかわりに//で囲みます。

注意:正規表現が正しくないと失敗します。'.'をそのまま/./にしてしまうと全ての文字にマッチし、'+'を/+/にすると構文エラーになります。

Use .replace or .exec for powerful string iteration

.replaceは第二引数に関数を渡せるので、多くの反復処理を記述することが可能です。
これは@jedtemplatesUUIDで使われています。

Use Array to repeat a string

反復文字列の作成にArrayが使える。

    for(a="",i=32;i--;)a+=0 // before
    a=Array(33).join(0)     // after
    a="0".repeat(32)        // ES6

Conditionals

Use && and || where possible

    if(a)if(b)return c // before
    return a&&b&&c     // after

    if(!a)a=Infinity // before
    a=a||Infinity    // after

Coercion to test for types

文字列型であるかを調べるには、typeof x=='string'ではなく''+x===xを使いましょう。
同様に数値型はtypeof x=='number'ではなく+x===xとします。
+xはxを数値型かNaNにするので、数値型以外の値はfalseになります。

注意:どっかのバカチンがprototypeをいじくっていた場合、これは動かないでしょう。

typeof x=='function'のかわりに/^f/.test(typeof x)を使っている例が@tkissingtemplate engineで参照できます。

Type-specific methods to test for types

型を調べる他の方法は、型固有のメソッドが使用可能かチェックすることです。
@adiusDOMinateなどで使われています。
型固有で最も短いメソッドは以下のとおりです。

Type Test
String x.big
Number x.toFixed
Array x.pop
Function x.call
textNode x.data

文字列比較よりも高速です。

注意:もしこれらのメソッドやプロパティが追加されてしまった場合、誤った結果になるでしょう。

Arrays

Use elision

配列の要素は不要であれば省略可能です。
@jedRouterで現実的な例を確認可能です。

    [undefined,undefined,2] // before
    [,,2]                   // after

    // 最後の,は無視されることに注意
    [2,undefined,undefined] // length is 3
    [2,,]                   // length is 2

undefinedを文字列型に変換すると空文字になります。
@aemkeiDigital Segment Displayで例を確認できます。

    b="";b+=x // before
    b=[b]+x   // after
    // 実際これやるとb="undefined"になるんだがなんだろう

以下の例も便利です。

    ((b=[1,2][a])?b:'') // before
    [[1,2][a]]          // after

Use coercion to do .join(',')

配列のセパレータは,であるため、array.join(',')''+arrayと書けます。

注意:配列内にシングルクォートやObjectや配列があるときはうまく動きません。

    ''+[1,true,false,{x:1},0,'',2,['test',2]]
    // "1,true,false,[object Object],0,,2,test,2"

String coercion with array literal []

数値型を文字列型にするときは配列のほうが短い。

    ''+1e3+3e7 // before
    [1e3]+3e7  // after

@jedUUIDでこれを使っています。

Use coercion to build strings with commas in them

カンマ区切りの文字列は配列で作ると短い。

    "rgb("+(x+8)+","+(y-20)+","+z+")"; // before
    "rgb("+[x+8,y-20,z]+")";           // after

最初か最後の値が静的のときも配列にした方が短くなります。

    "rgb(255,"+(y-20)+",0)"; // before
    "rgb(255,"+[y-20,"0)"];  // after

Use Arrays as Objects

Objectを作る必要があるときは、既に宣言されている配列にプロパティを突っ込みましょう。
実のところ配列はObjectの一種に過ぎません。
プロパティ名が配列の組み込みプロパティと競合しないことだけ確認しておきましょう。

Test if Array has Several Elements

if(array.length>1)if(array[1])と書けます。

array[1]がfalseっぽい値だったときは動かないので、trueっぽい値であることがわかっている場合にのみ使用してください。
falseっぽい値だったときは、かわりにif(1 in array)と書くことができます。

Regular Expressions

Use shortcuts

/[0-9]/は/\d/、/[A-Za-z0-9_]/は/\w/、空白は/\s/と表すことができます。
大文字はその反転で、\Dは数値以外にマッチします。
これらの正規表現は文字クラス内で使用可能です。
[\dA-F]は16進数文字列にマッチします。

/\b/は文字そのものではなく、単語の区切りにマッチします。
/\B/はその逆で、単語の区切り以外にマッチします。
/\b/は/\w/以外の文字ではうまく動かない。

その他の正規表現、\Q\Eなどはしばしば動作しません。
正規表現の完全なリストはRegular Expression Flavor Comparisonを参照してください。

/(a|b)/は/a|b/と同じです。
HTMLタグとマッチさせるには、/<[^>]>/ではなく/<.?>/を使います。
ただし、希に動作が異なる場合があります。

replaceする場合、マッチした文字列は$&、前後の文字列は$\$'に入ってきます。
従って、/(x)/と
$1を/x/と$&`に変更可能です。

    "abcdefg".replace(/^(cde)$/,"\1") // before
    "abcdefg".replace(/cde/,"$&")     // after

Denormalize to shorten

/\d{2}/はスマートに見えますが、/\d\d/のほうが短いです。

Don't escape

正規表現では、メタキャラクタを全てエスケープしなくても、正しく動作することがままあります。
たとえば/[[\]-]/は、]はエスケープが必要ですが、[はエスケープが不要、そして-は文字クラス内の最後に書けばエスケープ不要です。

eval() for a regexp literal can be shorter than RegExp()

可能であればnew RegExp('\\d','g')ではなく/\d/gを使いましょう。
実行時に正規表現を組み立てる必要があるならば、eval()の使用を検討してください。

    r=new RegExp("\\\\{"+p+"}","g") // before
    r=eval("/\\\\{"+p+"}/g")        // after

eval() around String.replace() instead of callback

出力にコールバック処理を行いたい場合、replace全体をevalで囲みましょう。
これは、マッチが複雑になればなるほど効果を増します。

    x.replace(/./,function(c){m=m+c.charCodeAt(0)&255})  // before
    eval(x.replace(/./,'m=m+"$&".charCodeAt(0)&255;'))   // after

Booleans

Use ! to create booleans

真偽値は数値から作成します。

    [true,false] // before
    [!0,!1]      // after

強制型変換も便利です。
数値型にするとtrueは1に、falseは0になります。
入力の条件にに対して2、1、0の3値を出力するプログラムは以下のように書けます。

    x>7?2:x>4?1:0 // before
    +(x>7)+(x>4)  // after

minifierは真偽値を置換することで文字数を削減します。

    true === !0  // save 2 chars
    false === !1 // save 3 chars

Functions

Shorten function names

関数には短い別名を割り当てます。
複雑な場合はこれで高速化することもあります。

    a=Math.random(),b=Math.random() // before
    r=Math.random;a=r(),b=r()       // after

Use named functions for recursion

再帰はしばしばループより簡潔に書けます。
名前付き関数による再帰は@jedwalk関数で使われています。

Use named functions for saving state

関数に状態を保存したいときは、関数名を付けてコンテナとして使用します。
これは@jedJSONPで使われています。

    function(i){return function(){console.log("called "+(++i)+" times")}}(0) // before
    (function a(){console.log("called "+(a.i=-~a.i)+" times")})              // after
    0,function a(){console.log("called "+(a.i=-~a.i)+" times")}              // another alternative

Omit () on new calls w/o arguments

new Object()new Objectは同じです。

    now = +new Date() // before
    now = +new Date   // after

Omit the new keyword when possible

一部のオブジェクトではnewも必要ありません。

    r=new RegExp(".",g) // before
    r=RegExp(".",g)     // after

    l=new Function("x","console.log(x)") // before
    l=Function("x","console.log(x)")     // after

The return statement

英数字以外で始まる値を返す際は、returnの後ろにスペースは必要ありません。

    return ['foo',42,'bar']; // before
    return['foo',42,'bar'];  // after
    return {x:42,y:417}; // before
    return{x:42,y:417};  // after
    return .01; // before
    return.01;  // after

Use the right closure for the job

即時実行が必要な場合は、適切なクロージャを選んで使用します。

    ;(function(){...})() // before
    new function(){...}  // after 何かをreturnしたいなら
    !function(){...}()   // after returnが不要

Shorten function with Function

これは関数を複数定義しないといけない場合に便利です。
ただし引数を文字列で渡さなければならないため、必要か否かは使用時に判断してください。

    function a(a){return a}
    function b(b){return b}
    function c(c){return c} //before

    var f=Function,
    a=f('a','return a'),
    b=f('b','return b'),
    c=f('c','return c')     //after

In the browser

Use browser objects to avoid logic

独自ロジックを書かずとも、ブラウザに専用メソッドが存在していることがあります。
URLのパースは@jedparseURLを、HTMLエスケープは@eligreyescapeHTMLで使われています。

Use global scope

ブラウザではwindowがグローバルなObjectなので、プロパティを直接参照可能です。
documentlocationはよく知られていますが、innerWidthのような他のプロパティも同様です。

A better way to getElementById()

27バイトも無駄があります。

    document.getElementById('a').innerHTML = "foo"; // before
    a.innerHTML = "foo";                            // after

Delimiters

他の文法で囲むことで;を省略できることがあります。

    x=this;a=[].slice.call(arguments,1); // before
    a=[x=this].slice.call(arguments,1);  // after

感想

いやあ、かなり使えますよ、このリファレンスは。
みんなもどんどん使っていきましょう。

division by zero gets you free internet pointsの意味がわからなかった。
Use ~~ and 0| instead of Math.floor for positive numbers~が使われてない。
Use elisionのところが色々おかしい気がするのだがよくわからん。

82
94
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
94