I'm writing a JavaScript Library that offers the function tablify(anything);
, which is able to represent any Array or Object as an HTML Table.
Now I'm trying to extend the Array and Object prototypes in order to use it like this:
var arr = ["a", "b", "c"];
var obj = {"A": "a", "B": "b", "C": "c"};
arr.tablify();
obj.tablify();
This is my approach:
Array.prototype.tablify = function() {
return tablify(this);
}
Object.defineProperty(Object.prototype, 'tablify', {
value: function () {
return tablify(this);
},
writable: true,
configurable: true,
enumerable: false
});
The issue is, that everything in JavaScript is an Object and therefore not only literals like {a: 1, b: 2}
can be converted, but also everything else.
This wouldn't be such a big deal since my tablify()
can also deal with primitive types, but when I extend Object.prototype, typeof anything
always returns "object"
and I can't distinguish between types anymore:
function tablify(object) {
if (object instanceof Array) {
return ArrayToTable(object);
}
//This is always true if I extend Object.prototype:
if (typeof object === "object") { //only for "normal" objects like "{a: 1, b: 2, c: [],...}"
return ObjectToTable(object);
}
return PrimitiveToTable(object); //strings, numbers, functions, ...
}
- Why is
typeof
always returning "object"
?
- Is it a good thing for a JS-API to provide this sort of functionality (extending Arrays/Objects with ".tablify();")?
- How is it possible to distinguish between "normal" objects, numbers, functions,... ?
- Is it possible to only extend "normal" objects?
(forbid
(42).tablify();
, "string".tablify();
...)
- Whats the name for those "normal" objects? How should I call them?
Why is typeof
always returning "object"
?
Because in sloppy mode, the this
context always is supposed to be an object. When you call a function or pass undefined or null, you get the global object, when you call a method the context will get casted to an object.
Use strict mode for your tablify
method and it will work!
Is it a good thing for a JS-API to provide this sort of functionality (extending Arrays/Objects with .tablify();
)?
Yes. No.. Many people will frown to use your script when you want to share it. See Why is extending native objects a bad practice? for a detailed discussion.
At least you've properly used non-enumerable properties - but I would recommend to use them on Array.prototype
as well, too many people abuse for in
enumerations.
How is it possible to distinguish between "normal" objects, numbers, functions,... ?
typeof
seems fine.
Is it possible to only extend "normal" objects? (forbid (42).tablify();, "string".tablify(); ...)
Not really. All primitive wrappers inherit from Object.prototype
.
Whats the name for those "normal" objects? How should I call them?
Just "objects". Or "plain objects" if you want (distinguishes them from arrays, built-ins, host objects).
The problem is that once you get into your newly-added method on the prototype, the original primitive value has already been converted. For example:
"foo".tablify();
In that call, before the function is called, the string primitive is converted into a String instance. In other words, it behaves as if you had written:
new String("foo").tablify();
I suppose you could use the Object.prototype.toString
function to tell the difference:
function tablify(obj) {
if (typeof obj === "object") {
var sig = {}.toString.call(obj);
if (/ (String|Number|Boolean)\]$/.test(sig)) {
// treat as primitive
}
else {
// object
}
}
// ...
}
You won't be able to tell if the boxed primitive was implicitly created or not, but that probably doesn't really matter for what your code does anyway.