Jump to content

Best JS trick I've ever learned - option objects for keyword arguments.


scheffgames
 Share

Recommended Posts

I've learned this one from Effective Javascript . Basically it says that instead passing a long list of arguments to function just pass an options (opts) object. This way even if you'll add additional args in the future everything will be kept neat and clean and the user (you or others) won't have to guess what each arg is intended for.

This may seem like an overkill for simple functions but imagine functions that take 5, 10 or even more arguments. You could say that it is bad practice but you'll have a function like this in your program one time or another. 

Or you could use named arguments but they do have to be in a specific order (last in args list). 

And using the opts object has it's advantages - you can pass args in whatever order you like and omit as many as you like and the function will still probably work - because you'll have implemented an arg check section that sets them to defaults - you'll probably do it anyway with normal arguments.

So hands down this is the trick that is making my code waaayyy more usable, clean and easy to use.

Let's see it in action - I'll be using examples from the book:

Here's a function without an option object with lots of args (USAGE):

var alert = new Alert(100, 75, 300, 200,
"Error", message,
"blue", "white", "black",
"error", true);

Here's the same function with an option object (USAGE) - note that you can input them in any order you want or miss them completely:

var alert = new Alert({
x: 100, y: 75,
width: 300, height: 200,
title: "Error", message: message,
titleColor: "blue", bgColor: "white", textColor: "black",
icon: "error", modal: true
});

And here's the implementation of the function with the option object:

function Alert(parent, message, opts) {
opts = opts || {}; // default to an empty options object
this.width = opts.width === undefined ? 320 : opts.width;
this.height = opts.height === undefined
? 240
: opts.height;
this.x = opts.x === undefined
? (parent.width / 2) - (this.width / 2)
: opts.x;
this.y = opts.y === undefined
? (parent.height / 2) - (this.height / 2)
: opts.y;
this.title = opts.title || "Alert";
this.titleColor = opts.titleColor || "gray";
this.bgColor = opts.bgColor || "white";
this.textColor = opts.textColor || "black";
this.icon = opts.icon || "info";
this.modal = !!opts.modal;
this.message = message;
}

It may be a little bit more work to do for the arg check but you're going to use it a lot more times than write it. And it forces you to do arg check and provide  defaults (which is a very,very good thing). And it's more readable and "writeable" IMO.

 

Link to comment
Share on other sites

function Alert({width = 320, height = 240, title = "Alert", titleColor= "gray"}) {
  this.width = width;
  this.height = height;
  this.title = title;
  this.titleColor = titleColor;
}

As usual, ES6 to the rescue! (this, along with native modules, and arrow functions make javascript beautiful, IMHO.

Link to comment
Share on other sites

Some more info on single arity functions.

tl;dr from the article

1. Easy maintenance and optional new

2. Named parameters

3. Better management of optional parameters

4. JSON deserialization for free

5. Idempotency

6. Avoid boilerplate

I definitely agree with the pattern, but you should try to avoid functions with too many args or objects with too many config options (encourages super objects, which isn't usually a good idea).

It falls down for currying functions though, but, its another tool in your toolkit, use as an when necessary.

 

Link to comment
Share on other sites

10 hours ago, mattstyles said:

currying functions

I'm still trying to find some use for currying in my day to day game development but nope, nada. I have the feeling that it will make my code less readable and flexible. Can you provide a practical day to day example of currying, something that you use daily in your game dev code?

Link to comment
Share on other sites

11 hours ago, scheffgames said:

Can you provide a practical day to day example of currying, something that you use daily in your game dev code?

Any sort of data transformation and it can be very useful, which helps you keep minimal representations of objects and transform them on the fly when you need to, what you absolutely don't want is two slightly different versions of basically the same object because then you have to maintain synchronicity between those two objects, better to have one object and two transforms of that object that can be applied as and when needed (only scrapping this when/if it becomes too expensive, which is rare).

return compose(
  mapValues(translatePrice(currencyCode)),
  pick(priceKeys)
)(pricing)

This is an example from my current project, the compose, pick and mapValues function are all from lodash/fp, what it does is create a composable function that can be passed an object and spits out an array of strings, the pricing object contains more data than I require in this case so I run it through a function that performs the transform I need, in this case I have called the composed function immediately but I have a whole bunch of generic functions (such as translating the currencyCode into a different string) that are then composed together which provides excellent reuse.

Here is the translatePrice function:

export const translatePrice = code => price =>
  `${getCurrencySymbol(code)}${format(price)}`

Very simple but due to the currying lets me tack on a code and then run the resultant function through a mapper to do the actual work.

This pattern is highly scalable and results in the wonderful scenario where a huge chunk of your codebase are low level small utility functions that are easily testable and easily composable into more specific functions.

Lodash actually has a function that works based on arity allowing you to use either:

translatePrice(code, price)
translatePrice(code)(price)

Most other functional libraries will contain a similar function (i.e. ramda, monet, bacon etc etc).

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...