looking for a full-immersion/face-to-face HTML5, JavaScript, and Mobile Web Development training in the heart of London? Book your spot
Showing posts with label polyfills. Show all posts
Showing posts with label polyfills. Show all posts

Sunday, June 24, 2012

The JavaScript typeof Operator Problem

TL;DR don't even try to normalize or shim newer typeof via code: you simply can't!

Whenever it was a mistake or not to consider typeof null == "object", many libraries that tried to normalize this operator failed to understand that null is not the only problem.

The JS Polymorphism Nature

We can borrow methods for basically everything but primitives values, such booleans, numbers, and strings, do not accept indeed any sort of property or method attached runtime.

var s = "hello";
s.greetings = true;
alert(s.greetings);
// "undefined"

However, we can still use methods through call() or apply():

function isGreetings() {
return /^(?:ciao|hello|hi)$/.test(this);
}
alert(isGreetings.call("hello"));
// true

The only way to find a method in a primitive value is to extend its own constructor.prototype:

String.prototype.isGreetings = function () {
return /^(?:ciao|hello|hi)$/.test(this);
};
alert("hello".isGreetings());
// true


What Happens When We Invoke A Method

In ECMAScript 3 up to 5.1, when "use strict" directive is not in place, any primitive value will be temporarily converted into an object, where only null and undefined will be converted into the original global object.

alert(function () {
return this === window;
}.call(null));

// ... and here a tiny winy problem ...
alert(function () {
return this ? true : false;
}.call(false)); // true
alert(function () {
return Boolean(this);
}.call(false)); // true
alert(function () {
return !!this;
}.call(false)); // true

Back to the previous chapter, a situation like this might mislead as well:

function setAsGreetings() {
this.greetings = true;
alert(this.greetings); // true
}
var s = "hello";
setAsGreetings.call(s);
alert(s.greetings); // undefined


Why This Is A Problem

Try to imagine this really simple piece of code:

function whichType() {
return typeof this;
}
// guess what ...
alert([
whichType.call(null), // "object"
whichType.call(false), // "object"
whichType.call(true), // "object"
whichType.call("hello"), // "object"
whichType.call(123), // "object"
whichType.call(undefined) // "object"
].join("\n"));

Now you can play adding "use strict" to the very beginning of the whichType function.
Doing the same with an argument, rather than context injection, will produce the same output, regardless the function has or not the strict directive.

function whichType(o) {
// pointless "use strict";
return typeof o;
}
// guess what ...
alert([
whichType(null), // "object"
whichType(false), // "boolean"
whichType(true), // "boolean"
whichType("hello"), // "string"
whichType(123), // "number"
whichType(undefined) // "undefined"
].join("\n"));


What If You Want Use new String/Number/Boolean

It's not only about context injection and the typeof this, it's also about the ability to use collections of the same type as we need.
As example, let's imagine we have a list of unique IDs, and we would like to flag them, relate them, or use them, as objects.

var ids = [
"a",
"b",
"c"
];

// an easy way to mirror strings as objects
var flagged = ids.map(Object);

// check if a generic input/id exists ...
var i = ids.indexOf("b");

// ... and check if it has been used/touched already
if (-1 < i && !flagged[i].touched) {
flagged[i].touched = true;
alert("touched");
}
// once again ... but it will never happen
if (-1 < i && !flagged[i].touched) {
flagged[i].touched = true;
alert("touched");
}

With above example we might use the variable ids to simply filter existent and not existent input, and mirror these ids through they respective objects and eventually reuse these objects as we need, concatenating them, recycling them, etc etc ... I know, above example is not such common use case, right? But of course it's not since we have so many problems with typeof and nobody in JS world has ever suggested to use, when necessary, these constructors in a useful way...

typeof In JS.Next

Since explicit should superset implicit behaviours, ECMAScript 6 and code under "use strict" directive will react like this.

function app(s) {"use strict";
alert(type.call(s));
}

function type() {"use strict";
return typeof this;
}

app("primitive"); // "string"
app(new String("object"));// "object"

This is actually awesome since we can always tell if that string/object has been created using new Constructor or not ... which leads with less ambiguous code and the possibility to use objects as primitives whenever we find a case were it's needed.

function setGreetings() {
this.greetings = true;
}
var s = "hello";
setGreetings.call(s); // pointless
s.greetings; // undefined

// but if we want ...

s = new String(s);
setGreetings.call(s);
s.greetings; // true !!!

As summary, knowing in advance how an object has been created, will let us understand if whatever property/method changed or attached to a generic this will make sense or not ... unless these operations are not simply used internally during the temporarily lifetime of that possible object ... where again we might need to know if we have to clean up after or not ... that's what I call control, isn't it?

