4

I'm manipulating the prototype of Object so that I can add some extension methods.
I've found out that typeof operator always returns object in case the operand is this:

Object.prototype.logType = function () { console.log(typeof this); }
"Hello".logType() 

output of code above is object instead of string. I know that in JavaScript everything is indeed an object, However I need to know the exact type of the value of this. How can I achieve that?

Shahryar Saljoughi
  • 2,599
  • 22
  • 41
  • 1
    Duplicate of [Messing with this in prototype's method](/q/20432009/4642212): simply put your code in [strict mode](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode). This is also mentioned in [How does the “this” keyword work?](/q/3127429/4642212). – Sebastian Simon Jun 20 '21 at 10:46

2 Answers2

7

When you call a method on a primitive, JS automatically wraps that primitive in its associative object wrapper, so:

"Hello".logType()  

becomes:

new String("Hello").logType() 

hence, this inside of your logType function refers to the wrapped primitive value, giving you an object. You can call .valueOf() to grab the primitive value wrapped in the object:

Object.prototype.logType = function () { console.log(typeof this.valueOf()); }

"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object

Or, you can use strict mode as suggested in the comments, as that keeps this as the original primitve:

Object.prototype.logType = function () { "use strict"; console.log(typeof this); }

"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • 4
    you could also use `this.constructor.name` - which will also work for your own `class`es (`.valueOf()` shows `object`) – Jaromanda X Jun 20 '21 at 07:14
  • 2
    This will incorrectly return `true` though if I use `(new class String {}).logType()` which is not a string. Better use `this.constructor === String`. – CherryDT Jun 20 '21 at 07:16
  • "return true" ... there is no true returned anywhere @CherryDT – Jaromanda X Jun 20 '21 at 07:20
  • @JaromandaX nice, yeah, that might be a better option if OP wants to get the constructor names instead of the data types – Nick Parsons Jun 20 '21 at 07:48
  • Yeah sorry, I extrapolated that with `=== 'String'` in my mind, since that's the only logical thing you can do with it in this case – CherryDT Jun 20 '21 at 07:49
  • 1
    `\`${typeof this.valueOf() === this.constructor.name.toLowerCase()?'#':''}${this.constructor.name}\`` - will output `#String` for actual Strings, and just `String` for class String ... or `XXX` for class XXX etc - if you want that level of granularity – Jaromanda X Jun 20 '21 at 07:51
  • This is probably beyond what anyone would ever encounter but... `new class String { valueOf () { return "abc" } }` << oops - I guess the closest you can get would be `${typeof this.valueOf() === this.constructor.name.toLowerCase() && this.constructor === globalThis[this.constructor.name]?'#':''}${this.constructor.name}` – CherryDT Jun 20 '21 at 08:48
  • @CherryDT hm yeah, you're right about that. Nice addition checking the global object for the constructor, wouldn't have thought of doing that myself – Nick Parsons Jun 20 '21 at 10:54
  • 1
    ...wait strict mode also changes this behavior... so all this is useless and we should just enable strict mode in the place where the prototype function is defined – CherryDT Jun 20 '21 at 11:06
4

When passed as this outside of strict mode, you have the very rare case that you encounter a primitive in their wrapped object form ("boxed"), as instance of String:

enter image description here

You can therefore check if your method was called on a string using this instanceof String instead of typeof this === 'string'. If you want to differentiate between strings and objects that inherit from String, you could use this.constructor === String instead.

To get a "regular" string back (or number for Number, or boolean from Boolean, etc.), you can call this.valueOf()

(This means you could also write typeof this.valueOf() - but note that this may be misleading because any object could return, say, a string from its valueOf method without actually having been a string originally.)

Note: You cannot differentiate between 'abc'.yourMethod() and new String('abc').yourMethod() this way because you get an instance of String either way.

(Also interesting: Seems like for newer types like BigInt or Symbol you cannot manually create such a wrapper instance, new BigInt(1n) will fail. But (function () { return this }).call(1n) would achieve the same, although I have no idea why anybody would want that...)


All that said: The easiest way to get the exact behavior you want (this being the actual string) is by defining your function in a context that is in strict mode:

(function {
  'use strict'
  Object.prototype.logType = function () { console.log(typeof this); }
})()

Now it will work as intended.

CherryDT
  • 25,571
  • 5
  • 49
  • 74