4

I am trying to create a function which can be invoked through the following ways:

log() 
log.error()

Based on my understanding, we can creating such a function by writing on the function itself:

function log(){

}
log.error = () => {}

Is it not recommended writing on the function this way? Because JavaScript might release an update in the future which might hold the "error" property, then my "error" property will be overriding that JavaScript feature.


Here's an example of where people might be using this pattern and why it's useful.

For example, Jquery:

$( "#button-container button" ).on( "click", function( event ) {} )

and

$.ajax({  url: "/api/getWeather" });

you see this pattern in many popular libraries.

the $ is an object,

it's callable, so you can invoke it $('css query selector here') and it's chainable, so you can call other methods on it $().on(), and it has methods on it $.ajax()

starball
  • 20,030
  • 7
  • 43
  • 238
Normal
  • 1,616
  • 15
  • 39

3 Answers3

4

Is it not recommended writing on the function this way? Because JavaScript might release an update in the future which might hold the "error" property, then my "error" property will be overriding this JavaScript feature.

I'll take it as this being your primary query, since you've showed that you already know how to go about doing what you want to do.

This line of thinking is well-intentioned, but doesn't scale. Take for instance Object.prototype. If we applied this line of thinking everywhere, we'd have to apply it to Object.prototype. Every object by default has Object.prototype in its prototype chain, unless you Object.create(null, {...}). Do people worry every day that every single property with a generic name that they set on an object could one day overwrite a future standard property? Not that I know of.

You worry about your own choices clashing with future changes to the standard, but you should think about the other way around too: The ECMAScript standards committee wouldn't want to make a change that breaks the web (all the websites on the web that use JavaScript and have been making naming decisions for the past how many years of its existence). Go google "JavaScript smoosh" and read about the array.prototype.flatten fiasco.

A common phrase/rule in TC39 (ECMA-International's ECMAScript standards committee) is "don't break the web":

A change to JavaScript is considered "web compatible" if it preserves the current behavior of existing websites. If it changes the behavior of existing websites, it's considered to "break the web".

The definition here is a bit fuzzy and empirical--it's always possible to construct a website which will break under any particular change or addition to JavaScript, and the key is how common the broken websites are. If too many websites break, then web browsers will refuse to ship the change.

"Don't break the web" is a shared goal of TC39 and all web standards bodies: We aim to preserve web compatibility as we evolve the language. Even if a change would be convenient for developers, it's not worth it if we hurt lots of users in the process!

Example

There was an effort to add a method Array.prototype.contains. However, this broke many websites (reference). As a result, the method was named as Array.prototype.includes instead.

In the end, the decision of what you do is up to you. Because of "don't break the web", I think in general, you can rest easier when choosing to do things like this.

I don't want to say this as a hard rule though, and there's a bit of nuance: If you're just adding properties to object instances at the child-most-end of prototype chains and not to prototypes of standard classes, then it's not too big of a deal, but when it comes to adding things to standard prototypes, I personally wouldn't go about doing things like this all the time- only when I see that it would increase readability significantly and over a large part of my codebase.

You can choose to do things exactly the way you have proposed, in which case you can at least mitigate the problem by following proposals to the ECMAScript standard to see if such conflict-inducing changes are planned or being discussed, or you could take a less active approach and just do a runtime check to see if the Function prototype chain includes an error property in it, and emit an warning message in the console if so (but don't throw an exception- then your site would break! Just log a warning and keep chugging along). And have a plan for what to do if such a conflict occurs in the future.

Or you can choose an alternate design making a compromise between your ideal and what you think is likely to avoid future problems- ex- choosing a naming scheme that is unlikely to have conflicts with new additions to the ECMAScript standard.

From what I'm hearing, you want something that is usable / convenient to write and read, but also defensive against standards changes. But if you actually only care about being defensive, you can take different approaches like not using property method functions at all (and instead use free functions like "function myError(func) {/*...*/}"), or use Symbols instead.

Since you expressed curiosity in what JQuery is doing, based on what you have in your question post: typeof $ gives "function", Object.getPrototypeOf($).constructor === Function is true, and typeof $.ajax gives "function". As far as I know, what you have proposed is currently the only way to achieving exactly what you have described that you want in your question post.

For what it's worth, any time you want to fiddle with something that has to do with changing how an object or a class of objects behaves with respect to "lower-level" JS behaviour, your first instict should be to see if there's a standard Symbol property for it, such as Symbol.toPrimitive, , Symbol.iterator, and Symbol.species. I am not aware of such a symbol property existing for turning any object into a callable object. I.e. to make something callable, you currently have have a Function in its prototype chain.

To answer a follow-up question you posed in the comments:

can we think about achieving the same using classes? or do you think they're useless regarding this scenario?

This sounds like you are proposing a more specific case of Symbol properties (on a class (in its prototype) instead of any object). So by extension, the answer is no, I don't think they're useful here.

starball
  • 20,030
  • 7
  • 43
  • 238
2

Is it not recommended writing on the function this way?

No, it's fine. It is an established practice and used by many libraries, as you noted. It is very common to put properties right on function objects, and heck, classes even have dedicated syntax for adding static properties to them.

You're right of course that in theory there is a potential for conflict - however it is really small. Iff the language were to be extended by adding a new Function.prototype method, and iff you were using a third-party library (out of your control) that did use and rely on this new method, then you could not pass your log function with its overwritten property into that library. This is unlikely to be an issue though, and it could trivially be fixed by using something(x => log(x)) instead of something(log).

A name clash is even more unlikely since the names of your custom static methods will have to do with logging, not with functions, as new Function.prototype methods would. Only if you were extending the Function capabilities with more function-methods, say .partialApply, .curry, .fixArity or something, then it would be a bad idea since they have a higher potential for collisions.

Also it's rather unlikely that new Function.prototype methods get added to the language at all. While I only know of an official policy not to add new methods to Object.prototype, the case for Function.prototype would be quite similar - the TC39 knows about the customary ways to add arbitrary static properties to functions and would prevent additions that are likely to break the web or cause incompatibilities further down the road. And the push is actually in the opposite direction: new proposals like the bind operator, call-this or the pipe operator actually add more syntax for different ways to call a function without having to use any of their methods.

So in conclusion: No, you should not worry.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
-5

It's very unlikely that ECMA spec will add a .error property to function objects.

PeterM
  • 439
  • 2
  • 9