1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
// 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);
|