Open domenic opened 9 years ago
See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26986 which would make this more clearly a stupid idea from an IDL perspective.
@travisleithead points out that exotic object is defined in ECMA-262.
Reference an ecmascript definition. https://ecma-international.org/ecma-262/6.0/#sec-exotic-object
Something should go in section 2.
In IDL we have since renamed the objects so-defined "legacy platform objects"
Also, please use https://tc39.github.io/ecma262/.
Need to figure out how to relate the various -like
constructs as well, e.g., maplike
and setlike
. How does the guidance for these differ from the general guidance around legacy platform objects? Presumably it's different guidance...
@slightlyoff also noted a couple of additional references:
Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?
@plinss and I looked at this today during a breakout and we were not sure we understand exactly what is proposed to be an antipattern here. My understanding was that every API that uses keys that depend on current state is an antipattern according to the proposed principle, but the actual ES definition of exotic objects is more restricted.
If my understanding is correct, this would render APIs like the following antipatterns:
localStorage
CSSStyleDeclaration
objects, including element.style
element.dataset
element.attributes
Is that the case?
My understanding was that every API that uses keys that depend on current state is an antipattern according to the proposed principle, but the actual ES definition of exotic objects is more restricted.
The ES definition is actually more expansive.
The only way to get object properties ("keys") which depend on the current state is by overriding the [[OwnPropertyKeys]]() internal method. And, any overridden internal method makes an object exotic by the ES definition. So, objects with custom keys are just one type of exotic object.
In practice on the web platform the two categories you identify are mostly equivalent, because as long as you define your objects using Web IDL, the primary way to make them exotic is by using indexed/named getters/setters/deleters. I.e., making their properties dynamic. This will make them into legacy platform objects.
A final type of exotic object that you can create using Web IDL is the observable array exotic object. This was very carefully designed to implement best practices for exotic objects, essentially making it behave as much like an Array (which is another type of exotic object) as possible. People should be able to use observable arrays going forward, and thus avoid using indexed getters/setters to create their own new types of exotic objects.
If my understanding is correct, this would render APIs like the following antipatterns:
Yes.
An important point on CSStyleDeclaration
, though. It is not exotic because of the various properties like fontSize
, marginTop
, etc. Those are defined as normal properties. There are a lot of them, but that's fine: it's a finite list, determined at "compile time", not at runtime. It is exotic instead because of the indexed getter, declaration[i]
, which returns the _i_th property name.
Perhaps relatedly, I saw in your recent post you cited color.lch.l
as exotic. I'm not sure how that's implemented in your library, or if I've understood the example, but if I were to implement that as a web platform API, I think it would be doable without exotic objects. You'd have a finite (determined ahead of time) set of properties, such as color.lch
and color.rgb
and so on, each of which was an instance of a specific class, such as LCHColor
and RGBColor
, each of which have a finite (determined ahead of time) set of properties, such as l
, c
, h
, and r
, g
, b
. Nothing exotic needed; just a bunch of normal classes with normal properties.
Thank you for the clarifications. Are there any other DOM APIs that this would render antipatterns?
Perhaps relatedly, I saw in your recent post you cited
color.lch.l
as exotic. I'm not sure how that's implemented in your library, or if I've understood the example, but if I were to implement that as a web platform API, I think it would be doable without exotic objects. You'd have a finite (determined ahead of time) set of properties, such ascolor.lch
andcolor.rgb
and so on, each of which was an instance of a specific class, such asLCHColor
andRGBColor
, each of which have a finite (determined ahead of time) set of properties, such asl
,c
,h
, andr
,g
,b
. Nothing exotic needed; just a bunch of normal classes with normal properties.
Definitely related, as we tried to avoid named properties in the Color API exactly because of this proposed principle, but there is a significant API ergonomics difference between color.get("l")
and color.l
.
No, a predefined set of getters would not work, as you can register new color spaces at runtime. That is also why we don't want to have Color subclasses like that.
Did you see this question btw?:
Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?
Are there any other DOM APIs that this would render antipatterns?
Yes, anything that uses getter
or setter
annotations. The best way to find this that I'm familiar with is code-searching your favorite browser's IDL files, e.g. here are the results for Chromium.
Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?
In general I would say they are. They conflate the interface of a class with its data. Besides that being a confusing problem and an issue for theoretical purity, it has practical issues. E.g. with localStorage
, what happens if you try to store an item named getItem
? Then is localStorage.getItem
a function, or is it the stored item? Similarly with HTMLCollection
and nodes with id="length"
. (There are well-defined answers to these questions, but the fact that they have to be asked is a sign of bad design.) Even if you think your class has very few parts of its interface, you're still stepping on the basic parts of the interface that all JavaScript objects share, e.g. toString()
, hasOwnProperty()
, constructor
, etc. (So e.g. in your color example, I wonder what happens if someone defines a colorspace whose components are (toString
, hasOwnProperty
, constructor
).)
For numeric-only cases, the reasoning is a bit different, which is basically that developers expect indexed collections to behave as much as possible like arrays. So developers (and web spec authors) should really just use arrays when possible. Sometimes they need extra functionality, usually to observe changes; to do this they need to use very carefully-constructed proxies (like ObservableArray<T>
does on the spec level).
I just wrote up https://code.google.com/p/chromium/issues/detail?id=503971#c6 and it'd probably be worth capturing here.