DEV Community

Cover image for JS 101: Implementing the "new" keyword
Kris Guzman
Kris Guzman

Posted on

JS 101: Implementing the "new" keyword

Because there is no real concept of classes in JavaScript, it helps to understand what some of these classical keywords are really doing under the hood.

We're going to create a simplified version of the "new" keyword in JavaScript. For example, using the new keyword to instantiate an object we would do:

const dog = new Dog()
Enter fullscreen mode Exit fullscreen mode

but we are going to do something like this:

const sparky = newObject(Dog, 'Sparky', 3)
const spot = newObject(Dog, 'Spot', 6)
Enter fullscreen mode Exit fullscreen mode

What the "new" Keyword Does

To understand the "new" keyword, it's important to understand constructor functions. Constructor functions in JS are just regular ole functions that are responsible for initialization. For example:

// the upper case function name is just a standard practice, not necessarily required
function Dog(petName, age) {
   this.petName = petName
   this.age = age
}
Enter fullscreen mode Exit fullscreen mode

So here are the steps that the "new" keyword does in the background:

  1. Create an empty object
  2. Assign the prototype of the empty object to the prototype of the constructor function
  3. Run the constructor function using the "this" context of the new object
  4. If the constructor returns an object, return that, otherwise, return "this"

Let's start off by defining a function called newObject that will replace the new keyword.

/* 
   We will need the constructor function, and all of the constructor
   parameters. Using the handy spread operator here.
*/
function newObject(constructor, ...params) {
 // we will fill this out in the following steps
}
Enter fullscreen mode Exit fullscreen mode

Step 1: Create an empty object

Easy enough. Let's do that:

function newObject(constructor, ...params) {
 function d() {}
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Assign the prototype of the empty object to the prototype of the constructor function

A little trickier, but Object has a handy function called setPrototypeOf. Let's use it:

function newObject(constructor, ...params) {
 function d() {}
 Object.setPrototypeOf(d, constructor.prototype)
}
Enter fullscreen mode Exit fullscreen mode

Not bad so far!

Step 3: Run the constructor function using the "this" context of the new object

Alrighty, this is probably the most complicated part for new JavaScript programmers. There is a function that all objects have called call and apply. They run a particular function where the this parameter of that function is the one we pass. For example:

function Dog(petName) {

   this.petName = petName
}

/* 
   we pass "this", so the "this" in "this.petName" refers to the one 
   we passed in.  
*/
Dog.call(this, 'Harold')

/* 
   if we want to pass an array of parameters, we can use the multi- 
   parameter equivalent of the "call" function.
*/
Dog.apply(this, ['Harold', 'James', 'Clark'])
Enter fullscreen mode Exit fullscreen mode

Okay, so now that we know how to use call / apply, which one do you think we should use to handle step number 3? Remember, a constructor function can have any number of parameters.

Ready? Here it is:

function newObject(constructor, ...params) {
 function d() {}
 Object.setPrototypeOf(d, constructor.prototype)
// apply expects an array-like second parameter, which is why
// we spread it in an array
 constructor.apply(d, [...params])
}
Enter fullscreen mode Exit fullscreen mode

Step 4: If the constructor returns an object, return that, otherwise, return "this"

To finish off our newObject function, we add a quick conditional check to see if the constructor function returns an object.

function newObject(constructor, ...params) {
 function d() {}
 Object.setPrototypeOf(d, constructor.prototype)
 const obj = constructor.apply(d, [...params])
 if(typeof obj === 'object') return obj
 return d
}
Enter fullscreen mode Exit fullscreen mode

Note that null is technically an object, so if a constructor returns null, that will be returned by our instantiation function.

Step 5: Profit

Let's put everything together and give our function a whirl!

// our fancy new function
function newObject(constructor, ...params) {
    function d() {}
  Object.setPrototypeOf(d, constructor.prototype)
  const obj = constructor.apply(d, [...params])
  if(typeof obj === 'object') return obj
  return d
}

// an example constructor function
function Dog(petName, age) {
   this.petName = petName
   this.age = age
   this.bark = function() {
      console.log(`${this.petName} is ${this.age} years old`)
   }
}

const bill = newObject(Dog, 'Bill', 8)
const sam = newObject(Dog, 'Sam', 2)

dog.bark() // prints: Bill is 8 years old
dog2.bark() // prints: Sam is 2 years old
Enter fullscreen mode Exit fullscreen mode

Conclusion

Now that we have seen how the new keyword works, we can appreciate how handy it is that all we have to do is type const dog = new Dog() to get the same result.

FUN FACT!

The new keyword will run the given function regardless of whether you type new Dog() or new Dog, so technically you don't need to do the former, but for the sake of everyone's sanity it's probably better to just go with the former option.

Top comments (1)

Collapse
 
ldtdream profile image
LDT-dream • Edited

Hi Guzman! Good implementation for the new function!
There is a small suggestion in your code implementation.
For the Code used to judge in the Step 4 of your article
if(typeof obj === 'object') return obj
I think you can add another judge in it, like
if(typeof obj === 'object' && typeof obj !== null) return obj
As it can really judge if the obj is in object type : )