What is Javascript's `new` keyword doing under the hood?
Vince Campanale
Posted on June 20, 2017
Good morning, afternoon, evening, night. I have some things to share with you about the new
keyword in Javascript. Important things.
I'll start with some context and background about Constructor functions and the class
keyword. Then, I will explain exactly what the new
keyword is doing under the hood. Next, I will show how it does what it does by implementing it in code. Finally, I will explain why it does these things and give a couple arguments for avoiding this approach to Javascript object creation altogether in most situations. The information presented here comes from these resources and several others, processed by my brain.
Constructor functions ðŸ›
A Constructor function is a function that builds and returns a new instance of object. It looks like this:
/** Car: {
* doors: number,
* color: string,
* drive: Function
* }
*
* Car(doors: number, color: string) => Car
*/
function Car(doors=4, color='red') {
this.doors = doors;
this.color = color;
this.drive = () => console.log('Vroom!');
}
The capital letter at the beginning of the Constructor name is simply a convention adopted by Javascript programmers to separate Constructor functions from regular functions.
The way Constructor functions work under the hood might make for an interesting article, but I'll leave that for another day. Today is about new
.
The most important thing to take from this section is that the Constructor function, when invoked with the new
keyword, will return an object with a doors
property, a color
property, and a drive
method.
class
The class
keyword was introduced to Javascript with the ES2015 specification, commonly known as ES6, soon to be known as "just Javascript."
The class
keyword introduces nothing new (ha) -- it just provides some syntactic sugar for folks who like Java and semantic keywords. Nothing wrong with that.
Here's how you use it:
class Car {
constructor(doors=4, color='red') {
this.doors = doors;
this.color = color;
}
drive() { console.log('Vroom!'); }
// or drive = () => console.log('Vroom!');
}
Notice anything familiar?
I'll give you a hint:
console.log(typeof Car) // Function
Under the Hood 🚗
Whether you are using a vanilla Constructor function or a special keyword to instantiate your object constructing mechanism, you will be using new
to create new instances of the defined object. (There is another not-so-secret and powerful way to generate objects in Javascript called a factory function which will have to be covered in a future post).
So what is the new
keyword doing under the hood (in human words)?
Three letters, four actions. When you say var myCar = new Car()
, it...
1) Creates a new (empty) object
2) Gets the prototype of the constructor function (Car) and sets it as the empty object's prototype
3) Calls the constructor function with the new empty object as `this`
4) Returns the new object
What does this process look like in computer words?
Note: In order to reimplement new
we will have to pass in the constructor and it's arguments separately.
First, let's do it in ES5 because you only live once.
// new(constructor: Function, constructorArgs: Array<any>) => Object
function new2(constructor, constructorArgs) {
// Step 1: Create an empty object
var newObject = {};
// Step 2a: Get the prototype of the constructor function
var constructorPrototype = constructor.prototype;
// Step 2b: Set the empty object's prototype
Object.setPrototypeOf(newObject, constructorPrototype);
// Retro technique to turn arguments into an actual array
var argsArray = Array.prototype.slice.apply(arguments);
// Slice off first argument b/c that's the constructor function itself.
var realConstructorArgs = argsArray.slice(1);
// Step 3: Invoke constructor with newObject as 'this'
constructor.apply(newObject, realConstructorArgs);
// Step 4: Return the new object :)
return newObject;
}
Now that we have a working implementation, we can clean it up and make use of some new tools from ES6.
// new(constructor: Function, constructorArgs: Array<any>) => Object
function new2(constructor, ...constructorArgs) {
const newObject = {};
Object.setPrototypeOf(newObject, constructor.prototype);
constructor.apply(newObject, constructorArgs);
return newObject;
}
And...
const myCar = new2(Car, 4, 'blue');
console.log(myCar) // { doors: 4, color: 'blue', drive: [Function] }
myCar.drive() // Vroom!
But wait, there is an edge case. If the constructor function itself returns a new object, like this...
function Car(doors, color) {
this.doors = doors;
this.color = color;
this.drive = () => console.log('Vroom!');
return {
doors,
color
}
}
we should just return that object directly:
// new(constructor: Function, constructorArgs: Array<any>) => Object
function new2(constructor, ...constructorArgs) {
const newObject = {};
Object.setPrototypeOf(newObject, constructor.prototype);
return constructor.apply(newObject, constructorArgs) || newObject;
}
And we're done.
Hope this helped!
Tweet me with feedback @_vincecampanale if it did or didn't.
Til next time 👋.
Posted on June 20, 2017
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.