The "new" blunder in JavaScript
mayankav
Posted on July 30, 2021
No, I am not even talking about why JavaScript tries to replicate classical inheritance. As much as that is an easy target on JavaScript, lets leave that upto anti evangelists. Just pondering over the "new" keyword in isolation is what I aim for right now. Do we know all the possible ways of creating objects in JavaScript? Assuming that we do, two of the four ways available to us, make use of the "new" operator. The first one being constructor functions and yes you guessed it, ES6 classes.
When I talk of classes, I am somehow driven by my conscience to talk about all the problems classical inheritance brings along but for now I will hold on to the "new" keyword. The question you should ask is, why did we feel the need to have "new", when we could actually use object literals in the first place? I mean, there must be some benefit of saying new SomeFunction() over your old pal { }. Make a guess. Did someone say "creating similar objects"? So when you have a class "YourClass" with class fields "x" and "y" and then when you say "new YourClass(1,2)", you're assured that everytime you do a "new" object creation you will get a similar object, right? We can do that using object concatenation or factory functions but alright, not bad. What else? Maybe it also feels way simpler, no? So far so good. Smart people probably won't even talk about classes and constuctor functions, leave alone the "new" operator. Smart people will do whatever they want. I personally don't favor using classes but that only makes sense when everything is under my control. Being a part of some team, that's not always the case. We need to deal with code whether we like it or not. Assuming that the "new" operator makes it intuitive for you specially when you're coming from OOP, can you figure out the difference here?
new Date(); // Fri Jul 30 2021 20:08:55 GMT+0530 (India Standard Time)
new Date; // Fri Jul 30 2021 20:08:55 GMT+0530 (India Standard Time)
Date(); // "Fri Jul 30 2021 20:08:55 GMT+0530 (India Standard Time)"
Is Date a class, a constructor function or a factory function? If you don't know what a factory function is, its just another normal function that returns an object. So, if a function explicitly returns an object and is apparently not a constructor function, you can call it an object factory function. So what do you think Date in JavaScript is? I'll leave that on you to experiment with. If you can't reckon, think of how "new String()" & "String()" behave. The former gives you a new object whereas simply calling String(..) over some primitive does cast the value's type to string. The question is how do you define a function that can be safely called with and without the "new" operator? A factory function returns you the same object irrespective of whether you call it with or without a "new" operator. A constructor function on the other hand unless and until called with a "new" prefix returns undefined.
function factoryFn(x, y) {
const obj = {};
obj.x = x;
obj.y = y;
return obj;
}
function ConstructorFn(x, y) {
this.x = x;
this.y = y;
}
console.log(factoryFn(1, 2)); // {x:1, y:2}
console.log(new factoryFn(1, 2)); // {x:1, y:2}
console.log(ConstructorFn(1, 2)); // undefined
console.log(new ConstructorFn(1, 2)); // {x:1, y:2}
Try on Codepen
Now, I am kind of more interested in the constructor function. Notice that when you simply call your constructor function without the "new" keyword, it returns undefined? Visibly so because there's nothing to return. Interestingly, unless you're in strict mode, you've now also created properties "x" and "y" on the global object. I understand, there's hardly someone in the wild who'd instantiate a constructor function without "new". Anyway, we know how a constructor function otherwise implicitly returns "this" (an anonymous object created using the "new" keyword). What if I put a blunt return statement right inside the constuctor function? Take a look.
function ConstructorFn(x, y) {
this.x = x;
this.y = y;
return new String('a blunder');
}
function AnotherConstructorFn(x, y) {
this.x = x;
this.y = y;
return "not a blunder";
}
console.log(new ConstructorFn(1,2)); // "a blunder"
console.log(new AnotherConstructorFn(1,2)); // {x:1, y:2}
Try on Codepen
Uncannily, if you return an object or an array, it seems to block the implicit nature of the constructor function that returns the "this" object, upon being instantiated with the "new" operator whereas returning an atomic string makes no difference as such. How do you think it is possible to make a constructor function work safe without the "new" operator? Why would you even want to do that? Well, you may have your own reasons, I just want to prevent the users of my constructor function from mistakenly trying to invoke it without the "new" operator. I know you can simply use an ES6 class but for some reason I want to stick to the old functions style and yes I am not using the strict mode as well. Strict mode inside the function can alert you from creating implicit globals.
function StrictConstructor() {
if(this.constructor === StrictConstructor) {
this.x = 1;
this.y = 2;
} else {
throw new Error("StrictConstructor should only be instantiated with 'new' operator")
}
}
console.log(new StrictConstructor()); // {x:1, y:2}
StrictConstructor(); // Error
Try on Codepen
So the conditional filter we used to throw the error depends on how the "new" operator creates a new object and assigns it a constructor under the hood. If you want to get deep into this you should definitely go check out the MDN reference and then my last blog post. As a matter of fact, instead of throwing an error you can even return an object to eliminate the need of calling the function using "new" like so:
function StrictConstructor() {
if(this.constructor === StrictConstructor) {
this.x = 1;
this.y = 2;
} else {
return new StrictConstructor();
}
}
console.log(new StrictConstructor()); // {x:1, y:2}
console.log(StrictConstructor()); // {x:1, y:2}
Conclusion
For a while, if you forget about JavaScript, its not very intuitive to instantiate functions with the "new" operator. Probably that's why we name our constructor functions in PascalCase. Due to the fact that the "new" operator and constructor functions may behave eerie at times (specially when you forget the "new" operator), you can choose a combination of options from the available list to keep your code safe from surprises.
- An ES6 class shall help you spot when someone forgets the "new" keyword, by throwing an error
- Following the convention of naming constructor functions in Pascal.
- Placing a check within your constructor function to either throw an error on skipping the "new" operator or to silently fix the implicit behavior of the constructor function.
Originally Posted Here -
https://mayankav.webflow.io/blog/the-new-blunder-in-javascript
Posted on July 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
March 20, 2023