Javascript艺术&技术:节省字节的技术
来源:互联网 发布:阿里云黑洞触发值 编辑:程序博客网 时间:2024/05/17 02:47
这是一篇转载文章,主要讲怎么写出最简短的Javascript代码,看后让人觉得学到许多新的思维方式。
实际项目开发中,我们基本不会以这样的极端方式来书写代码的,往往是最后由压缩工具来完成。
但是我们仍然有必要去了解一下这些缩短代码的技巧,如果你要自己写一个js压缩工具,那就更有必要了。
可能每一条你都懂,但你能整理得这么完整吗?
文章很长,虽是英文,但主要是代码,JSer都能看懂的。
原文地址:https://github.com/jed/140bytes/wiki/Byte-saving-techniques
Byte saving techniques
This is a collection of JavaScript wizardry that can shave bytes off of your code. It's mainly intended as a reference for those creating entries for140byt.es. Feel free to add your own or send any feedback to @140bytes.
Disclaimer
Outside of the 140bytes challenge or other code golf challenges, please be considerate and don’t pre-minify code you wish to share with others. We have minifiers for that.
Arguments
Use one-letter positional arguments, in alphabetical order
Since arguments will need to be as short as possible, and will likely be reused within their lifetime, it's best to treat them as positionals instead of trying to give them meaning through their name. While using one-letter names marginally aids readability for a single function, keeping a consistent approach helps readability across all functions.
function(t,d,v,i,f){...} // beforefunction(a,b,c,d,e){...} // after
Test argument presence instead of length
Use in
to check whether a given argument was passed
arguments.length>1||(cb=alert) // before1 in arguments||(cb=alert) // after
If only truthy arguments are of interest, you can even boil that down to
arguments[0]&&(cb=alert) // works only if arguments[0] coerces to true
Embed functionality within arguments
Save delimiters by processing stuff within (unused) arguments
a=b<<1+a;x(a,1); // beforex(a=b<<1+a,1); // after
Reuse parenthesis of the function call
There are some functions which take no argument, and obviously you can reuse the parentheses when calling them. See @snowlord's RPNfunction.
((a=b.pop(),b.pop())+c+a) // before((b.pop(a=b.pop())+c+a)) // after
If you're not sure if a function really takes no arguments, see if its .length
is 0.
Variables
Use placeholder arguments instead of var
Save bytes on the var
declaration by putting placeholder arguments in the function declaration.
function(a){var b=1;...} // beforefunction(a,b){b=1;...} // after
Please be careful as sometimes var
declaration is shorter. Take the right decision in each case.
function(a,b,c,d){b=1;c=2;d=3;...} // beforefunction(a){var b=1,c=2,d=3;...} // after
Re-use variables where possible
Careful reuse of a variable that is no longer needed can save bytes.
setTimeout(function(){for(var i=10;i--;)... }, a) // beforesetTimeout(function(){for(a=10;a--;)... }, a) // after
Assign wherever possible
Since assignment returns the assigned value, perform assignment and evaluation at the same time to save bytes. A good example of this is @jed's JSONP function, where the string script
is assigned in the createElement
method.
a=this.localStorage;if(a){...} // beforeif(a=this.localStorage){...} // after
Use an array to swap variables
An array can be used as a temporary placeholder to avoid declaring another variable.
var a=1,b=2,c;c=a;a=b;b=c // beforevar a=1,b=2;a=[b,b=a][0] // after
Exploit coercion
JavaScript coercion is a blessing and a curse, but sometimes it can be very useful. @jed's pubsub function decrements a negative variable, and then concatenates the results with a string, resulting in a string like someString-123
, which is exploited later by using the hyphen as a split token to return the original string.
Choose small data format
Required data will often be represented as Array or Object. In many cases, these byte-hungry formats can be replaced by strings. The Date.parse polyfill shows a great example of a conversion table that'd usually be an Object.
Loops
Omit loop bodies
If you can perform all the logic you need within the conditional part of a loop, you don't need the loop body. For an example, see @jed's timeAgofunction.
Use for
over while
for
and while
require the same number of bytes, but for
gives you more control and assignment opportunity.
while(i--){...} // beforefor(;i--;){...} // afteri=10;while(i--){...} // beforefor(i=10;i--;){...} // after
FYI, the second argument to a for-loop can be omitted, too - it will only stop the loop if it returns anything false-y at all.
Use index presence to iterate arrays of truthy items
When iterating over an array of objects that you know are truthy, short circuit on object presence to save bytes.
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
a=[];i=0;for(b in window)a[i++]=b // beforea=[];i=0;for(a[i++]in window) // after
Coercion Hint: you can coerce the counter from an array: i=a=[];for(a[i++]in window);
Use reverse loops where possible
If an array can be iterated reversely, it may save some bytes:
for(a=0;a<x.length;a++)... // beforefor(a=x.length;a--;)... // after
Use both for
body and counting expression for multiple operations
for(i=3;i--;foo(),bar()); // beforefor(i=3;i--;)foo(),bar(); // beforefor(i=3;i--;bar())foo(); // after
for..in will not iterate over false - use this to trigger iteration
If for..in encounters anything but an object (or string in any browser but ye olde IE), e.g. false or 0, it will silently continue without iteration.
if(c)for(a in b)x(b[a]); // beforefor(a in c&&b)x(b[a]); // after
Operators
Understand operator precedence
This Mozilla page is an excellent resource to get started.
Understand bitwise operator hacks
Use ~
with indexOf to test presence
hasAnF="This sentence has an f.".indexOf("f")>=0 // beforehasAnF=~"This sentence has an f.".indexOf("f") // after
Use ,
to chain expressions on one conditional line
with(document){open();write("hello");close()}with(document)open(),write("hello"),close()
Use []._
instead of undefined
""._
, 1.._
and 0[0]
also work, but are slower. void 0
is faster than undefined
but longer than the alternatives.
Remove unnecessary space after an operator
Whitespace isn't always needed after an operator and may sometimes be omitted:
typeof [] // beforetypeof[] // after
Numbers
Use ~~
and 0|
instead of Math.floor
for positive numbers
Both of these operator combos will floor numbers (note that since ~
has lower precedence than |
, they are not identical).
rand10=Math.floor(Math.random()*10) // beforerand10=0|Math.random()*10 // after
If you are flooring a quotient where the divisor is a multiple of 2, a bit-shift-right will perform both operations in one statement:
Math.floor(a/2) // beforea>>1 // afterMath.floor(a / 4) // beforea>>2 // after
Use A + 0.5 | 0
instead of Math.round
for positive numbers
Math.round(a) // beforea+.5|0 // after
Use AeB
format for large denary numbers
This is equivalent to A*Math.pow(10,B)
.
million=1000000 // beforemillion=1e6 // after
Use A<<B
format for large binary numbers
This is equivalent to A*Math.pow(2,B)
. See @jed's rgb2hex for an example.
color=0x100000 // beforecolor=1<<20 // after
Use 1/0
instead of Infinity
It’s shorter. Besides, division by zero gets you free internet points.
[Infinity,-Infinity] // before[1/0,-1/0] // after
Use division instead of isFinite()
Division of 1 by any finite number results nonzero "truthy" value.
if(isFinite(a)) // beforeif(1/a) // after
Exploit the "falsiness" of 0
When comparing numbers, it's often shorter to munge the value to 0 first.
a==1||console.log("not one") // before~-a&&console.log("not one") // after
Use ~
to coerce any non-number to -1,
Used together with the unary -
, this is a great way to increment numerical variables not yet initialized. This is used on @jed's JSONPimplementation.
i=i||0;i++ // beforei=-~i // after
It can also be used to decrement a variable by flipping around the negation and complement:
i=i||0;i-- // beforei=~-i // after
Use ^
to check if numbers are not equal
if(a!=123) // beforeif(a^123) // after
Use number base for character to number conversion
parseInt(n, 36)
is not only a very small character to number conversion, it also has the added value of being case-insensitive, which may save a .toLowerCase()
, like in subzey's parseRoman function.
Use current date to generate random integers
As seen in aemkei's Tetris game.
i=0|Math.random()*100 // beforei=new Date%100 // after
Note: Do not use in fast loops, because the milliseconds might not change!
Strings
Prefer slice
over substr
over substring
Prefer slice(start,stop)
over substr(start,length)
over substring(start,stop)
. Omit the second parameter to fetch everything to the end of the string. Do not use negative positions. It may be shorter (e.g. s.substr(-n)
fetches the last n characters) but does not work in Internet Explorer (including version 9).
Split using ''
Use s.split('')
to create a character array from a string. Unfortunately you can not use s[i]
to access the characters in the string. This does not work in Internet Explorer (including version 9).
Split using 0
Save two bytes by using a number as a delimiter in a string to be split, as seen in @jed's timeAgo function.
"alpha,bravo,charlie".split(",") // before"alpha0bravo0charlie".split(0) // after
Use the little-known .link
method
Strings have a built-in .link
method that creates an HTML link. This is used in @jed's linkify function.
html="<a href='"+url+"'>"+text+"</a>" // beforehtml=text.link(url) // after
Strings also have several other methods related to HTML, as documented here.
Use .search
instead of .indexOf
First, because this RegExp implicit is 1 byte shorter, but you get the added value of coercion of undefined to /undefined/ instead of '' being matched at position zero. This is used in @atk's base64decoder function.
Warning: This will fail when you search with an invalid regular expression. For example, '.'
as /./
matches any character, '+'
as /+/ gives an error so you'd want to ensure you know what the value is.
Use replace
or .exec
for powerful string iteration
Since the .replace
method can take a function as its second argument, it can handle a lot of iteration bookkeeping for you. You can see this exploited in @jed's templates and UUID functions.
Use Array
to repeat a string
for(a="",i=32;i--;)a+=0 // beforea=Array(33).join(0) // after
Conditionals
Use &&
and ||
where possible
if(a)if(b)return c // beforereturn a&&b&&c // afterif(!a)a=Infinity // beforea=a||Infinity // after
Coercion to test for types
Instead of using typeof x=='string'
, you can use ''+x===x
.
Instead of using typeof x=='number'
, you can use +x===x
. +x
will coerce x to a number or NaN, so if it is anything else but a number, this will turn false. Warning: If someone goes really crazy on the prototypes, this will probably fail.
Instead of using typeof x=='function'
, you can use /^f/.test(typeof x)
as in @tkissing's template engine.
Type-specific methods to test for types
Another way to test types is to check if type-specific methods are available. (Seen on @adius DOMinate)
The shortest method of strings is the big()
method. So it's as simple as if(x.big)
.
The same technique can be used for arrays with its pop()
method. if(x.pop)
(You could also use map()
, but it works on far fewer browsers)
For functions it's if(x.call)
.
Furthermore this is even faster than string comparison.
Warning: This will lead to wrong results if properties or methods with those names were added.
Arrays
Use elision
Array elision can be used in some cases to save bytes. See @jed's router API for a real-world example.
[undefined,undefined,2] // before[,,2] // after// Note: Be mindful of elided elements at the end of the element list[2,undefined,undefined] // before length is 3[2,,] // after length is 2
You may notice that the undefined
turns empty. In fact, when we coerce an array into a string, the undefined
turns to empty string. See one exploitation from @aemkei's Digital Segment Display
b="";b+=x // beforeb=[b]+x // after// Bonus: b=x+[b] uses same bytes as b=[b]+x, while b="";b=x+b uses one more byte over b="";b+=x.
Another exploitation is also useful:
((b=[1,2][a])?b:'') // before[[1,2][a]] // after
Use coercion to do .join(',')
You can use ''+array
instead of array.join(',')
since the default separator of arrays is ",".
Warning: this will only work if the contents of the Array are true-ish (except false) and consist of Strings (will not be quoted!), Numbers or Booleans, Objects and Arrays within arrays may lead to unwanted results:
''+[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
See @jed's UUID function.Use coercion to build strings with commas in them
"rgb("+(x+8)+","+(y-20)+","+z+")"; // before"rgb("+[x+8,y-20,z]+")"; // after
Or if the first or last values are static:"rgb(255,"+(y-20)+",0)"; // before"rgb(255,"+[y-20,"0)"]; // after
Use Arrays as Objects
When you need to return an Object, re-use an already declared Array to store properties. An Array is of type 'object', after all. Make sure the field names don't collide with any of Array's intrinsic properties.
Regular Expressions
Use shortcuts
\d
is short for [0-9]
and \w
is short for [A-Z_a-z0-9_]
. \s
matches whitespace. Upper case shortcuts are inverted, e.g. \D
matches non-digits. You can use these shortcuts inside character classes, e.g. [\dA-F]
matches hex characters.
\b
does not match a character but a word boundary where a word and a non-word character met (or vice versa). \B
matches everywhere except at word boundaries. Some other shortcuts do not work, e.g. \Q...\E
. For a full list check the ECMA column in the Regular Expression Flavor Comparison.
/a|b/
is the same as /(a|b)/
.
Sometimes it's shorter to use <.*?>
(ungreedy matching) instead of <[^>]*>
to match (for example) an HTML tag. But this may also change the runtime and behavior of the regular expression in rare cases.
In the replacement string, $&
refers to the entire match and $`
and $'
refer to everything before and after the match, so /(x)/,'$1'
can be replaced with /x/,'$&'
.
Denormalize to shorten
While /\d{2}/
looks smarter, /\d\d/
is shorter.
Don't escape
In many cases almost no escaping (with \
) is needed even if you are using characters that have a meaning in regular expressions. For example,[[\]-]
is a character class with the three characters [
, ]
(this needs to be escaped) and -
(no need to escape this if it's the last character in the class).
eval()
for a regexp literal can be shorter than RegExp()
Prefer /\d/g
over new RegExp('\\d','g')
if possible. If you need to build a regular expression at runtime, consider using eval()
.
// we escape the first curly bracket so if `p` is a number it won't be// interpreted as an invalid repetition operator.r=new RegExp("\\{"+p+"}","g") // beforer=eval("/\\{"+p+"}/g") // after
eval()
around String.replace() instead of callback
If a callback is used to achieve a certain effect on the output, one can use replace to build the expression that achieves the same effect and evaluate it (the more complicated the matches are, the less this will help):
x.replace(/./,function(c){m=m+c.charCodeAt(0)&255}) // beforeeval(x.replace(/./,'m=m+"$&".charCodeAt(0)&255;')) // after
Booleans
Use !
to create booleans
true
and false
can be created by combining the !
operator with numbers.
[true,false] // before[!0,!1] // after
Functions
Shorten function names
Assign prototype functions to short variables. This may also be faster in more complex cases.
a=Math.random(),b=Math.random() // beforer=Math.random;a=r(),b=r() // after
Use named functions for recursion
Recursion is often more terse than looping, because it offloads bookkeeping to the stack. This is used in @jed's walk function.
Use named functions for saving state
If state needs to be saved between function calls, name the function and use it as a container. This is used for a counter in @jed's JSONPfunction.
function(i){return function(){console.log("called "+(++i)+" times")}}(0) // before(function a(){console.log("called "+(a.i=-~a.i)+" times")}) // after0,function a(){console.log("called "+(a.i=-~a.i)+" times")} // another alternative
Omit ()
on new
calls w/o arguments
new Object
is equivalent to new Object()
now = +new Date() // beforenow = +new Date // after
Omit the new
keyword when possible
Some constructors don't actually require the new
keyword.
r=new Regexp(".",g) // beforer=Regexp(".",g) // afterl=new Function("x","console.log(x)") // beforel=Function("x","console.log(x)") // after
The return
statement
When returning anything but a variable, there’s no need to use a space after return
:
return ['foo',42,'bar']; // beforereturn['foo',42,'bar']; // afterreturn {x:42,y:417}; // beforereturn{x:42,y:417}; // afterreturn .01; // beforereturn.01; // after
Use the right closure for the job
If you need to execute a function instantly, use the most appropriate closure.
;(function(){...})() // beforenew function(){...} // after, if you plan on returning an object and can use `this`!function(){...}() // after, if you don't need to return anything
In the browser
Use browser objects to avoid logic
Instead of writing your own logic, you can use browser anchor elements to parse URLs as in @jed's parseURL, and text nodes to escape HTML as in @jed's escapeHTML.
Use global scope
Since window
is the global object in a browser, you can directly reference any property of it. This is well known for things like document
andlocation
, but it's also useful for other properties like innerWidth
, as shown in @bmiedlar's screensaver.
Delimiters
Only use ;
where necessary. Encapsulate in other statements if possible, e.g.
x=this;a=[].slice.call(arguments,1);a=[x=this].slice.call(arguments,1);
APIs
Pass static data via argument where possible
Use extra bytes to provide default values
Do one thing and do it well
Other resources
- Ben Alman's explanation of his JS1K entry
- Marijn Haverbeke's explanation of his JS1K entry
- Martin Kleppe's presentation about his 140byt.es and JS1K entries
- Suggested Closure Compiler optimizations
- Angus Croll's blog
- Aivo Paas's jscrush
- Cody Brocious's post on superpacking JS demos
- Javascript艺术&技术:节省字节的技术
- 技术与艺术的结合
- 思考的技术与艺术
- 揭开AJAX技术如何节省应用的带宽的内幕
- 10 种可以节省时间的 Eclipse Europa 技术
- 10 种可以节省时间的 Eclipse Europa 技术
- 10 种可以节省时间的 Eclipse Europa 技术
- 10 种可以节省时间的 Eclipse Europa 技术
- Flash 平台技术的优化(二) 节省内存
- 10 种可以节省时间的 Eclipse Europa 技术
- 游戏设计的艺术和技术
- 脱壳的艺术--4反分析技术
- 谈话的艺术-参与性技术
- 谈话的艺术:影响性技术
- 技术、艺术、商业
- 从技术到艺术
- 常用的javaScript技术
- 常用的javaScript技术
- jQuery的load方法,可用于单独加载页面中的某个独立模块
- 静态字段定义
- strcpy_s的用法
- 安装ubuntu 12.04 server版
- web.xml配置总结
- Javascript艺术&技术:节省字节的技术
- BOLT UI界面引擎是如何工作的?(BOLT UI入门教程)
- 解释隐式转换赋值构造和复制构造等一些列问题(运行就明白了)
- Flex Label组件扩展边框与背景
- sqlserver 高版本数据倒到低版本 不同SQL Server版本间的数据库恢复问题
- NSArray和NSMutableArray的使用总结
- 黑马程序员_java中的面向对象
- 简单php分页类
- exec函数族的详解