Closed LeaVerou closed 9 months ago
For Temporal we took our from
name from other factory methods of JS builtin objects, e.g. Array.from()
, Object.fromEntries()
, String.fromCharCode()
. Object.create()
is the oddball, I think.
IMO there are several categories of things here:
Constructors. These should be used for the "most general" creation of a class, i.e. the one that specifies the raw data. E.g. new Response()
or new DOMQuad()
.
Factory functions. These should be used for specialized shortcuts to creating an object, essentially sugar over the constructors. They should live as static methods on the constructor. E.g. Response.json()
or DOMQuad.fromRect()
.
Misplaced factory functions. These are mostly legacy from the bad old days when people didn't use constructors. They live on some other class. E.g. document.createXYZ()
or audioContext.createXYZ()
.
And then the async variants:
Async constructors. These are similar to constructors---they are the main entry point for creating a class---but because class instances must not be created in a sync way, we need to return a promise, and thus we cannot use new
. There is no clear guidance for this that I know of, and not very many examples; createImageBitmap()
might be the only one. Guidance would be good as I feel this has come up in the past. I kind of like X.create()
, but I haven't thought about this too much. I think it's OK for the namespace here to overlap with factory functions, personally.
Async factory functions. Similar to sync factory functions, but even fewer examples. CropTarget.fromElement()
might fit in here, or might be an async constructor, not sure.
Guidance here should probably be integrated with https://w3ctag.github.io/design-principles/#constructors .
In addition to the categories that @domenic mentioned, Ecma262 JavaScript also has some “coercion” functions that are constructors being called as if they were ordinary functions, without new
. For example, Integer(v)
.
The iterator-helpers proposal also uses this pattern: Iterator
is a constructor, but Iterator(o)
(an ordinary function call) attempts to “coerce” the given input into an Iterator object.
Should it not be forbidden to use the same name of an instance method (Response.prototype.json
) and static methods (Response.json
)?
Take for example the MDN Response document. They just omit the .prototype
part of the names, so they could not acutally create two pages with their current URL scheme.
MDN is working on fixing their unfortunate URL-scheme choices from many years ago, and such unfortunate choices should not constrain web platform API design.
Agreed with @domenic. Also, naming static and instance methods the same can actually be a very good practice in certain cases where a method does the same thing, the instance one operating on the instance itself, and the static one on the first parameter (though I can't think of an example of that off the top of my head).
The original PR (#471) included this sentence:
An example of valid usage of a factory method is when an object is being created it also requires association with the parent object.
document.createXXX()
is an example of this.
I think this is actually an antipattern that we should actively discourage. This should have been Element.create({ document })
, because:
document.createXXX()
could return anything.Furthermore, the way this is phrased is inaccurate: for DOM elements the parent object is element.parentNode
, not document
. Which highlights another problem with this pattern.
OTOH, things like CanvasRenderingContext2D#createXXX()
seem more reasonable (though static methods would also not have been terrible), so the guidance should probably be more nuanced than "never do this".
This was brought up in our discussion of
Response.json()
in https://github.com/w3ctag/design-reviews/issues/741We should have guidelines for naming factory methods. Currently the web platform is pretty inconsistent:
document.createXXX()
for creating nodes, alsodocument.createRange()
,document.createTreeWalker()
etcdocument.initXXXEvent()
factory methodsfrom()
methodscreateImageBitmap()
which is oddly placed in the global scope (?!)Object.create()
CanvasRenderingContext2D#createXXX()
Array.from()
Object.fromEntries()
String.fromCharCode()
audioContext.createXYZ()
Response.json()
DOMQuad.fromRect()
Perhaps we should have guidance that factory methods should start with
create
orfrom
to make it obvious that they are factory methods? Not sure we should restrict it further, as these are not interchangeable.Though there are also things like
Date.parse()
, which are fine as-is…