A curious case of closure. Can anyone explain how this works?

I came across this solution to a problem I was using using closure, but I have no idea why this actually works, and why my original code didn't.

I have a helper function that takes a default_object. It returns another function that takes some object and uses it to populate the default values ​​for the object:

function DTO(default_object){
    return function (object) {

        var dto = default_object;

        for (key in default_object) {
            if (key in object) {
                dto[key] = object[key];
            }
        }
        return dto;
    }
}

      

Then I use this function like this:

var user = function(object) {
        return DTO({
            name: null,
            color: "#ffffff",
            public_id: null,
        })(object);
    }

var clean_users = []
unclean_users.forEach(function(unclean_user){
    clean_users.push(user(unclean_user));
});

      

Which returns an array of safe custom objects. I originally had:

var user = DTO({
            name: null,
            color: "#ffffff",
            public_id: null,
        });
    }

      

What I assumed would work the same because the DTO returns a function, but it makes clean_users populated by the same user (first) over and over.

I was under the impression that executing the user in a loop would create a new function, each time adding a new unclean_user as an argument and returning a new clean_user, but for some reason this only happens once. Can someone explain why?

+3


source to share


5 answers


DTO returns a function

Yes it is.

but this makes clean_users populated by the same user (first) over and over.

Yes, because the function that is returned DTO(o)

returns the same object o

every time. Your problem is the line



 var dto = default_object;

      

which does not create a copy , but assigns a reference to the same object to another variable. Your original code works because it creates new objects through the literal every time it is called user

, but your new function returns the same object every time it is called user

.

You will need to change your code so that the return function creates a new object each time and copies the properties from default_object

:

function DTO(default_object){
    return function (object) {
        var dto = {};
        for (key in default_object) {
            if (key in object) {
                dto[key] = object[key];
            } else {
                dto[key] = default_object[key];
            }
        }
        return dto;
    }
}

      

+3


source


Without the second closure, you only call DTO()

once. DTO()

is written in such a way that it takes an object and returns a function that will only work on the object that was passed to DTO

. This way, when you call user()

, you are modifying the same object that was originally passed in DTO()

when you called it.

As you push

results user()

, you keep references to the same object, since this object changes during the last loop, at the end yours clean_users

will contain references to the same object. They are not different objects that look the same, if you look inside the array before going through everything unclean_users

you will see this.



Adding a second closure returns a function user()

that returns a function to call DTO()

with the object that was called ( user()

). This is what you want in this case. DTO()

Will then work on a new object every time it is called user()

.

+2


source


The problem is the function DTO

. This line: var dto = default_object;

creates a reference to the object stored in the closure, which is then changed every time the DTO function is called. In addition, the DTO function always returns a reference to the same object DTO

, so the array is filled with the same values.

This fixes the problem:

  function clone(ob) {
   return JSON.parse(JSON.stringify(ob));                                                      
  }

  function DTO(default_object){
    return function (object) {

        var dto = clone(default_object);

       for (key in default_object) {
           if (key in object) {
               dto[key] = object[key];
          }
       }
       return dto; 
   }         
 }  

      

This fixes the problem as it creates a new object every time the function is called.

Why does this work when the function DTO

is called in a closure?

var user = function(object) {
        return DTO({
            name: null,
            color: "#ffffff",
            public_id: null,
        })(object);
    }

      

In the above code snippet, the function DTO

is called for each object in the array, whereas in the second case it is called only once. Hence, a default_object

is created every time the function is called.

JSFIDDLE https://jsfiddle.net/y2enopok/ Open your console to see the result.

Note I suggest using the best clone feature. For example cloneDeep from lodash.

+1


source


Closing JavaScript

A closure is an internal function that has access to the outer (enclosing) functional chaining of the chain variables. A closure has three chains of visibility:

  • It has access to its own scope (variables defined between its curly braces),
  • It has access to external variables of the function,
  • It has access to global variables

When you write a function this way it means

(function(){

})();

      

just by writing your code directly without any function, but variables only have scope for that closure function.

In your case, you are passing the object to the closure function that is being held default_object

, and then you are passing that object to a pointer to another variable dto

. And because of this, it uses the same memory location every time.

Decision:

function DTO(default_object) {
  return function(object) {

    var dto = {};

    for (key in default_object) {
      if (key in object) {
        dto[key] = object[key];
      } else {
        dto[key] = default_object[key]
      }
    }
    return dto;
  }
}

      

+1


source


I think a simple example can provide some clarity. Consider that any function with more than one argument is theoretically equivalent to a single argument function that returns functions that return one argument until all arguments have been passed. I.e:

add(1, 2) == add(1)(2) == 3

      

This is what is understood as curry. Note that add(1, 2)

all arguments must be passed when calling the function, while otherwise you can do it in two steps:

var add1 = add(1)
add1(2)

      

1

fixed in the closure; this one 1

will be the same on every call add1

, so you end up with a reusable function .

There is a difference between a function that is a natural map, like yours, and one that you have to partially apply manually. To illustrate this case, imagine you are working with a add(1, 2)

non-currency version and you want to partially apply 1

:

var add1 = function(b) {
  return add(1, b) // equivalent to add(1)(b)
}

      

Now 1

recreates every time you call add1

. This is not equivalent to the partially applied curry function. So with a number it doesn't matter, but objects are passed as value references, which means they are the same object and are mutable.

In conclusion, the difference is in creating a new object every time the same object captured in the closure is used.

+1


source







All Articles