94
\$\begingroup\$

This is similar to other "Tips for golfing in <...>" but specifically targeting the newer features in JavaScript brought up in ECMAScript 6 and above.

JavaScript inherently is a very verbose language, function(){}, .forEach(), converting string to array, array-like object to array, etc, etc are super bloats and not healthy for Golfing.

ES6+, on the other hand, has some super handy features and reduced footprint. x=>y, [...x], etc. are just some of the examples.

Please post some nice tricks that can help shave off those few extra bytes from your code.

NOTE: Tricks for ES5 are already available in Tips for golfing in JavaScript; answers to this thread should focus on tricks only available in ES6 and other future ES versions.

However, this thread is also for users who currently golf using ES5 features. Answers may also contain tips to help them understand and map ES6 features to their style of ES5 coding.

\$\endgroup\$
0

40 Answers 40

1
2
3
\$\begingroup\$

Avoiding commas when storing lots of data

If you have a lot of data (i. e. indices, characters, …) that you need to store in an array, you might be better off leaving all commas away. This works best if every piece of data has the same string length, 1 obviously being optimal.

43 Bytes (baseline)

a=[[3,7,6,1,8,9,4,5,2],[5,4,3,2,7,6,5,4,3]]

34 Bytes (no commas)

a=[[..."376189452"],[..."543276543"]]

If you're willing to change your array access, you might reduce this even further, storing the same values like so:

27 Bytes (same data, only changes array access)

a=[..."376189452543276543"]
\$\endgroup\$
2
  • \$\begingroup\$ Why is only the last block highlighted? \$\endgroup\$ – CalculatorFeline Jul 20 '17 at 16:14
  • \$\begingroup\$ @CalculatorFeline Thanks, fixed. \$\endgroup\$ – Chiru Jul 20 '17 at 16:35
3
\$\begingroup\$

Splitting Strings

Surprised this hasn't been posted already (or maybe I missed it).

If you have an array of 5 or more strings, you can save bytes by instead splitting a string containing all the elements.

["one","two","three","four"]
"one,two,three,four".split`,` // 1 byte longer
["one","two","three","four","five"]
"one,two,three,four,five".split`,` // 1 byte shorter
["one","two","three","four","five","six"]
"one,two,three,four,five,six".split`,` // 3 bytes shorter

For each additional string in your array, you'll save a further 2 bytes with this approach.

If any of your strings contain a comma then change the delimiter to a differet character.

\$\endgroup\$
1
  • \$\begingroup\$ "one,two,three,four,five,six".split() // 4 bytes shorter, split defaults to a comma \$\endgroup\$ – Mettin Parzinski Apr 30 at 20:07
3
\$\begingroup\$

Convert BigInt back to Number (Chrome 67+ / Node.js 10.4+)

!! ALERT : ESNext feature ahead !!

BigInt makes arbitrary-precision integer arithmetic more handy in JS, but there is a caveat - most already existing functions that accepts Number, does not accept BigInt. UPDATE: array indexer DOES support BigInt for some reason.

When we need to convert a BigInt back to Number for some reason, we cannot use +n (this is explicitly forbidden in the specs), but instead we need to use Number(n) -- except we do not really need to do so.

Instead of using Number(n), we realise that we can actually convert the BigInt first into a String and then to a Number using +`${n}` , which saves 2 bytes.

But we can do better: wrap the BigInt with an array then directly cast it using + operator. This gives +[n] which saves 3 more bytes. Most importantly this eliminates the use of Number(n) or even (N=Number)(n) and further N(n)s for multiple uses because +[n] has the same length as N(n).

You can test this out with this example:

s=163n;
console.log(Number(s)) // 9 bytes
console.log(+`${s}`)   // 7 bytes
console.log(+[s])      // 4 bytes

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Use ** (ES7)

** is the new exponentiation operator. Occasionally useful.

