Forget the global undefined
Too many developers relies into undefined variable in ES3, and all they should do is to set undefined = true on global scope and see if the application or all unit tests break or not. I am going to demonstrate how simple is, at least in ES3, to redefine by mistake the global undefined.
// inside whatever closure/scope
function setItem(key, value) {
this[key] = value;
}
// later in the scope, setItem may be reused
// through call or apply for whatever object
setItem.call(myObject, myKey, "whatever value");
// if for some reason the first argument used as context
// is undefined, the "this" will point to the global context
// if for some reason the second argument used as key
// is undefined, the accessor will cast it as undefined string
// the result of latter call with these two
// common or simple conditions
// is the quivalent of:
window.undefined = "whatever value";
// where window["undefined"] or window[undefined]
// are the equivalent of window.undefined
undefined; // "whatever value"
Here a list of side effects every time we deal with undefined:
- it is not safe to compare variables against undefined since it is simply implicitly declared as unassigned variable, as var u; could be, but in the global scope
- it is not possible to minify it since it is a well known variable
- its access is slow, since requires scope lookup potentially up to the global one every time we write that bloody variable name in our code, wherever it is
Here is a list of best practices to avoid the usage of undefined:
- define your own undefined variable, if necessary, via var undefined;, and only if you are sure that nothing can change it's value in the current scope (e.g. eval)
- the first point will allow minifiers/compilers to shrink the undefined variable into, possibly, one char, so it is size safe
- if null value can be considered undefined as well, where both undefined and null do not support accessors such unknown.stuff, compare the potential undefined variable against null, since by specs null == null && null == undefined && null != 0 && null != "" && null != NaN && null != false && null != whateverThatIsNotNullOrUndefined
About latter point, null is a constant so no lookup is performed since we cannot re-assign the null value and every time somebody tells you something like: "doooode, JSLint is complaining about that 'v == null'" simply tell him that JSLint is suggesting a bad practice and point this person to this post :D
About typeof v === "undefined" ? Bullshit! a typeof operation with an eqeqeq against a string that cannot be minified ... are you a programmer that knows the language or you think JSLint, as automation tool, is the bible? In the second case I have already posted JSLint: The Bad Part why this tool is not always ideal: have a look!
It must be told that in ES5 the global undefined won't be enumerable/writable/configurable anymore, and that showed example will fail since this reference, when null or undefined is passed through call/apply, will be null as well (errors).
Cache the bloody variable or namespace !
It does not matter how fast and cool are nowadays CPUs, it's about common sense.
If you spot something like:
my.lib.utils.Do.stuff(some);
my.lib.utils.Do.stuff(thing);
fix it ASAP!
This is a list of side effects caused by duplicated access for whatever it is:
- a namespace requires a lookup usually up to the global scope, this costs time behind the scene
- minifiers/compilers cannot optimize anything so far since properties cannot be shrinked so this technique is bigger application size prone
- getters are always invoked, and 99.9% of the time this is not what we are looking for. JavaScript has a beautiful and easy interface exposed to developers but behind the scene there are 90% of the time getters which means slower performances for everybody.
About latter point, we can simply check, from one of the fastest browsers in the market, how much a simple node.childNodes[0] could cost.
If you are not familiar with C++, just imagine this piece of JavaScript every time we access an index of some Array:
Array.prototype.item = function (index) {
var
undefined,
pos = 0,
// useless if we return lastItem but for some reason there ...
n = this.slice(0, 1)
;
// optimized for multiple access with the same index
if (this._isItemCacheValid) {
if (index == this._lastItemOffset)
return this._lastItem;
var
diff = index - this._lastItemOffset,
dist = Math.abs(diff)
;
if (dist < index) {
n = this._lastItem;
pos = this._lastItemOffset;
}
}
if (this._isLengthCacheValid) {
if (index >= this._cachedLength)
return undefined;
var
diff = index - pos,
dist = Math.abs(diff)
;
if (dist > (this._cachedLength || (this._cachedLength = this.length)) - 1 - index) {
n = this[this._cachedLength - 1];
pos = this._cachedLength - 1;
}
}
if (pos <= index) {
while (n && pos < index) {
n = this[pos];
++pos;
}
} else {
while (n && pos > index) {
n = this[pos];
--pos;
}
}
if (n) {
this._lastItem = n;
this._lastItemOffset = pos;
this._isItemCacheValid = true;
return n;
}
return undefined;
};
Array.prototype._lastItemOffset = 0;
[1,2,3].item(0);
Now, consider above code against what we usually do which is simply arr[0] ... and consider that this is just the single item access for a ChildNodeList collection ... how many other operations we want to perform through DOM searches, namespaces, Array access, etc etc? Cache It Whenever It Is Possible!, and this should be the point number one in every "performances oriented" article or book.
The only thing to consider when we cache are object methods, if we "de-context" a method that use this reference inside, we can simply cache the object, it is going to be enough, but if we access a property twice, as often happens with domNode.style property, as example, cache it!
// my.name.space.Do.stuff is a method
// of my.name.space.Do where this is used
// WRONG
var stuff = my.name.space.Do.stuf;
stuff(); // global this in ES3, error in ES5
// BETTER
var Do = my.name.space.Do;
Do.stuff();
Use the in operator
For the same getter/access reason, this classic check can be harmful:
if (someObject.property) {
// do stuff with property
}
Specially if we are dealing with host objects, some access could cause errors (e.g. (domNode || unknown).constructor in IE or similar operations) while a classic:
if ("property" in object) {
// do stuff with object.property
}
can "save the world" since we do not access the property but we simply check if it is accessible ... a tiny difference extremely important and fast in any case.
Avoid redundant Function Expressions
We are kinda lucky here, since functions as first class objects, are truly fast to create in JavaScript. These do not require a class to be used, neither an object or special tricks, these are simply variables able to be invoked executing what has been defined inside their body through an activation context process, plus named arguments, the length of these arguments, the name of the function, if any, plus arguments variable if accessed in the body, and "almost nothing else" ... but we can already get the fact functions do not come for free, do we?
Here there are a couple of function expression common mistakes.
Closure inside a Loop
// WRONG
// the classic way to avoid
// unexpected behavior on lazy evaluation
// the equivalent of 20 functions
for (var i = 0; i < 10; ++i) {
(function (i) {
setTimeout(function () {
alert(i);
}, 15);
}(i)); // trap it!
}
// BETTER
// 11 functions rather than 20
// same behavior, better performances
for (var
getTimeout = function (i) {
return function () {
alert(i);
};
},
i = 0; i < 10; ++i
) {
setTimeout(getTimeout(i), 15);
}
Array.extras Misunderstood
// WRONG
// a new expression for each Array.extra operation
// a lookup to access another this reference
var self = this;
what.forEach(function (value, index, what) {
// do something
self[index] = value;
});
ever.forEach(function (value, index, what) {
// do something
self[index] = value;
});
// BETTER
// 1 function against N
// this reference through the native interface
// easier to debug/maintain/improve/change
function forThisEachCase(value, index, what) {
this[index] = value;
}
what.forEach(forThisEachCase, this);
ever.forEach(forThisEachCase, this);
Use Natives !!!
Newcomers are lazy, it does not matter if they are noob or they have 10 years of Java, PHP, Python, C#, or Ruby over their shoulders, they will always look for a framework able to do truly simple stuff for the simple reason that they don't know/get yet JavaScript which is different from every other common programming language. This is the best starting point to slow down every little operation.
Many frameworks offer classes, mixins, native wrapper which aim is often the one to invert arguments for whatever reason simplifying operations (e.g. the classic $.eash in jQuery which is making junior developers think that the native forEach will pass the index as first argument and this as current reference).
If three lines based on native prototypes/functionality are more than 1 magic method call, go for it!
Specially if standard, natives will never change while libraries are constantly improving and APIs changing as well for whatever valid reason.
If you need a for in loop, do the for in knowing what you are doing, ignoring JSLint if necessary 'cause you are dealing with objects that inherits from objects and you may be interested into inherited properties/methods as well.
If the problem is the list of property, we can always create safer ways to interact with what we would like to enumerate, as example:
var SafeLoop = (function (id) {
function SafeLoop() {
this[id] = [];
}
SafeLoop.prototype.keys = function() {
return this[id];
};
SafeLoop.prototype.enum = function (key) {
var enumerable = this.keys();
enumerable.push.apply(
enumerable,
typeof key !== "object" ? arguments : key
);
return this;
};
return SafeLoop;
}(Math.random()));
var o = new SafeLoop().enum("a", "b");
o.a = o.b = o.c = o.d = 123;
o.e = 456;
// enum accepts N arguments or an array
o.enum(["c", "d"]);
// fast and safe, without an hasOwnProperty call for each item
for (var key = o.keys(), i = key.length; i--;) {
alert([key[i], o[key[i]]]);
// d, c, b, and a with 123
}
// extend the prototype if necessary
SafeLoop.prototype.forEach = function (callback, context) {
for (var
enumerable = this.keys(),
i = 0, length = enumerable.length,
key;
i < length; ++i
) {
key = enumerable[i];
callback.call(context, this[key], key, this);
}
};
o.forEach(function (value, key, o) {
alert([value, key, o]);
// 123,a|b|c|d,[object Object]
});
Above code is just an example "AS IS" and there are many part to improve. The concept is that JavaScript allows us to define what we need in such simple way and most of the time we don't want to include and "move" a whole framework to do something simple as loops are, as example, do we? If we do, well, we are creating redundant function expressions, including extra bytes for just some extra functionality, and potentially making the application slower ... remember: we are in the mobile era, CPUs are not those you have in your MacBook Pro and frameworks should be used only when we have real benefits, e.g. selector engines or much more complicated methods. Do you agree?
thanx for great tips!
ReplyDeletejust one rather noob question: what do you meen by "caching"? like assigning var f = my.lib.utils.Do.stuff; and using f anywhere further ahead where needed in the scope?
as long as that function does not refer to "this", otherwise cache the object "Do" and use Do.stuff() buta void duplicated namespace/objects/accesses ;-)
ReplyDeleteI'm kinda used to typeof(something) !='undefined', hope it will still work when someone messes up window.undefined, but please, correct me if not ;)
ReplyDeletewho uses some.thing.long is breaking Law of Demeter anyway, not that it wouldn't be one of the easiest rule to break, it's only that we're used to it.
Usually, when using Ext.JS (my favourite for business/crud apps, although ugly at the inside, easy at the outside framework), one should use namespaces only at initialization.
So, doing a new my.objects.reside.in.this.space() (not in a for-loop, but on init) is worth the lookup I believe. We mostly use a vendor specifier, a library specifier, and the classname usually, that's three getters, if anyone needs longer maybe the architecture should be re-thought.
strings cannot be minified + typeof is a runtime call, not an == or an ===
ReplyDeletevar u; is enough
namespace for singletons are fine as well, if you use the namespace twice you are "doing wrong"
Put the Ext JS class inside a closure and specify namespaces you call more for its creation once but the point is about repeated code execution where namespace/object/array access could cost, classes are created once so it is not such big deal ;)
Thanks ;)
ReplyDeleteThanks Andrea for the thought-provoking post! Keep posting!
ReplyDeleteConcerning ==null etc., the code-checker JSCheck has matured a bit. It supports the full ECMAScript 3 and a minimalistic set of warnings.
@Guillaume excellent stuff, I will try it ASAP!!! Please tell me if JavaScriptCore is supported as well so I can create the macro in Mate avoiding JSLint, thanks!
ReplyDeleteAndrea, jscheck.js works in the Safari browser and in the Google V8 Engine so it should do as well with JavaScriptCore.
ReplyDeleteAt work, I batch-check large amounts of code using the Google V8 Engine and JSCheck. V8 should be pretty close to JavaScriptCore so it should work - as long as read() print() write() os.system() os.chdir() are supported.
Here is an example of command-line use: test.unit.js, which uses (among other things) jscheck.js. Hope this helps.
hi Andrea. I have a question. I know that there are a lot of frameworks out there and I don't like to suggest one more but... have you ever thought of a micro-framework oriented to learn to program in pure Javascript? I'm not sure how to do it, but all the stuff that the existing frameworks have, hide the beauty of the language and, maybe, some kind of framework can bring some light to the power of js...
ReplyDeletethat was vice-versa purpose, where yu can use natives in IE as well, as example :)
ReplyDeleteGreat examples
ReplyDeleteI will be using "property" in object
*going red *
Hey andrea, are you still with nokia?
ReplyDeleteyes ... why? :)
ReplyDeleteIn case of interest, the JSCheck site moved to http://glat.info/jscheck/
ReplyDelete"Newcomers are lazy, it does not matter if they are noob or they have 10 years of Java, PHP, Python, C#, or Ruby over their shoulders, they will always look for a framework able to do truly simple stuff for the simple reason that they don't know/get yet JavaScript which is different from every other common programming language."
ReplyDeleteCan't agree. The fact there are many so frameworks out there is simply because Javascript fails at 3 fundamental things:
- Manipulating the DOM (how ironic!)
- Triggering events the same way across browsers
- Absence of good standard methods for dealing with strings, numbers, dates, etc. Things all programming languages have for a good extent.
Javascript is a good sandbox, but is far from a rich programming language.
a framework because you don't know standards way to manipulate strings, numbers, or dates? There are many, and here come the laziness ;-)
ReplyDelete