Many times I read or write silly stuff and often it takes a while before I realize "
what dafuq did I just read ?"
Well, this post is about just few
a-ha moments, together with
WTFs, I keep having from time to time: enjoy!
Those 4 Repeated + Disturbing Bytes
I mean, this must come first, right? Web Development Tools are improving like hell and Web App development are 99% of the time behind a build process able to shrink HTML, recompile and optimize CSS, eliminate even death code from JS after parsing, recompilation, minification, pre-gzip-compression ... you name it ... and finally, we gonna have the output with a file called:
mystuff.min.js
?!?!
Am I the only one thinking that this does not make sense and all files should be named by default
.max
so that the equivalent after minifcation won't include those damn 4 bytes per each file?
Entire CDN dedicated to common libraries and these expose minified files with
.min
extension ... isn't this bloody brilliant?
The "Not So Smart" Condition
This time is about logic, and the fact that order matters. "
Which order?" you say? Thanks for asking!
// knowing only one stuff item has a value
for (var fValue, i = 0; i < stuff.length; i++) {
fValue = parseFloat(computate(stuff[i])) ||
fValue;
}
Above code might look weird but it's actually more common than we can imagine.
Think about defining, as example, a prefixed method so that only one of them will be available. As soon as the value is not undefined we keep going checking for no reason something we don't need so ... can you spot the difference?
// knowing only one stuff item has a value
for (var fValue, i = 0; i < stuff.length; i++) {
fValue || (
fValue = parseFloat(computate(stuff[i]))
);
}
If
fValue
has a value there is no reason to perform other expensive function calls. Moreover, how can we be that lazy that breaking a for loop is one of those things I basically never see in production code ?
// knowing only one stuff item has a value
for (var fValue, i = 0; i < stuff.length; i++) {
if (fValue = parseFloat(
computate(stuff[i]))
) break;
}
Scary ... uh ? Point here is: always think about the execution order, logic, cost of your boolean expressions ... you might realize swapping few things around could result in better performance easily while adding a break, sometimes, will be smarter than anything else.
Not Just Missing The Falsy
This is the most common one, no way JS developers will learn this differently. I have
tried many times but there's nothing to do, bad habits are bad habits!
Let's see what I am talking about ...
obj.prop = obj.prop || defProp;
Can anyone answer exactly what are we trying to do up there? ^_^
Don't bother, here the summary and ... NO, we are not trying to do what we think!
- access a possibly undefined property to a generic object
- if that was a getter, invoking a getter of that object
- either getter or property, verifying after lookup that value is not
""
, 0
, false
, NaN
, null
, or undefined
- even if defined, discarding any of previous valid values in order to lookup for the variable
defProp
, assuming this won't have similar values we already discarded or the sequence could be infinitely pointless if repeated - once a value has been addressed, if any, assign as the property of the object
- if this has a setter, invoke the setter for no reason potentially reacting for no reason as property update.
- if the prop had a getter only, we have simply raised an Error ^_^ and without any valid reason
Nobody Uses Getters/Setters Anyhow
Excuse me? It's
even worst than we think since one of the most powerful, cool, useful, tools ever for JS has been
approved in harmony:
Object.observe().
Can you imagine a
defProp
as
NaN
by mistake so that these three lines will most likely cause three records notifications because we are using a dumb pattern to assign a property if not defined yet? ^_^
// NaN != NaN => true
var defProp = NaN;
obj.prop = obj.prop || defProp;
obj.prop = obj.prop || defProp;
obj.prop = obj.prop || defProp;
// 3 notification records
// 1 as "new", the one we are interested
// 2 as update, with "same value" ...
Just Brilliant, and
moArover this habit is so common that whoever will decide to simulate
Object.observe()
through
Proxy
(I am already, stay tuned) will need to guard the Records creations and notifications avoiding redundant notification because developers keep reassigning for no reason!
As Easy As This
// the in operator does not trigger anything
"prop" in obj || (
obj.pro = defProp
);
// alternatively, no trigging
obj.hasOwnProperty("prop") || (
obj.prop = defProp
);
There, you better embrace this pattern or you gonna have bad time soon either with Proxy, already available, and Object.observe, specially when we don't know who created the object and how. So, once again, repeat with me:
avoid property access and/or reassignment for no reason, no excuse!
Bitwise To The Rescue
Rather than understand them, we often avoid them ... well, we shouldn't. Bitwise are cool and let us make a lot of things.
These days we are discussing
Collection#has(key)
and
Collection#contains(value)
where latter one could be the equivalent, if it won't use
identicalType()
to
-1 < arr.indexOf(value)
or, as I can read in many source code, the boring
arr.indexOf(value) !== -1
.
How about
if (~arr.indexOf(value)) { ... }
to know if an Array contains a value? Problems only with massive collection length but that's 99% of the time not our case.
If you are wondering what's going on there, consider that
~
is the equivalent operation of
-(x + 1)
where
-1
is the only number that becomes
0
, the falsy value, while all other indexes, included
0
, will be
-1
or lower number, all truthy.
Smart Configuration
How many times we end up defining configuration options as strings ? ie,
"shrink",
"minify",
"lint" ...
And how many check to define one or more behavior, accordingly to these strings?
And how about having same behavior within different configuration options ?
if (obj.option == "shrink" || obj.option == "minify") { ... }
or even this:
/^shrink|minify$/.test(obj.option)
, creating a RegExp object maybe for each time we want to check that option ... how about this:
var Option = {};
Option.SHRINK = 1 << 0; // pow(2, 0)
Option.MINIFY = 1 << 1; // pow(2, 1)
Option.LINT = 1 << 2; // pow(2, 2)
// etc ...
Option.ALL = Option.SHRINK |
Option.MINIFY |
Option.LINT;
Option.NUT = Option.ALL &
~Option.LINT;
// test
alert([
Option.SHRINK & Option.MINIFY, // 0
Option.LINT & Option.MINIFY, // 0
Option.MINIFY & Option.MINIFY, // 2
Option.SHRINK & Option.ALL, // 1
Option.LINT & Option.ALL, // 4
Option.MINIFY & Option.ALL, // 2
Option.ALL & Option.ALL, // 7
Option.SHRINK & Option.NUT, // 1
Option.MINIFY & Option.NUT, // 2
Option.LINT & Option.NUT, // 0
Option.ALL & Option.NUT // 3
].join("\n"));
The easiest way to configure an object is via
obj.option = Option.LINT | Option.MINIFY
, as example, and we are sure that all associated operations will be performed ... how ? As example, with a switch :)
switch (!!obj.option) {
case !!(obj.option & Option.ALL):
// perform linting first
case !!(obj.option & Option.NUT):
// perform minification shrinking ?
minify(
obj.option & Option.SHRINK
);
}
If we don't like the double
not
we can always use a
function b(v){return !!v}
and make it look gorgeous, isn't it?
Enough for this time
These are just few things I've been dealing with recently either with my projects or in some mailing list. More will come as soon as I spot it, of course ... right now, just think about these patterns and hint, maybe your code could make more sense, becoming more readable, easier to maintain, future proof, and all this with very little extra effort. Have a nice week end.
6 comments:
The problem is, and will be, that as the lingua franca of the web, JS won't be developed by just you and me and a few others who dedicated a fair bit of our time understanding how it works.
The web will be developed by a lot of people who just don't care.
(Personally, I usually apply all the options from a config object in a for in cycle, using hasOwnProperty, which makes it quick, although I haven't written a single line of prod code since last June)
But in general, the the job of a compiler, wether it is a minifier, a cross-language compiler or the built-in compiler of a JIT VM, is to make sure that people who know the tricks, who know deeply and care about the underlyings of the language make the optimizations.
So no, I don't say such needs to be in the compiler because I'm lazy: I'm aware not all the things can go into the compiler and we should care and if we're unsure on which construct to use, we either have to check VM sourcecode, or simply run jsperf tests.
But most people won't do this. They'll just put in whatever comes into their mind. Engineers of big JS-based application providers, like Facebook, yes, they have to care about it, as hundreds of millions of people depend on this every single day.
But for the others, it has to be a language feature.
Besides, wether we like it or not, the original || notation on setting the property makes the intent clear even to someone who has no knowledge of the underlying hash implementation of JS objects, but has some knowledge of C-style languages in general.
The reassign for no reasons cannot be optimized by JIT because it's a valid operation with intents. Object.observe() will have problems because of that pointless operation, or better... the code using it will have problems, no matter who or how many wrote the code. Last, but not least, I don't talk about my job, team, company I work for here... since ever ! I represent myself, with my thoughts, and nothing else.
No, I didn't say Facebook because you work there... I believe you work at Facebook because you care about such things.
I still think that a
variablename = variablename || scalarvalue
can be easily recognized, and either optimized automatically, or at least, spotted by a linter like jslint or jshint.
that one yes but there is no way to optimize obj.prop = obj.prop || defValue; which is the case mentioned in this post
maybe your code could make more sense, becoming more readable, easier to maintain
Really?! By using bitwise operators where a logical expression is expected? Rule of thumb is not to use "not common" solution in common places like these. Also bitwise operators are very rarely used in JS with a good reason: all the numbers are represented as "double"s and when bitwise operators are used on them they are converted to integral representation.
I think code "makes more sense" is "more readable" and is "easier to maintain" if you use 'boolean' expressions where boolean expressions are expected and keep the bit-operation magic for those places where you actually want to do bit operations.
Your last example is actually a reasonable one for using bitwise operations :)
All good refactoring but I especially like the bitwise example. That was "The Way It Was" for C programmers; a dev could recognize a bitmask pattern as easily as JS devs can recognize "obj.prop = obj.prop || someVar". Shame it fell out of use, but it is definitely a good one.
Post a Comment