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
変数割り当てはその値を返すので、割り当てと評価を同時に実行できます。
よい例が@jedのJSONPで、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にとっての祝福であり呪いでもありますが、時には非常に便利です。
@jedのpubsubでは、sub時に負数をデクリメントし、その結果を文字列と結合して"someString-123"のような文字列を生成します。
pub時はマイナス記号を分割文字のハイフンとして使用し、元々の文字を復元しています。
Choose small data format
必要なデータはしばしば配列やObjectで与えられますが、多くの場合これらの無駄はstringで置き換えることができます。
Date.parse polyfillは、普通はObjectで扱う変換テーブルをstringで扱う見事な例です。
Loops
Omit loop bodies
必要な処理を全てループの条件部分で実行できる場合、ループの本体は必要ありません。
@jedのtimeAgo関数を参照してください。
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 0
はundefined
より早いですが文字数が長くなります。
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
AeB
はA*Math.pow(10,B)
と同じです。
million=1000000 // before
million=1e6 // after
Use A<<B format for large binary numbers
A<<B
はA*Math.pow(2,B)
と同じです。
@jedのrgb2hexがよい例となります。
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になる。
-
と一緒に使えば、初期化されているいないに関わらずインクリメントすることができます。
これは@jedのJSONPの実装で使われています。
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)
は、大文字小文字を区別せずにアルファベットを数字にする最も短い書き方です。
これは@subzeyのparseRoman関数で使われています。
Use current date to generate random integers
@aemkeiのBinary 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バイト節約できます。
@jedのtimeAgo関数で使われています。
"alpha,bravo,charlie".split(",") // before
"alpha0bravo0charlie".split(0) // after
Use the little-known .link method
Stringは実はlinkというビルトインメソッドを持っています。
@jedのlinkify関数で使われています。
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
は第二引数に関数を渡せるので、多くの反復処理を記述することが可能です。
これは@jedのtemplatesやUUIDで使われています。
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)
を使っている例が@tkissingのtemplate engineで参照できます。
Type-specific methods to test for types
型を調べる他の方法は、型固有のメソッドが使用可能かチェックすることです。
@adiusのDOMinateなどで使われています。
型固有で最も短いメソッドは以下のとおりです。
Type | Test |
---|---|
String | x.big |
Number | x.toFixed |
Array | x.pop |
Function | x.call |
textNode | x.data |
文字列比較よりも高速です。
注意:もしこれらのメソッドやプロパティが追加されてしまった場合、誤った結果になるでしょう。
Arrays
Use elision
配列の要素は不要であれば省略可能です。
@jedのRouterで現実的な例を確認可能です。
[undefined,undefined,2] // before
[,,2] // after
// 最後の,は無視されることに注意
[2,undefined,undefined] // length is 3
[2,,] // length is 2
undefinedを文字列型に変換すると空文字になります。
@aemkeiのDigital 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
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
再帰はしばしばループより簡潔に書けます。
名前付き関数による再帰は@jedのwalk関数で使われています。
Use named functions for saving state
関数に状態を保存したいときは、関数名を付けてコンテナとして使用します。
これは@jedのJSONPで使われています。
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のパースは@jedのparseURLを、HTMLエスケープは@eligreyのescapeHTMLで使われています。
Use global scope
ブラウザではwindow
がグローバルなObjectなので、プロパティを直接参照可能です。
document
やlocation
はよく知られていますが、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
のところが色々おかしい気がするのだがよくわからん。