... And No Polyfill Will Do

Bad news here is ... there is no way to replicate the new and correct behaviour of the next typeof operator.
If we remove "use strict" directive from the latter example's type() function, we'll notice that the result will be "object" in both cases ... no matter how we pass the original argument.

Reasonable + Inconsistent = Fail

If your only concern is that typeof null should produce the string "null", you might realize that o === null is all you need, rather than creating and calling a function every time you want/need to understand the type of an object.
As we have seen before, when null is used as context, the typeof could return "object" in any case.
When latter case happens, the check against the triple equality operator will fail as well.
If null is not the only problem, just consider that if an engineer created a variable using new String(text) rather than using just text there must be a bloody reason: either the engineer does not know JavaScript OR, most likely, decided to use the possibility offered by an object that is wrapping a primitive value.
If you use a framework that does this wrapping by default there's only one thing to do: change framework!
Strings are immutable while Objects are always freshly baked ... since a list of strings as objects cannot even use Array#indexOf unless you don't hold and/or compare via Generic#valueOf() every time the list content, the amount of pointless RAM and CPU used to work with these kind of wrappers does not scale ... full stop.
If you never use new String and believe that nobody else will as well, your logic might be screwed in any case by the fact that newer browsers might implement a proper typeof and make your code/logic weak when the original constructor has been used as wrapper.

How To Migrate, How To Not Fail

Unless both your code and your environment is frozen by respective versions, and it does not matter if it's client or server side, you cannot basically trust your own code because one day, in some browser, it might act differently.
If you need typeof so much and your code is for 3rd parties development, you might decide to create two slightly different versions of your code or simply normalize the old typeof behavior forgetting then returning "string" when you don't actually know if the developer meant string or new String.
A feature detection like this one could help:

var NEW_TYPEOF = function(){
"use strict";
return typeof this == "string";
}.call("");

To simplify above code you might trust the generic strict directive behaviour, through undefined, via this snippet:

var USE_STRICT_COMPATIBLE = function(){
"use strict";
return !this;
}();

Followed by the rarely seen check:

var USE_STRICT_ENABLED = function(){
return !this;
}();

Above check does not simply tell us if the the browser can handle the strict directive, it also tells us if we are already under use strict.
If we wrap everything in our own closure we might not care in any case ... so, back to the topic, we might understand through these checks if we are under strict directive but what we cannot normalize in any case is something like:

function borrowedMethod() {
alert(yourTypeOfShim(this));
}
borrowedMethod.call("fail");
borrowedMethod.call(new String("fail"));

In ECMAScript 3rd Edition latest snippet will return "object" while in ES5 and strict directive it will return "string".
If your normalizer is "so cool" that transform any instanceof String into "string", then you might realize that same code run under strict in ES5 will return "object" so either both ways, and as summary, your function is not reliable.
Can we say now there's more mess than before? Oh well ...

Doesn't Matter, Had Type

If you wanna have a function that will never be able to compete with the real power of ES6 or "use strict" typeof operator, you might end up with something like this:

function type(o) {"use strict";
var type = typeof o;
if (type == "object") {
if (!o)
type = "null"
; else if (
o instanceof Boolean ||
o instanceof Number ||
o instanceof String
)
type = typeof o.valueOf()
;
}
return type;
}

However, all you gonna have in this case is a function that removes a new feature from the next version of JavaScript.
I have aded this script just to give you a chance if you wanna believe that using methods across primitive wrappers does not make sense ( subclassing anybody ? at that point you gonna have another problem ... oh well, again ... )

Friday, March 16, 2012

On Obtrusive Polyfills

I'll try to make the point short and simple, starting with the tl;dr version: if you create a polyfill and during features detection you realize this cannot work, drop it and let other libs deal with it.

Broken Object.defineProperty

This is just one case really annoying. Bear in mind I am not talking about "making perfect polyfills", this is almost impossible most of the case, I am talking about broken runtime even if you did not really do anything that special.
It was the case of Object.defineProperty shimmed through es5-shim code ... this library is good but that method, as many others, are simply broken.
I was trying to add a setter/getter in IE < 9 and shit happened, more specifically a thrown exception happened ... the repository itself explains that this method and others should fail silently ... so it's OK for the developer to know about it and avoid trusting these methods ...


This Is Not OK

If I check if Object.defineProperty is there and this is all I need to know, I don't want to double check which version of IE, if IE, it is, neither I want to check anything extra.
I simply trust the fact the browser, or some library, fixed the problem for me and I can use this method as it is.

if ("defineProperty" in Object) {
// I want to use it, wouldn't check for it otherwise
}

