Ever heard of code golfing? In case not: you write as short code as possible. Nothing else matters. Typically something you do for fun with rare real needs in work life.
This means that the code will end up being awful to read, but typically you can write it is shorter than what automatic minification and uglification of JS is able to do. The reason for this being possible is that you end up "abusing" language features, and writing code that has bigger dependency of the other code than is typically favored by a sane programmer. This is also hard for machine to automate even though some code golfing tricks have made their way to the uglification process.
The basics of localStorage
In this article we do a short code example with localStorage. The question we want to answer is "can we use localStorage?" There are many gotchas that relate to this, so we have to work our way step-by-step.
Let's go through with naive implementations:
window.localStorage
This seems fine, right? Just put this within if
and you know whether you can use the store!
Nope! If you disable cookies this code will throw and code execution is halted. So, can we do a check without throwing?
'localStorage' in window
Yay! We no longer crash! And we know if the browser knows about localStorage. But how useful this information really is? Well... every browser for a long long time has had localStorage implemented, so you will always get true
.
This code also does not account for the fact that you can disable cookies, which also disables localStorage. We need something more.
(function() {
try {
if (window.localStorage) {
return true
}
} catch (error) {}
return false
})()
Here we have wrapped code into a try catch. Now the code does not crash as we catch the error, and if localStorage exists we return true
. We have also wrapped it into an IIFE so that we can execute it immediately to get a boolean value that we can then put into a variable or use within if condition.
So this should be enough, right?
Nope! We have some brilliant browsers out in the wild. This code will return true
in Safari private mode, but you can't actually put anything into the store as using setItem
will throw. This forces us to put something into the store and then remove it.
(function() {
try {
localStorage.setItem('detectLocalStorage', '_')
localStorage.removeItem('detectLocalStorage')
return true
} catch (error) {
return false
}
})()
In this case we are sure that if there is a problem using localStorage the code will throw. This means we can move return false
inside catch. The previous code could just run off through everything without triggering an error, which is why false
had to be after the catch. It is always a good idea to return a consistent type, in this case a boolean.
This code is very much what you can find from Modernizr. And since Modernizr is a battle tested utility we can now trust that this code will be enough for our minimal implementation.
Setting further rules
It is time to take the above and manually reduce the amount of code to a minimum. We could go ahead and just use shorter modern JavaScript syntax, such as arrow functions. However since the context is localStorage and shortest possible syntax the reason we are doing this might be to execute a little bit of code outside of our normal JavaScript bundle so that we can do some minimal stuff with legacy browsers. Examples:
- Display a warning dialog that the user can turn off permanently even if the rest of the site is likely to be totally broken in layout and/or functionality.
- Expose a custom highly compatible localStorage implementation globally for all the other code which you know you can safely access the way you want instead of direct localStorage access. This wrapper could also internally attempt to use other means of storage (cookies, UserData, legacy databases...). You could do feature detects and then load further code for what is supported.
Having these kinds of possible reasons to exist it is better to limit ourselves to ES5
level of code which means no arrow functions and no other modern goodies.
Getting ready to golf!
WARNING! Below I show a few changes step-by-step. If you want a challenge and try this yourself you better keep the scroll above the spoiler heading. However you can keep on reading the next three paragraphs as there are a few further rules and a bit of help.
Now we can take the previous code and start looking at things we do to reduce the length of the code. The easiest part is to take the formatting away, but let's limit ourselves here a bit and keep readability as long as possible. Thus we keep spacing and line changes for a bit longer. With this the starting point of original code is 200 characters.
With this what can we do? You may want to try it out yourself so take the last code example above and start reducing the amount of characters! Remember to keep the limitations: functionality must remain the same, output must remain the same, and keep good formatting (indentation, spacing) for as long as you can.
As a final thing before spoilers there is the art of testing. To test as you go I recommend you to have two different browsers with console open: one where you have disabled cookies entirely from settings, and another with default cookie settings. You can run IIFE functions in the console and see the result!
Spoiler process
The first thing most people figure out is that we can remove setItem
!
(function() {
try {
localStorage.detectLocalStorage = '_'
localStorage.removeItem('detectLocalStorage')
return true
} catch (error) {
return false
}
})()
This brings us down to 191 characters.
Another obvious change: use a shorter key. One character should be enough so why not re-use the value?
(function() {
try {
localStorage._ = '_'
localStorage.removeItem('_')
return true
} catch (error) {
return false
}
})()
Wonderful, down to 156 characters!
At this point removals are starting to become trickier. Maybe have a look at the booleans?
(function() {
try {
localStorage._ = '_'
localStorage.removeItem('_')
return !0
} catch (error) {
return !1
}
})()
This now adds a few readability issues, but we went down to 152 characters.
We are still repeating return
s, could we get rid of them? When we look the above code we notice the first condition is using zero while the latter one is using one. We are also repeating the exclamation, so maybe it would be possible to have only one exclamation? However that means it has to go outside the function. Can we deal with that?
!function() {
try {
localStorage._ = '_'
localStorage.removeItem('_')
} catch (error) {
return 1
}
}()
Down to 132 characters, and oh we are getting clever! Instead of executing all of our code inside IIFE we are constructing the IIFE by using an exclamation. Thus with one character we can enforce the end result to be a boolean, which is what we have set as a requirement. This allows us to use any truthy value as output for the error case. The readability also improves, so we have a win-win.
But we're still not done! Can we reduce things further?
!function() {
try {
localStorage._ = '_'
localStorage.removeItem('_')
} catch (e) {
return 1
}
}()
I'm sure someone has been getting ultra mad at me for not shortening error
to e
earlier :) But now we're down to 128 characters. We're almost to half the original size despite NOT touching the code formatting. But is there anything further we can do?
At this point we have two notable repetitive things in the code: two times localStorage, and three underscores. localStorage is a pretty long string, so we could have it only once. However declaring a variable consumes space! Doing var l = localStorage
does not save a lot of space. So could we just pass it to the function?
!function(l) {
try {
l._ = '_'
l.removeItem('_')
} catch (e) {
return 1
}
}(localStorage)
Down to 119 characters! But... well, the code can crash. We moved localStorage outside the try catch :( So this idea is a no go!
Or... is it really?
!function(l) {
try {
(l=localStorage)._ = '_'
l.removeItem('_')
} catch (e) {
return 1
}
}()
The length increased to 122 characters, but we are still six characters less than earlier. We have a victory :) Now, how about those underscores?
!function(l) {
try {
(l=localStorage).removeItem(l._ = '_')
} catch (e) {
return 1
}
}()
We are down to 111 characters! Now the code is really starting to get confusing, because we are setting localStorage to a variable at the beginning of the line, but then have removeItem
which is executed after code that accesses the variable later on. So in any normal code you should never write it, because it breaks the usual read order of how things execute.
The neat trick we have here is the fact of setItem
returning the value that was assigned which allows us the possibility of re-using the output for removeItem
.
But is still there something that can be done? Could we try declaring the underscore only once?
!function(v,l) {
try {
(l=localStorage).removeItem(l[v] = v)
} catch (e) {
return 1
}
}('_')
We have gone up to 115 characters, so the answer is a solid no. We did remove all the repetition there is but it no longer helps us. We have also reduced all the strings and variables to their minimum length.
The only thing we could still attempt is to relocate the return
.
!!function(l) {
try {
return(l=localStorage).removeItem(l._ = '_')
} catch (e) {}
}()
Down to 98 characters, and are we clever again! We do dual exclamations, work with the so that we preserve the output and this allows us to get away a lot of code formatting!!!
Except that this does not work. Why? Well, unlike setItem
removeItem
does not give a value. So we have to add in a bit of extra code...
!function(l) {
try {
return((l=localStorage).removeItem(l._ = '_')|1)
} catch (e) {}
}()
So now we are at 101 characters, but the code now works correctly!
However at this point we have been working with formatted code. What happens if we take the last two working versions, the 111 chars one and 101 chars one?
// was 111 characters formatted
!function(l){try{(l=localStorage).removeItem(l._='_')}catch(e){return 1}}()
// was 101 characters formatted
!!function(l){try{return((l=localStorage).removeItem(l._='_')|1)}catch(e){}}()
The was-formatted 111 chars code is at 75 chars, while the 101 chars code is at 78 chars. This is why it is important to look at the final end result instead of only looking at the code with formatting! You can do the obvious reductions with formatting, but once you're getting into the final polishing phase where every character removal counts you really need to work without formatting to be able to see the truth.
One thing that I didn't account for with my code was semicolons. Once you're working with one-liners that actually have multiple lines you must use semicolons instead of line changes. In the above code we never had the issue as we worked with formatted code all the time, and the end result just happens to work without multiple lines.
I hope you learned a thing or two about code golfing! The usages and needs for doing this are rare, but sometimes when going for the least possible code you may have a need for the tricks. Golfed pieces are ideal for snippets embedded to HTML.
Of course it will be great if the actual uglification process is automated and you have some kind of process in CI or somewhere that allows you to compare the size of the golfed code in question, yet keep a nice formatting and code comments so other people can also figure out what the code is doing and why it is the way it is.
But mah arrow functions!!11
Okay. Will do.
!(l=>{try{(l=localStorage).removeItem(l._='_')}catch(e){return 1}})()
Enjoy your 69! Have a wonderful day!
Top comments (0)