How to extend Obect.prototype correctly?

2020-03-31 02:12发布

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?

2条回答
小情绪 Triste *
2楼-- · 2020-03-31 02:46

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).

查看更多
来,给爷笑一个
3楼-- · 2020-03-31 02:49

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.

查看更多
登录 后发表回答