A broken shim doesn't make anyone life easy while that simple condition in my code could redirect my logic in a completely different approach.
Since in IE < 9 that method simply does not make sense to exist, then it should not exist, period.

Remove The Broken Shim

I hope, and expect, that es5-guys will simply delete Object.defineProperty afterward, once realized it's not usable.
I would not compromise entirely theObject.create but again, if defineProperty() is not there there is no bloody point on using create() too.
Let other libraries deal with the fact this is missing in their own way since every single case could be different: someone simply need to inherit another object, someone else might need the method as it should be.

As Summary

A shim/polyfill that works 90% is OK, specially if the API is covered 100% and even if behind the scene things are not identical, but a polyfill that let a piece of code think everything is fine and the whole app breaks who knows when and why should not be exposed at all or, if you really want to bring similar functionality, it should be exposed prefixed, i.e. Object.$defineProperty() so that the rest of the code is aware that such method exists but it's limited.

Thanks for understanding

Wednesday, January 18, 2012

ES6 Harmony Collections Fast Polyfill

Well, just in case you read RSS and you missed my tweet ... everything you need to know in github repository.
Have fun with Harmony Collections

Friday, June 03, 2011

Partial Polyfills

This is a quick one already discussed during my recent workshop in Warsaw, a concept rarely considered or adopted from JS developers.

What Are Polyfills

If we are updated enough to know ECMAScript 5th Edition, we probably know all possible shims and fallbacks for Array extras as well (e.g. Array.prototype.forEach).
Polyfills are simply snippets able to bring features already available for most recent browsers into those not updated yet.

Why Polyfills

If we develop with present and future in mind, we can take advantage of most recent techniques and solutions in order to both speed up via native support, where available, and maintain our application just stripping out code once all target browsers support them natively ... isn't this cool ?!

Polyfills Side Effects

The most common side effect of a polyfill is performances impact. The Array::forEach is already a good example of decreased performances.
If we think about a forEach, it's nothing different than a classic for loop, except we can reuse a function and eventually inject a different context.
As summary, while the code size will be reduced and all "Array loops gotcha" resolved nicely, old browsers already slower will be even slower due simulated native behavior implemented through JavaScript.
The second most important side effect is that sometimes we cannot possibly reproduce the new native behavior via old JavaScript, which means we are compromising logic and potentials of our application in order to support "not there yet" browsers.

Partial Polyfills

Once we have defined a "full specs simulated" polyfill we can forget problems and use it whenever we want. Unfortunately, we have to agree with all other external libraries as well, libraries that would like to be dependencies free and that most likely will re-implement or re-check over and over if that polyfill is present or not.
On the other hand, there are many situations were we do not need the full implementation of a missed feature and in these cases the advantage of a partial polyfill will result in reduced and safer code.

What Is A Partial Polyfill

Let's take the classic Array.prototype.indexOf as example and check the size and the "complexity" of the full spec proposal in MDC ... done?

// our main library closure
(function () {

// all we may need to indexOf
var indexOf = [].indexOf || function (value) {
for (var i = this.length; i-- && this[i] !== value;);
return i;
};

// wherever we need ...
var index = indexOf.call(arrayLikeObject, value);

// end of our main closure
}());

If we deal with properly defined arrays, arguments, or DOM collections, and if we use indexOf simply to avoid duplicated entries in a generic stack, ask yourself if it makes sense to implement the full specs there rather than those two lines of JS showed above.
As somebody noticed, that snippet is more similar to Array.prototype.lastIndexOf, due reversed loop, but why bother if our indexOf use case is specific?
Why pollute the global Array.prototype causing potential problems for other libraries?
Why make the "search into array" routine more expensive for already slower browsers?
In few words we can reuse few extra bytes in every closure we want to obtain a simplified indexOf behavior compatible with every browser, isn't it ?!

Why Safer

Unless we are not sure 100% that the Array.prototype.indexOf has been replaced with a full specs version and since we may lazy load extra code that may be greedy or change again that prototype, we can address that callback and use call as much as we want being sure that no library will ever change our expected performances and/or behavior.

Why Smaller

Not only we can copy and paste those two lines of code in many places without impacting code size, we can also take advantage of most popular minifiers, able to make each call smaller.

// minified indexOf through prototype
a.indexOf(b);

// minified addressed indexOf
x.call(a,b);

Last, but not least, using call we can automatically use by default the indexOf with objects and every other ArrayLike variable.

Conclusion

There are many cases where we may need one or more ES5 feature but not all of them will be used 100% of their potentials. A partial polyfill can ensure better performances and easier access in the private scope.
Surely we may avoid this technique if by default we normalize all behaviors but once again, we all know we don't like much core functionalities dependencies that may be broken or extremely slow for what we need in our specific cases.