Find me on medium
1. Use factory functions
If you don’t know what a factory function is, it’s simply a function (that isn’t a class or constructor) that returns an object. This simple concept allows us to take advantage of JavaScript and its features to create powerful robust applications.
It’s important to know that they’re no longer factory functions when they’re called with the new
keyword.
Why factory functions?
Factory functions can be used to easily produce instances of objects without having anything to do with classes or the new keyword.
What it essentially means is that they ultimately become treated as just functions, which means they can be used to compose objects, functions, and even promises. This means you can mix and match factory functions together to create an enhanced factory function, then continue composing with other functions or objects to create even further enhanced ones. The possibilities are endless.
When we take that into consideration and combine it with good code practices, it really begins to shine.
Here is a simple example of a factory function:
function createFrog(name) {
const children = []
return {
addChild(frog) {
children.push(frog)
},
}
}
const mikeTheFrog = createFrog('mike')
When you’ve used factory functions enough, you begin to realize that compared to its class constructor counterpart, it promotes stronger reusability. This results in less code, an easier time refactoring since factory functions ultimately return arbitrary objects, and an easier time managing one code to another.
2. Add methods on the .prototype when writing constructors
If you’re new to JavaScript, this section might be a little new to you as it was for me for the first two years of my experience with JavaScript.
(Keep in mind that this does not apply to classes because classes already attach methods onto their prototypes.)
Here’s an example of a constructor:
function Frog(name, gender) {
this.name = name
this.gender = gender
}
Frog.prototype.leap = function(feet) {
console.log(`Leaping ${feet}ft into the air`)
}
Why do this instead of directly attaching the leap method, like in the example below?
function Frog(name, gender) {
this.name = name
this.gender = gender
this.leap = function(feet) {
console.log(`Leaping ${feet}ft into the air`)
}
}
When we attach methods directly on the prototype, they get shared among all instances created by the constructor.
In other words, using the last example, if we created three separate Frogs (from this.leap = function() {...}
), then we end up creating three separate copies. This is a problem because the leap method will always stay the same and doesn't need to have its own copy to its instance.
Ultimately, this results in lower performance, when it could have been avoided. The this.name and this.gender properties need to be defined on the instance because in real life, frogs probably have their own names and gender so it makes sense to have them created on the instance level.
Here's an example of this approach used by the popular request package.
3. Use the conventional .type property when differentiating
This practice works so well that it’s in extensive use today. If you’re a React developer, you’ve probably already been seeing this every day, especially when you’ve been working with Redux.
Using similar approaches also makes it extremely easy for you in your development flow since it even documents itself extremely well:
function createSpecies(type, name, gender) {
if (type === 'frog') {
return createFrog(name, gender)
} else if (type === 'human') {
return createHuman(name, gender)
} else if (type == undefined) {
throw new Error('Cannot create a species with an unknown type')
}
}
const myNewFrog = createSpecies('frog', 'sally', 'female')
4. Use TypeScript
TypeScript has become widely adopted in the JavaScript community due to its ability to provide a strong defense for type safety as well as its ability to help us catch bugs before they even occur.
Using TypeScript will enable your compiler to detect and show warnings about any potential errors in code before the code even runs.
But that’s not even close to a complete list of reasons why adopting TypeScript is good for any situation. One of the best things about TypeScript is that it allows you to use new features in JavaScript before they’re supported by major browsers since they get compiled down to earlier versions of JavaScript, ultimately being able to run in old browsers.
5. Write tests
If you’re working on a project and plan to get serious with it, it’s almost a must to employ tests so that your app becomes more predictable, less error-prone, and resilient to changes in the future. In other words, if you plan to make your project future-proof, there’s no better way than to establish tests throughout your code. The more tests you put in your code, the more confidence you’ll have when it gets deployed in production to the real world.
The best part about tests? Just the fact that that they can help you catch bugs before bugs even get a chance to occur — wouldn’t anyone want that ability? I’m certain I do! That’s why I write unit tests in my projects.
You can get started with the types of tools that are in use today by reading this post on testing frameworks and tools to get you started.
6. Keep functions as simple as possible
As we all know, in JavaScript it’s obviously possible to have huge functions that do more than one thing.
When you’re new to programming, it might have felt like a good thing — I know I felt extremely good about myself when I wrote large pieces of code that worked. It basically meant the most to me and gave me lots of confidence to see any of my code run without problems, let alone when it was humongous blocks of code that I wrote. Boy oh boy, was I extremely naive back then!
If you want to write code that’s more maintainable, simple, and less error-prone, it’s best to try to keep it as simple and small as possible. The simpler it is, the easier it is to test individually.
This is especially relevant if you’re more fond of the functional programming paradigm. It’s common knowledge that functions should do one thing and must do it well.
7. Always consider using try/catch
when using JSON.parse
or JSON.stringify
In JavaScript, when we pass JSON as input to the JSON.parse
method, it expects a properly formatted JSON as the first argument. If it's formatted incorrectly, it will throw a JSON parse error.
The danger coming from JSON parse errors is that receiving invalid JSON crashes your app. I’ve recently been in a situation at work where one of our web projects was failing because another internal package did not wrap a JSON.parse
in a try/catch. This ended up making a web page fail, and there was no way to get past this error unless the internal package fixed it. This happened because the JavaScript runtime was broken.
SyntaxError: Unexpected token } in JSON at position 107
You shouldn’t always expect valid JSON input as it can receive weird characters like the >
character, which is not uncommon today.
Conclusion
And that concludes this piece. I hope you found this to be valuable. Watch for more in the future!
Find me on medium
Top comments (11)
I normally use a function with try/catch inside it so that I don't need to always add try/catch blocks.
Hey! What good tips! Just a few comments about
Add methods on the .prototype when writing constructors
Even I'm not a huge fan of the
class
syntax sugar and the use ofclosures
to simulate OOP, I think it is better to go for theclass
one to attach to one standard and keep it clean instead of keeping modifying theprototype
.Keep functions as simple as possible
void
functions)function composition
Always consider using try/catch when using JSON.parse or JSON.stringify
Here depends a lot of your app structure, It is better if you don't use
try/catch
every time you use it but let the error goes bubbled up until you're able to handle it properly and show a message to the user or print it to the logs.These are actually good tips! I am a big fan of the factory functions myself and prefer them over classes. Good to see that one coming by for once. And I even learned something. I never knew why you would want to put functions on the
.prototype
, but your explanation makes sense. Definitely gonna revise some of my packages with factory functions.I am so glad to hear that! I was hoping I would grab some readers who were stuck on that prototype mystery!
Great article, just one thing. Isn't the first snippet supposed to be
addChild(name)
?Thank you! The intention of addChild's
frog
argument was that if the frog were to have children then it should have be instantiated withcreateFrog
right before being passed in the instance. So there's the parent frog who had a baby (and the baby frog should have been instantiated withcreateFrog
which is now also an instance of a frog sometime in our app before being passed as a child)So in the code it would look something like this:
or
Great summary! I would like to think that I'm perfect in all of these 7 practices, but I really need to start writing tests. I'll be sure to check out the link you provided. Thanks!
Your welcome!
Why type is not a switch I don't know, although I have never heard that this is a conversation. Nor that using prototype when a class keyword is more appropriate.
A switch here would be just fine since it just returns the result. However in a lot of situations when i'm considering switch cases I don't have to return the value evaluated from the case block which ultimately I'd become forced to put in a
break
which unnecessarily adds more lines than needed. In those situations it looks much nicer to just write if/else conditions while keeping the code short.I didn't know about (2). I'll definitely keep that one in mind. Thanks for sharing!