DEV Community

Cover image for Encapsulate and Moderate with Closures
Jonathan Silvestri
Jonathan Silvestri

Posted on

Encapsulate and Moderate with Closures

Have you ever found yourself wanting to modify and update a data structure throughout the life of your application? Take the following code:

const obj = {}

const modifyObj = (key, value) => {
  obj[key] = value
  return obj
}
modifyObj('foo', 'bar') // Returns { foo: 'bar'}
modifyObj('biz', 'baz') // Returns { foo: 'bar', biz: 'baz' }

We have some object, obj, defined in our global scope, and a function, modifyObj that can accept a key/value pair that are assigned to the global object. We can see what happens when we invoke our function via the provided comments.

Given how our function is constructed and where our modified object is defined, is there anything that prevents us from directly interacting with the object outside of our function?

The answer is no:

delete obj.foo // Yup I can do this!
// Or...
obj['foo'] = 'biz' // Totally valid - this is what our modifying function does!

This is not ideal. We really would like for an object that we can modify, but we'd also like to do so without exposing the object to the global scope. We can achieve this with closure!

Closures leverage what is known as a higher order function - a function that returns a function - to encapsulate variables and other data within a private scope. The returned function can access and modify that data, known as creating closure around it.

What this means is, we can create our object within the context of a higher order function, and then modify it without exposing that to the global scope. Let's take a look at the code.

const higherOrderFunc = () => {
  const obj = {}
  return function modifyObj(...args) {
    const [key, value] = args
    obj[key] = value
    return obj
  }
}

const modifyObj = higherOrderFunc()
modifyObj('foo', 'bar') // Returns { foo: 'bar'}
modifyObj('biz', 'baz') // Returns { foo: 'bar', biz: 'baz' } !!!
console.log(obj) // ReferenceError: obj is not defined!

// And I have no method of messing with the object from the outside!

Let's also walk through our code and talk about what's happening.

const higherOrderFunc = () => {
  const obj = {}
  return function modifyObj(...args) {
    const [key, value] = args
    obj[key] = value
    return obj
  }
}

This is our closure. We create a higher order function that returns an inner function which retains knowledge of the outer obj variable. That way, we can invoke our function to modify the obj.

const modifyObj = higherOrderFunc()

Here, we invoke our higher order function to create our object and provide us with a setter function. Nothing has been done to the object quite yet.

modifyObj('foo', 'bar') // Returns { foo: 'bar'}
modifyObj('biz', 'baz') // Returns { foo: 'bar', biz: 'baz' }

We operate on our object with our closure, and can see that the object updates as expected!

console.log(obj) // ReferenceError: obj is not defined!

Finally, we try to log the object outside of our closure's scope. We'll get a reference error, which means we are trying to operate on a variable that does not exist in our current scope!

Hopefully you can find some use for closures in your current or upcoming work. Can you think of some uses for it right now? We'll be discussing one in my next post and how powerful closure is in that context.

BONUS: My function only allows for setters. But there's a way to extend the higher order function to also return a getter so we can check our object. Feel free to provide your solution in the comments below!

Top comments (2)

Collapse
 
millebi profile image
Bill Miller

Hmmm... I don't see much difference. You can still get access to 'obj' from the return value of the call to modifyobj()... and then directly update it. This particular example only disallows accidental usage of 'obj'... which is useful in many cases.

Just thought I'd point this out for anyone thinking that the 'obj' instance is being hidden or protected in some manner by this example. This example is a great example of not advertising the internals of some object to the global scope... which is extremely useful in many cases.

Collapse
 
silvestricodes profile image
Jonathan Silvestri • Edited

Ack, you're right. The closure should not be returning the object.

The example was meant to illustrate how to prevent global scope pollution if you had a need for that. Slight mis-fire on my part.