The JavaScript typeof Operator Problem
TL;DR don't even try to normalize or shim newer
Whenever it was a mistake or not to consider
However, we can still use methods through
The only way to find a method in a primitive value is to extend its own
Back to the previous chapter, a situation like this might mislead as well:
Now you can play adding
Doing the same with an argument, rather than context injection, will produce the same output, regardless the function has or not the strict directive.
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.
With above example we might use the variable
This is actually awesome since we can always tell if that string/object has been created using
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
If we remove
As we have seen before, when
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
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
If you never use
If you need
A feature detection like this one could help:
To simplify above code you might trust the generic strict directive behaviour, through undefined, via this snippet:
Followed by the rarely seen check:
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:
In ECMAScript 3rd Edition latest snippet will return
If your normalizer is "so cool" that transform any
Can we say now there's more mess than before? Oh well ...
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 ... )
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 onlynull
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 thetypeof 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 nexttypeof
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 thattypeof 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 ... )
Comments
Post a Comment