// ES6 7.1.1 ToPrimitive(input [, PreferredType]) specifies a new extension // point in the language. Objects can override the behavior of ToPrimitive // somewhat by supporting the method obj[@@toPrimitive](hint). // // (Rationale: ES5 had a [[DefaultValue]] internal method, overridden only by // Date objects. The change in ES6 is to make [[DefaultValue]] a plain old // method. This allowed ES6 to eliminate the [[DefaultValue]] internal method, // simplifying the meta-object protocol and thus proxies.) // obj[Symbol.toPrimitive]() is called whenever the ToPrimitive algorithm is invoked. var expectedThis, expectedHint; var obj = { [Symbol.toPrimitive](hint, ...rest) { assertEq(this, expectedThis); assertEq(hint, expectedHint); assertEq(rest.length, 0); return 2015; } }; expectedThis = obj; expectedHint = "string"; assertEq(String(obj), "2015"); expectedHint = "number"; assertEq(Number(obj), 2015); // It is called even through proxies. var proxy = new Proxy(obj, {}); expectedThis = proxy; expectedHint = "default"; assertEq("ES" + proxy, "ES2015"); // It is called even through additional proxies and the prototype chain. proxy = new Proxy(Object.create(proxy), {}); expectedThis = proxy; expectedHint = "default"; assertEq("ES" + (proxy + 1), "ES2016"); // It is not called if the operand is already a primitive. var ok = true; for (var constructor of [Boolean, Number, String, Symbol]) { constructor.prototype[Symbol.toPrimitive] = function () { ok = false; throw "FAIL"; }; } assertEq(Number(true), 1); assertEq(Number(77.7), 77.7); assertEq(Number("123"), 123); assertThrowsInstanceOf(() => Number(Symbol.iterator), TypeError); assertEq(String(true), "true"); assertEq(String(77.7), "77.7"); assertEq(String("123"), "123"); assertEq(String(Symbol.iterator), "Symbol(Symbol.iterator)"); assertEq(ok, true); // Converting a primitive symbol to another primitive type throws even if you // delete the @@toPrimitive method from Symbol.prototype. delete Symbol.prototype[Symbol.toPrimitive]; var sym = Symbol("ok"); assertThrowsInstanceOf(() => `${sym}`, TypeError); assertThrowsInstanceOf(() => Number(sym), TypeError); assertThrowsInstanceOf(() => "" + sym, TypeError); // However, having deleted that method, converting a Symbol wrapper object does // work: it calls Symbol.prototype.toString(). obj = Object(sym); assertEq(String(obj), "Symbol(ok)"); assertEq(`${obj}`, "Symbol(ok)"); // Deleting valueOf as well makes numeric conversion also call toString(). delete Symbol.prototype.valueOf; delete Object.prototype.valueOf; assertEq(Number(obj), NaN); Symbol.prototype.toString = function () { return "2060"; }; assertEq(Number(obj), 2060); // Deleting Date.prototype[Symbol.toPrimitive] changes the result of addition // involving Date objects. var d = new Date; assertEq(0 + d, 0 + d.toString()); delete Date.prototype[Symbol.toPrimitive]; assertEq(0 + d, 0 + d.valueOf()); // If @@toPrimitive, .toString, and .valueOf are all missing, we get a // particular sequence of property accesses, followed by a TypeError exception. var log = []; function doGet(target, propertyName, receiver) { log.push(propertyName); } var handler = new Proxy({}, { get(target, trapName, receiver) { if (trapName !== "get") throw `FAIL: system tried to access handler method: ${String(trapName)}`; return doGet; } }); proxy = new Proxy(Object.create(null), handler); assertThrowsInstanceOf(() => proxy == 0, TypeError); assertDeepEq(log, [Symbol.toPrimitive, "valueOf", "toString"]); reportCompare(0, 0);