Jacob Ruiz

View Original

Mastering Javascript Fundamentals: AccountingJS Internal Helper Methods (Part 2): defaults

Get the fundamentals down and the level of everything you do will rise. - Michael Jordan

As stated in my original post, I do 1 hour of video lessons from Watch and Code every day. If you're interested in learning Javascript in a way that goes beyond basic tutorials and gives you a foundational, practical knowledge without relying on frameworks - I'd highly recommend it. If you're reading these posts, please keep in mind that these are just my notes, and I'm not an expert (yet!). If your goal is also to master the fundamentals of Javascript, please head over to Watch and Code and start your journey there!

All screenshots were annotated using Shotty.


defaults

This function extends an object with a defaults object, similar to underscore's _.defaults. It's used for abstracting parameter handling from API methods.

So what the hell does that mean?

Let's take a step back and think about defaults conceptually.

Imagine you have a defaultCar object. Most properties of the car will be pretty standard. You may want to customize something like color, but you probably don't need to customize the number of wheels:

See this content in the original post

So if I wanted to define my own car and tell the dealership what I want, I would probably only specify the color, and the jacobCar object would get its other properties (like number of wheels) by default:

See this content in the original post

This is the dynamic we're dealing with when we're talking about defaults. Defaults make it so you don't have to specify certain things.

In the console we can use the defaults function from AccountingJS to give jacobCar the default values we want from defaultCar:

See this content in the original post

Because the defaults function enumerates through the properties in the defaults object (the second argument), and looks to see if the same property exists on the object (the first argument). If that property doesn't exist (is null or undefined) on the object, then it creates a new one of the same name and value as the defaults object. Finally, it returns the object with the new defaults applied. Here's the code:

See this content in the original post

Let's go line by line.

See this content in the original post

👆Here we start by declaring a key variable, which we use later on in the function.

See this content in the original post

👆Here we have some interesting lines of code. These lines check the corresponding boolean values of the two objects passed in as arguments (object, defs). If the corresponding value is true, it leaves the object alone. But if it's false (undefined, zero, or null), then it will set it to an empty object.

It's unclear why this code is here. It seems it's trying to deal with the case where someone passes in something unexpected and this is a way to recover. Maybe the person calling defaults() fails to pass in anything at all.

Next we have the meat of the function. This is where the action happens:

See this content in the original post

👆Here we're going through every property in defs which is the second argument (the default object). In our example case, it would be going through the color, wheels, and tires properties.

Then, in the line:

See this content in the original post

👆We're saying: If object doesn't have this property (key) on it, then use the value from the defs object.

What is hasOwnProperty? Our if statement uses it like this:

See this content in the original post

👆This line lets you enumerate through the objects own properties, and not properties than are on the object's prototype. For example, imagine myDog has only one property called name, but the dog prototype object has a method called bark. If we were to enumerate through properties in myDog and console.log each one we would see both name and bark:

See this content in the original post

We can modify this easily by adding an if statement:

See this content in the original post

By putting the console.log inside of the if statement, we guarantee that it only prints myDog's own properties.

Looking back at the defaults function: 

See this content in the original post

Let's look specifically at the line where we apply values from the defs object to the object.

See this content in the original post

👆This isn't the best code. First, it's using ==. Javascript will try to change the types of the two sides of the ==. Better to use === so we have a straight comparison without transforming the left and right sides.

The second problem is that the comment is misleading. It says that the code will "Replace values with defaults only if undefined (allow empty/zero values)". This isn't exactly true.

Let's look at a case where this breaks down. Imagine we haven't decided on our car color, so we set it to null. What we'd want is for the defaults function to extend the first object, but instead it overwrites our color value:

See this content in the original post

It ignores the fact that we wanted the color to be null.

What we could do to fix this is:

  • Don't write this in one line (this is a style thing).
  • Use === instead of ==
  • Use undefined instead of null

So this was our old code:

See this content in the original post

And this is our new code:

See this content in the original post

Now if we set a property's value to be null it won't be overwritten with defaults, and it has the added benefit of our code actually behaving as the comment suggests.

Summary

  • The defaults() function allows you to pass in two objects and apply the properties from the second object to the first one where those properties don't already exist on the first object.
  • It does this by enumerating through each property on the second object and checking to see if that property exists on the first object. If the property doesn't exist, it applies that property to the first object.
  • This is a way to only need to define "custom" properties when creating a new object, and then setting all the "standard" options to the defaults set in another "default" object.
See this form in the original post