Two ways to do it, via Symbol
const Primitive = (() => {
const secret = Symbol();
const update = (obj, value) => {
Object.defineProperty(obj, secret, {configurable: true, value});
};
function Primitive(value) {'use strict';
update(this, value);
}
Primitive.prototype = new Proxy(
Primitive.prototype,
{
get(target, property, receiver) {
const value = receiver[secret][property];
return Object.defineProperty(
receiver,
property,
typeof value === 'function' ?
{value() { return value.apply(receiver[secret], arguments); }} :
{get() { return receiver[secret][property]; }}
)[property];
}
}
);
return Primitive;
})();
or via WeakMap
const Primitive = (() => {
const wm = new WeakMap;
function Primitive(value) {'use strict';
wm.set(this, value);
}
Primitive.prototype = new Proxy(
Primitive.prototype,
{
get(target, property, receiver) {
const value = wm.get(receiver)[property];
return Object.defineProperty(
receiver,
property,
typeof value === 'function' ?
{value() { return value.apply(wm.get(receiver), arguments); }} :
{get() { return wm.get(receiver)[property]; }}
)[property];
}
}
);
return Primitive;
})();
The code could be tested as such:
// test
var s = new Primitive('abc');
s == 'abc'; // true
[...s] // ["a", "b", "c"]
s.length; // 3
s.toString(); // "abc"
With the Symbol
version it is possible, internally, to update the value via update(s, "def")
.
With WeakMap
version, you can update via wm.set(s, "def");
.
All string operations will work out of the box but you are free to silently change the value at distance.