Search code examples
javascripttypeof

Need help understanding how prototype propagates in JavaScript


In my code below, I try to easily check "typeof" a given variable, assuming it's already declared. The native typeof check is not very satisfying when Array returns as "object." My code mostly works as expected: 'this is a string'.typeof() yields 'String'; and Object.prototype.typeof('this is a string') yields 'String'; but when I tried NaN.typeof(), it yields 'Number', but Object.prototype.typeof(NaN) yields 'NaN' as expected. Unlikely that this difference would be useful for me in the future, but I just want to understand why the different results. Your insights are very much appreciated. Thank you.

if (!Object.prototype.hasOwnProperty('stringify')) {
    Object.defineProperties(Object.prototype, {
        stringify: {
            value: function () { 
                if (arguments.length >= 2) return JSON.stringify(this, ...arguments); //this means replacer is used
                return JSON.stringify(this, null, ...arguments) } //when replacer ( function, array) is skipped.
        },
        getType: { value: function(target) {
                if (arguments.length === 0) target = this; //assuming no error, variable.typeof() will yield the target. To avoid error use Object.prototype.typeof(target);
                if (typeof target === 'number' && Number.isNaN(target)) return 'NaN';//NaN.typeof() will return "number" even if it's not a number
                if (typeof target !== 'object') return typeof target; //when typeof returns a primitive (Function, String, Number, BigInt, Symbol, Boolean, undefined); 
                const return_value = Object.prototype.toString.call(target);
                // we can also use regex to do this...
                const type = return_value.substring(
                        return_value.indexOf(" ") + 1, 
                        return_value.indexOf("]"));
                return type.toLowerCase();
            }
        },
        typeof: {value: function(target) { //this should be same as getType() above. weird that NaN.typeof() still  yields 'Number' but Object.prototype.typeof(NaN) yields 'NaN'
            if (arguments.length === 0) target = this; //in case target is of 'null' value;
            return target === undefined ? 'undefined' : target === null ? 'null' : Object.is(NaN, target)? 'NaN' : target.constructor.name;
        }},
        isTypeof: {value: function(type, val) {
            return ([undefined, null].includes(val) && val === type) || Object.is(NaN, val) || val.constructor.name === type;
        }},
        is: {value: function() {
            if (arguments.length === 1) {
                let target = this;
                return Object.prototype.isTypeof(arguments[0], target);
            } else if (arguments.length === 2) {
                return Object.prototype.isTypeof(...arguments);
            }
        }}
    });
}

console.log(Object.prototype.typeof(NaN)) //gets 'NaN' as expected.
console.log(NaN.typeof()); //expecting 'NaN' not 'Number'

see explanation above.


Solution

  • This happens because you run your script in sloppy mode and not in strict mode. In sloppy mode, this is always an object even when the relevant value (like NaN) was a primitive, while in strict mode it can be a primitive value without getting wrapped into an object. In your case, Object.is(NaN, this) can never be true if you evaluate it in sloppy mode.

    So just add "use strict" either as the first line inside each function that needs this to be a primitive when relevant, or at the global level on the top of your script.

    For instance:

            typeof: {value: function(target) { 
                "use strict"
                if (arguments.length === 0) target = this;
                return target === undefined   ? 'undefined' 
                     : target === null        ? 'null' 
                     : Object.is(NaN, target) ? 'NaN' 
                     : target.constructor.name;
            }},