Home > Blockchain >  How does toString() only return a primitive values but ignores objects?
How does toString() only return a primitive values but ignores objects?

Time:02-20

This tutorial says that when Object.prototype.toString() gets overriden, it should only return a primitive value:

The toString() function you create must return a primitive, otherwise it will be ignored.

However, in the following example we return a object inside of toString() and it returns the object normally:

var ob2 = {
  val1: 100,
  val2: 200,
  toString: function() {
    return {
      veh: "meh"
    }; //object
  }
};

console.log(ob2.toString())

Output:

{veh: 'meh'} 

So the toString() returns the object normally. What is up with the misinformation?

CodePudding user response:

You’re right, the documentation was misleading and incomplete. I’ll submit a PR when I have time. I think what the author was referring to was the consequence of the OrdinaryToPrimitive abstract operation in the specification: when a value is coerced to a primitive, the two methods toString and valueOf (the methodNames) will be called in a specific order based on a type hint. And then:

  1. For each element name of methodNames, do
    1. Let method be ? Get(O, name).
    2. If IsCallable(method) is true, then
      1. Let result be ? Call(method, O).
      2. If Type(result) is not Object, return result.

This step is a loop, iterating over the list of method names. It takes the next method from the list of methodNames, checks if it is a function, calls it, and stores its result in result. Then it performs the type check. If it’s a primitive, i.e. not an object, this result is returned. Otherwise, the loop continues, effectively ignoring the result.

If the loop reaches the end without returning a value, a TypeError will be thrown.

In order to demonstrate this behavior, you have to have both methods:

class Test1 {
  toString() {
    return "1";
  }
  valueOf() {
    return "2";
  }
}

class Test2 {
  toString() {
    return {};
  }
  valueOf() {
    return "2";
  }
}

class Test3 {
  toString() {
    return "1";
  }
  valueOf() {
    return {};
  }
}

class Test4 {
  toString() {
    return {};
  }
  valueOf() {
    return {};
  }
}

const test1 = new Test1,
  test2 = new Test2,
  test3 = new Test3,
  test4 = new Test4;

console.log(String(test1)); // "1"; toString is preferred.
// `""   test1` and ` test1` also demonstrate this.
console.log(Number(test1)); // 2; valueOf is preferred.
console.log(String(test2)); // "2"; toString is ignored; valueOf is chosen instead.
console.log(Number(test2)); // 2; valueOf is preferred.
console.log(String(test3)); // "1"; toString is preferred.
console.log(Number(test3)); // 1; valueOf is ignored; toString is chosen instead.
console.log(String(test4)); // TypeError; none of the methods returns a primitive.
console.log(Number(test4)); // TypeError; none of the methods returns a primitive.

What the documentation didn’t refer to was explicitly calling the toString method, because that works just fine:

class Test {
  toString() {
    return {
      hello: "world"
    };
  }
}

const test = new Test;

console.log(test.toString()); // Logs { hello: "world" }.

  • Related