Converting into a primitive

Converting a value into its primitive representation is not something we can do directly, but is done implicitly (that is, coercively) by the language in a number of different situations, such as when you try to use the abstract equality operator, ==, to compare a String, Number, or Symbol to a value that is an Object. The Object, in that scenario, will be converted into its primitive representation via an internal procedure called ToPrimitive, which in summary does the following:

  1. If object[Symbol.toPrimitive] exists, and when called it returns a primitive value, use that
  1. If object.valueOf exists, and it returns a primitive (non-Object), use its return value
  2. If object.toString exists, use its return value

We can see ToPrimitive in action if we attempt a comparison with ==:

function toPrimitive() { return 1; }
function valueOf() { return 2; }
function toString() { return 3; }

const one = { [Symbol.toPrimitive]: toPrimitive, valueOf, toString };
const two = { valueOf, toString };
const three = { toString };

1 == one; // => true
2 == two; // => true
3 == three; // => true

As you can see, if an object has all three methods ([Symbol.toPrimitive], valueOf, and toString), then [Symbol.toPrimitive] will be used. If it has just valueOf and toString, then valueOf will be used. And, of course, if there is only toString, then it will be used.

There is the possibility that 2 and 3 in that procedure will swap if ToPrimitive is called with a hint of String (meaning that it has been instructed to attempt to coerce to a String instead of any primitive). An example of such a case would be when you use a computed member access operator (object[something]), where if something is an object, it would be converted into a String via ToPrimitive with a hint of String, meaning toString() will be attempted before valueOf(). We can see this in action here:

const object = { foo: 123 };
const something = {
valueOf() { return 'baz'; },
toString() { return 'foo'; }
};

object[something]; // => 123

We have both toString and valueOf defined on something, but only toString is used to determine which property to access on object.

If we do not define our own methods, such as valueOf and toString, then the default methods available on the [[Prototype]] of whatever object we're using will be used instead. The primitive representation of an array, for example, is defined by Array.prototype.toString, which will simply join its elements together with a comma as a separator:

[1, 2, 3].toString(); // => "1,2,3"

All types have their own natively provided valueOf and toString methods, so if we wish to force the ToPrimitive internal procedure to use our own methods, then we'll need to override the native ones by supplying our object with its own methods directly or by inheriting from the [[Prototype]]. For example, if you wished to provide a custom array abstraction that had its own primitive conversion behavior, then you could implement it by extending the Array constructor:

class CustomArray extends Array {
toString() {
return this.join('|');
}
}

Then, you'd be able to rely on your CustomArray instances being handled in their own unique way by the ToPrimitive procedure:

String(new CustomArray(1, 2, 3));    // => 1|2|3
new CustomArray(1, 2, 3) == '1|2|3'; // => true

The coercive behaviors of all operators and native language constructs will vary. Any time you pass a value to a language construct or operator that is expecting a primitive (typically either a string or a number), it will likely be passed through ToPrimitive. As such, it's useful to know about this internal procedure. We'll refer to this section as well as we start to explore all of JavaScript's operators in detail.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.59.231