This is now an official project and will be eventually improved over there.
-
-
Save WebReflection/56d04ccb1e5b0e50c121 to your computer and use it in GitHub Desktop.
Following an attempt to make __proto__
and null objects work with Symbols too. It does work apaprently with everything but {__proto__: null}
literals since these won't pass through the setter.
(function (Object) {'use strict';
// (C) Andrea Giammarchi - Mit Style
if (Object.getOwnPropertySymbols) return;
var
hasConfigurableBug,
id = 0,
random = '' + Math.random(),
prefix = '__\x01symbol:',
prefixLength = prefix.length,
GOPN = 'getOwnPropertyNames',
create = Object.create,
$null = create(null),
gOPN = Object[GOPN],
gOPD = Object.getOwnPropertyDescriptor,
ObjectProto = Object.prototype,
defineProperty = Object.defineProperty,
descriptor = gOPD(Object, GOPN),
__proto__ = gOPD(ObjectProto, '__proto__'),
__$proto__ = {
configurable: true,
get: function () {
return gPO(this);
},
set: function (proto) {
sPO(this, proto === null ? $null : proto);
}
},
gPO = Object.getPrototypeOf || function (o) {
return __proto__.get.call(o);
},
sPO = Object.setPrototypeOf,
get = function get(){},
onlyNonSymbols = function (name) {
return name.slice(0, prefixLength) !== prefix;
},
onlySymbols = function (name) {
return name.slice(0, prefixLength) === prefix;
},
setAndGetSymbol = function (uid) {
var descriptor = {
enumerable: false,
configurable: true,
get: get,
set: function (value) {
if (hasConfigurableBug) {
delete ObjectProto[uid];
delete $null[uid];
}
defineProperty(this, uid, {
enumerable: false,
configurable: true,
writable: true,
value: value
});
if (hasConfigurableBug) {
defineProperty(ObjectProto, uid, descriptor);
defineProperty($null, uid, descriptor);
}
}
};
defineProperty(ObjectProto, uid, descriptor);
defineProperty($null, uid, descriptor);
return uid;
}
;
descriptor.value = function getOwnPropertyNames(o) {
return gOPN(o).filter(onlyNonSymbols);
};
defineProperty(Object, GOPN, descriptor);
descriptor.value = function getOwnPropertySymbols(o) {
return gOPN(o).filter(onlySymbols);
};
defineProperty(Object, 'getOwnPropertySymbols', descriptor);
function Symbol(description) {
if (this && this !== window) {
throw new TypeError('Symbol is not a constructor');
}
return setAndGetSymbol(
prefix.concat(description || '', random, ++id)
);
}
descriptor.value = Symbol;
defineProperty(window, 'Symbol', descriptor);
// defining `Symbol.for(key)`
descriptor.value = function (key) {
var uid = prefix.concat(prefix, key, random);
return uid in ObjectProto ? uid : setAndGetSymbol(uid);
};
defineProperty(Symbol, 'for', descriptor);
// defining `Symbol.keyFor(symbol)`
descriptor.value = function (symbol) {
return (
(prefix + prefix) === symbol.slice(0, prefixLength * 2) &&
-1 < gOPN(ObjectProto).indexOf(symbol)
) ?
symbol.slice(prefixLength * 2, -random.length) :
void 0
;
};
defineProperty(Symbol, 'keyFor', descriptor);
descriptor.value = function (proto, descriptors) {
return create.apply(
Object,
proto === null ?
(arguments.length === 2 ?
[$null, descriptors] :
[$null]) :
arguments
);
};
defineProperty(Object, 'create', descriptor);
if (!sPO) {
try {
// if it's not poisoned
__proto__.set.call({}, null);
sPO = function (o, p) {
__proto__.set.call(o, p);
};
} catch(_) {
sPO = function (o, p) {
defineProperty(ObjectProto, '__proto__', __proto__);
o.__proto__ = p;
defineProperty(ObjectProto, '__proto__', __$proto__);
};
}
}
defineProperty(ObjectProto, '__proto__', __$proto__);
try {
hasConfigurableBug = Object.create(
defineProperty(
{},
prefix,
{
get: function () {
return defineProperty(this, prefix, {value: false})[prefix];
}
}
)
)[prefix];
} catch(o_O) {
hasConfigurableBug = true;
}
}(Object));
So far the caveats I notice are that Symbol.for
/Symbol.keyFor
won't work cross-realm, and that typeof
won't report "symbol" for any value. Also, could the string version of the Symbol conflict with a shimmed Symbol created in another realm?
not sure even natively for
and keyFor
would work cross realm (need to test) but I think there's no much I can do there (and x-realm is rarely a real-world problem). Conflicts are extremely highly improbable and using a DateTime won't give me much more security so ... not sure that's a real concern.
typeof returns a primitive as specs say, and string
is the only primitive that makes sense.
Transpilers could wrap it as typeOf()
and verify it's a Symbol
and since Symbol() instanceof Symbol
is false, as well as Symbol() instanceof Object
I don't think returning an Object
would be a better option than returning a string.
If you hold a Symbol
you should not have problems though, and since the whole logic is based on the assumption in order to retrieve symbols you need to getOwnPropertySymbols()
I guess we can live with the current state.
That being said, {__proto__: null}
and null
objects are IMO a bigger, probably not worth fixing, gotcha ... but that plus all other concerns are the reason we cannot possibly have a shim
but only a sham
for ES6
The spec requires them to work cross-realm but I'm not sure how engines are doing wrt to that compliance.
Right, I think that i'll have to be a sham. Looks great so far though!
Also, possibly a more reliable test for https://gist.github.com/WebReflection/56d04ccb1e5b0e50c121#file-symbol-js-L5 is if (typeof Symbol === 'function' && typeof Symbol() === 'symbol')
?
I think if getOwnPropertySymbols
is there we have to assume somebody else either patched upfront or the browser must support Symbol
natively. I don't see how that check can improve a sham, since we risk to overwrite other attempts that might be there and work as intended for reasons.
TL;DR I wouldn't go for that check, it's prolix for no advantages.
Notes
Object.assign
should be polyfilled on top. Engines withObject.assign
support, already supportSymbol
too but those that don't should assign bothObject.getOwnPropertyNames
andObject.getOwnPropertySymbols
propertiesit might be necessary to feature-detect problematic engines same way I've done in lazyvalnull
objects should be supported, it's necessary to wrapObject.create
and try to interfer with__proto__
descriptor too so that allnull
objects will inherit from anull
object that will have all Symbol set in there too. Quite obtrusive, but it can work ( please see next comment for an almost full working solution )