Home > Software engineering >  Converting objects to numbers - how it's being implemented under the hood?
Converting objects to numbers - how it's being implemented under the hood?

Time:01-07

I'm trying to figure out how the conversion from Object to Number is being implemented under the hood. The abstract definition from the ES doc for converting objects into numbers and strings:

7.1.1.1 OrdinaryToPrimitive ( O, hint ) The abstract operation OrdinaryToPrimitive takes arguments O (an Object) and hint (string or number) and returns either a normal completion containing an ECMAScript language value or an abrupt completion. It performs the following steps when called:

  1. If hint is string, then:
  • Let methodNames be « "toString", "valueOf" ».
  1. Else:
  • Let methodNames be « "valueOf", "toString" ».
  1. For each element name of methodNames, do
  • Let method be ? Get(O, name).
  • If IsCallable(method) is true, then:
    • Let result be ? Call(method, O)
    • If Type(result) is not Object, return result.
  1. Throw a TypeError exception.

So, I tried to implement it in JavaScript:

function OrdinaryToPrimitive(O, hint) {
  let methodNames;
  if (typeof hint === "string") {
    methodNames = ["toString", "valueOf"];
  } else {
    methodNames = ["valueOf", "toString"];
  }
  for (let name of methodNames) {
    let method = O[name];
    if (typeof method === "function") {
      let result = method.call(O);
      if (typeof result !== "object") {
        return result;
      }
    }
  }
  throw new TypeError();
}

console.log(OrdinaryToPrimitive({}, "string")); //[object Object]
console.log(String({})); //[object Object]
console.log(OrdinaryToPrimitive({}, "number")); //[object Object]
console.log(Number({})); //NaN

The problem is that the implementation doesn't work like the built-in conversion. Can someone tell me what did I do wrong? I even have no idea why it should work on theory since we first get the value of the object which is the object itself and then convert it to string so the result is "[object Object]". Did I misunderstand the docs?

CodePudding user response:

You're missing the other steps that occur when Number() is used.

The object passed to Number({}) is first run through OrdinaryToPrimitive, where it returns the primitive string "[object Object]". But once that is done, the spec then passes this string value to ToNumber() which calls the abstract operation StringToNumber. The spec then attempts to parse the string value "[object Object]" based on the production StringNumericLiteral, which it cannot do successfully, and so StringToNumber returns NaN as the parsing steps contains errors.

CodePudding user response:

Something to illustrate @NickParsons' answer is to see what happens if you wrap a primitive in an object and run it through the same process; this time, it works as expected, since calling valueOf() on Object(123) yields the number 123. Pass that to Number and you get the number back.

function OrdinaryToPrimitive(O, hint) {
  let methodNames;
  if (typeof hint === "string") {
    methodNames = ["toString", "valueOf"];
  } else {
    methodNames = ["valueOf", "toString"];
  }
  for (let name of methodNames) {
    let method = O[name];
    if (typeof method === "function") {
      let result = method.call(O);
      if (typeof result !== "object") {
        return result;
      }
    }
  }
  throw new TypeError();
}

const str = new Object("string");
console.log(`str is an ${typeof str}`);
const num = new Object(123);
console.log(`num is an ${typeof num}`);
console.log(`num.valueOf() is a ${typeof num.valueOf()}`);
console.log(OrdinaryToPrimitive(str, "string")); //string
console.log(String(str)); //string
console.log(OrdinaryToPrimitive(num, "number")); //123
console.log(Number(num)); //123

  • Related