Math.pow(2,2) // size: 13, result: 4 
2**2          // size:  4, result: 4
\$\endgroup\$
2
  • \$\begingroup\$ This does work for me, tested in Chrome 58 on Linux. Kind of obvious as it's being reported in 2017 and ES6/7 have settled down now, but just to verify. \$\endgroup\$ – i336_ Aug 23 '17 at 0:06
  • \$\begingroup\$ @Satoshi, when raising a single digit number or a variable with a single character name to the power of 2, it's shorter to just multiply it by itself (e.g., 3*3 or n*n); the exponentiation operator only works out shorter when raising to the power of 3, or higher. \$\endgroup\$ – Shaggy Oct 17 '18 at 13:34
2
\$\begingroup\$

Shortening Promise Chains with async/await

Taken from my answer here.

Sometimes you can shorten longer promise chains with async/await. The main benefit is from getting rid of the beginning of the arrow function in each then callback. .then(x=>x (10) gets replaced with await( (-4), but you first pay with async (+6). So to make up for the initial overhead of 6 bytes, you'd need at least two then chains to get any benefit.

+-------------+----------------+
| then chains | async overhead |
+-------------+----------------+
| 0           | +6             |
| 1           | +2             |
| 2           | -2             |
| 3           | -4             |
| …           | …              |
+-------------+----------------+

Example 1

x=>x().then(y=>y.foo()).then(z=>z.bar())
async x=>await(await(x()).foo()).bar()

Example 2

u=>fetch(u).then(r=>r.text()).then(t=>/\0/.test(t))
async u=>/\0/.test(await(await fetch(u)).text()))
\$\endgroup\$
2
\$\begingroup\$

Array.prototype.find in array traversing

When traversing an array with Array.prototype.map, all elements are processed. However, when only the first truthy invocation is needed, flags are needed to stop subsequent elements from being processed. When processed with a recursion, more flags may be needed.

However, with Array.prototype.find, flags can be saved. Array.prototype.find receives a function with the same signature as Array.prototype.map does, and returns the first element that the function returns true for that element, or undefined if none returns true.

Consider an example of finding the first non-zero value from a numeric array:

// For-loop
A=>{for(i of A)if(i)return i}
// Array.prototype.map
A=>A.map(x=>i=i||x,i=0)&&i   // -3
// Array.prototype.filter
A=>A.filter(x=>x)[0]         // -9
// Array.prototype.find
A=>A.find(x=>x)              // -14

Real case - determine whether a numeric matrix has all non-zero values connected. The function is written in the way that can be invoked by both map and recursion:

// Uses map: 115 bytes
A=>w=>A.map(x=F=(u,i,a)=>u&&(x||!a)&&[-w,-1,1,w].map(v=>v*v>1|i%w+v>=0&i%w+v<w&&F(A[v+=i],v),x=A[i]=0))&&!+A.join``
// Uses find: 102 bytes
A=>w=>(A.find(F=(u,i)=>u&&[-w,-1,1,w].map(v=>v*v>1|i%w+v>=0&i%w+v<w&&F(A[v+=i],v),A[i]=0)),!+A.join``)

In the case above, we need to only rewrite one region, so we must use flags to avoid further invocation of F by map, but in the meantime we need to also let the recursion run, so another flag is used to indicate whether the invocation is by map or by recursion. With find, both flags are now unnecessary. However, be careful with the return value of the function. In this case, when u is zero then zero is returned, and otherwise an array is returned, which is truthy.

\$\endgroup\$
2
\$\begingroup\$

Default parameters for variable initialization

Suppose you have some long duplicated subexpressions in a lambda:

d.map((x,i)=>[c[i]*c[i+1],c[i]*c[i+1]*x])

Unfortunately, you cannot just do d.map((x,i)=>e=c[i]*c[i+1],[e,e*x]) because apparently e is not available in the scope of the second operand of the comma operator.

But you can "initialize" a variable in a default parameter declaration:

d.map((x,i,_,e=c[i]*c[i+1])=>[e,e*x])

You have to do it at an unused parameter position. Above, the function passed to map only ever gets invoked with three parameters and hence we can exploit the fourth parameter for our purposes.

\$\endgroup\$
1
1
\$\begingroup\$

Aliasing using eval and template strings

This one's more effective with longer code containing more repetition:

a.push([0,1,2,3,4,5,6,7,8,9,10].push([0,1,2,3,4,5,6,7,8,9,10].push([0,1,2,3,4,5,6,7,8,9,10]))) // before
a.push([...Array(11).keys()].push([...Array(11).keys()].push([...Array(11).keys()]))) // before - golfed
a.push((f=_=>[...Array(11).keys()])().push(f().push(f()))) // before - golfed more

eval(`a${b=`.push([...Array(11).keys()]`}${b+b})))`) // after

Probably not the best example, but you get the idea.

Although the use case is somewhat limited, it can save quite some bytes, especially because it can get at several char sequences (like [function]([args]) or [...) that would otherwise be unable to be aliased.

Be careful with this, however; very often, using this technique can actually increase, not decrease, your byte count.

\$\endgroup\$
3
  • \$\begingroup\$ It's actually shorter just to do: a=>[...a[b='toLowerCase']()].map(x=>x[b]())[b](). Shouldn't this go under the regular Tips for Golfing in JS? \$\endgroup\$ – Downgoat Jan 28 '16 at 2:19
  • \$\begingroup\$ Perhaps that was a bad example. But the main reason this should go here is because using ${...} is the only way this stays golfy without losing its usefulness. \$\endgroup\$ – Mama Fun Roll Jan 28 '16 at 3:24
  • \$\begingroup\$ I'm coming up with a better example. \$\endgroup\$ – Mama Fun Roll Jan 28 '16 at 3:24
1
\$\begingroup\$

Using flat() when building an array recursively (ES10)

When elements are appended conditionally to an array with recursive calls, a typical construction is:

f=k=>k?[...k%3?[k]:[],...f(k-1)]:[] // 35 bytes

Try it online!

(this example code filters out values that are multiple of 3)

Notice that the spread operators is used twice (costing 6 bytes) and the payload value k has to be put within a singleton array (costing 2 more bytes).

An alternate approach is to apply .flat() immediately after each iteration, saving a byte:

f=k=>k?[k%3?k:[],f(k-1)].flat():[]  // 34 bytes

Try it online!

\$\endgroup\$
0
\$\begingroup\$

(strawman) Use do expressions instead of evaling strings

[Babel preset] and [proposal].

Comes with the added bonus of keeping syntax highlighting. This might be a stretch in some contests, but is nonetheless interesting. I think if it's supported in Babel, it should qualify as competing.

Consider the following function (37 bytes):

f=n=>{for(o=i=0;i<n;o+=++i);return o}

This might be golfed down with the classic eval trick (34 bytes (thank you everyone)):

f=n=>eval("for(o=i=0;i<n;)o+=++i")

But we can do better with do (30 bytes):

f=n=>do{for(o=i=0;i<n;)o+=++i}

Working example - note that Babel needs let statements in all above functions as well.

Essentially what I'm trying to say is if a solution can be done with eval, you can almost always shave off a few bytes using do.

\$\endgroup\$
9
  • \$\begingroup\$ 35 bytes f=n=>eval("for(o=i=0;i<n;)o+=++i") \$\endgroup\$ – Yay295 Aug 16 '16 at 18:41
  • \$\begingroup\$ @Yay295 Incredible! Updated my post, thank you! \$\endgroup\$ – Scott Aug 16 '16 at 18:50
  • \$\begingroup\$ You can golf both of the first two functions down by 5 bytes by replacing f=(n,o=0)=>...i=0; with f=n=>...o=i=0; \$\endgroup\$ – ETHproductions Sep 13 '16 at 17:45
  • \$\begingroup\$ @ETHproductions Good point, I should have reflected those changes in the other examples when I updated the third. Thank you! \$\endgroup\$ – Scott Sep 13 '16 at 18:26
  • \$\begingroup\$ Briefly what is Babel? \$\endgroup\$ – edc65 Sep 13 '16 at 18:31
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.