JavaScript: self-calling function returns a closure. What is it for?
Asked Answered
A

4

7

Studying one JavaScript library I found following construction:

theMethod: function () {
    var m1 = new SomeClass();
    return function (theParameter) {
        this.someMethod();
        m1.methodCall(this.someField1);
        this.someField2 = 'some value';
    }
}()

theMethod is called as follows:

c.theMethod(paramValue);

What did the author want to say with this declaration?

Why not to use such declaration:

theMethod: function (theParameter) {
    var m1 = new SomeClass();
    this.someMethod();
    m1.methodCall(this.someField1);
    this.someField2 = 'some value';
}
Anthodium answered 21/6, 2013 at 12:29 Comment(10)
@dystroy: I am sure. Look at the brackets at the end of theMethod declaration.Anthodium
"Why not to use such declaration?" Because the two are not equivalent. It's the same difference between a piece of data, and a function which returns a piece of data.Weiner
Can you share the actual code?Saree
There are many reasons for closure based function factories but it's hard to tell here why it's being used, the code is too abstracted.Lumbering
@Patrick McElhaney: sure, it is a three.js library. Method is named THREE.Object3D.applyMatrixAnthodium
This? github.com/mrdoob/three.js/blob/…Saree
@Patrick McElhaney: yes, you have pointed the right place out.Anthodium
@Anthodium Juhana answer explain the what is it doing question, but see my answer as for the why.Orthodontia
One thing that helps with code like this is to put a set of parentheses around the outer function: (function () { ... })(). This is a convention that helps to flag up to anyone reading the code that you're going to immediately call the function with that second set of empty parentheses (). Otherwise when someone reads theMethod: function () { ... they're likely to assume that the function they're now reading is going to be the thing that runs when theMethod is called.Harelip
@DanielEarwicker, well put. Years ago, I didn't think the outer brackets mattered but was eventually convinced by this precise point of view.Chorea
O
3

It's for encapsulation. m1 is hidden to other methods and from external access by places that might use theMethod. Javascript programmers usually don't care about encapsulation (but they should on non-trivial scripts) because you need to use closures which complicates design of classes and when implemented poorly can reduce performance. That is why you don't see this kind of structure often outside of libraries.

Also your second piece of code is not equivalent. Something equivalent would be:

theMethod: function (theParameter) {
    if (typeof this.prototype.__private__ === "undefined") {
        this.prototype.__private__= {}
    }
    if (typeof this.__private__.m1 === "undefined") {
        this.prototype.__private__.m1= new SomeClass();
    }
    this.someMethod();
    this.__private__.m1.methodCall(this.someField1);
    this.someField2 = 'some value';
}

But then m1 is not really private here.

Also note that in that piece of code m1 is only visible to the function returned, if theMethod was a member of a class the other methods of that class would not be able to see m1 (making it different than the "private" keyword on Java). Also depending on how you declared theMethod m1 would be the java equivalent to "static" (m1 is a function on the prototype of an object it's static, if it's not on a prototype it's not static).

Orthodontia answered 21/6, 2013 at 12:53 Comment(10)
perfect explanation! And, of course, Juhana should be thanked too.Anthodium
I understood! Let me complement your answer. How this works: the line theMethod: function does not declare a function() named theMethod but instead an anonymous function() is called once(!!!), when JavaScript interpreter constructs the object, and result of this call is passed to theMethod field. This result is a closure using a pre-initialized m1 variable.Anthodium
Notice that using the this keyword here might not be the best example, since the function in question is a prototype method actually and invoked on different instances. It had to be ….prototype.__private__ = {}; therefore.Kamenskuralski
@Anthodium you are correct and also Bergi is right, my code snippet only makes sense if theMethod was a method of a prototype (I'm very careful when using the term "class" in javascript since Javascript does not have classes). I will correct the answerOrthodontia
Who said "Javascript programmers usually don't care much about encapsulation" and that " you don't see this kind of structure often"? Javascript programmers actually care very much and tend to use encapsulation rather a lot. For example, just about every mainstream jQuery plugin is encapsulated, as are all modules of the Module Pattern. The importance of encapsulation to tidy javascript cannot be overstated.Chorea
@Chorea you are right, there are several design patterns for encapsulating plugins and such. When you are making a plugin that is going to be used by other people you do care a lot about encapsulation. But in simple scripts that are only going to run on my own pages I usually don't try to encapsulate if it's going to generate a more hard to read code. Regardless I will rectify my answer as it may suggest that encapsulation is not important in Javascript.Orthodontia
@Chorea To be fair, it probably depends on one's background. (Seeing as many people still program JS as part of an application written in something else.) So a Python person likely wouldn't care about data encapsulation at all, but try and keep module namespaces uncluttered.Sephira
Hoffman, you take criticism on the chin like a gentleman sir!Chorea
@Chorea english is my second language so I tend to be a little too formal when writing in it, but thank you =)Orthodontia
I have family in Brazil ... stay safe.Chorea
F
5

Declaring the variable outside the function makes the function use the same object every time.

An example (with an integer instead of an object, for simplicity's sake):

var c = { 
    theMethod: function () {
        var m1 = 0;
        return function (theParameter) {
            m1++;
            console.log( m1 );
        }
    }()
};

c.theMethod(); c.theMethod();  // output: 1 2


var d = { 
    theMethod: function () {
        return function (theParameter) {
            var m1 = 0;
            m1++;
            console.log( m1 );
        }
    }()
};

d.theMethod(); d.theMethod();  // output: 1 1

The self-invoking function works like this:

var c = { 
    theMethod: function () {
        var m1 = 0;
        return function (theParameter) {
            m1++;
            console.log( m1 );
        }
    }()
};

When the object c is created, the self-invoking function invokes itself and theMethod now equals the return value of that function. In this case the return value is another function.

c.theMethod = function( theParameter ) {
    m1++;
    console.log( m1 );
};

The variable m1 is available to the function because it was in scope when the function was defined.

From now on when you call c.theMethod() you are always executing the inner function that was returned from the self-invoking function, which itself executed only once at the time the object was declared.

The self-invoking function works just like any function. Consider:

var c = { 
    theMethod: parseInt( someVariable, 10 )
};

You don't expect parseInt() to execute every time you use the c.theMethod variable. Replace parseInt with an anonymous function as in the original and it's exactly the same thing.

Festivity answered 21/6, 2013 at 12:34 Comment(2)
Juhana, it seems that I do not understand how in-place called functions work.Anthodium
I added more explanations.Festivity
O
3

It's for encapsulation. m1 is hidden to other methods and from external access by places that might use theMethod. Javascript programmers usually don't care about encapsulation (but they should on non-trivial scripts) because you need to use closures which complicates design of classes and when implemented poorly can reduce performance. That is why you don't see this kind of structure often outside of libraries.

Also your second piece of code is not equivalent. Something equivalent would be:

theMethod: function (theParameter) {
    if (typeof this.prototype.__private__ === "undefined") {
        this.prototype.__private__= {}
    }
    if (typeof this.__private__.m1 === "undefined") {
        this.prototype.__private__.m1= new SomeClass();
    }
    this.someMethod();
    this.__private__.m1.methodCall(this.someField1);
    this.someField2 = 'some value';
}

But then m1 is not really private here.

Also note that in that piece of code m1 is only visible to the function returned, if theMethod was a member of a class the other methods of that class would not be able to see m1 (making it different than the "private" keyword on Java). Also depending on how you declared theMethod m1 would be the java equivalent to "static" (m1 is a function on the prototype of an object it's static, if it's not on a prototype it's not static).

Orthodontia answered 21/6, 2013 at 12:53 Comment(10)
perfect explanation! And, of course, Juhana should be thanked too.Anthodium
I understood! Let me complement your answer. How this works: the line theMethod: function does not declare a function() named theMethod but instead an anonymous function() is called once(!!!), when JavaScript interpreter constructs the object, and result of this call is passed to theMethod field. This result is a closure using a pre-initialized m1 variable.Anthodium
Notice that using the this keyword here might not be the best example, since the function in question is a prototype method actually and invoked on different instances. It had to be ….prototype.__private__ = {}; therefore.Kamenskuralski
@Anthodium you are correct and also Bergi is right, my code snippet only makes sense if theMethod was a method of a prototype (I'm very careful when using the term "class" in javascript since Javascript does not have classes). I will correct the answerOrthodontia
Who said "Javascript programmers usually don't care much about encapsulation" and that " you don't see this kind of structure often"? Javascript programmers actually care very much and tend to use encapsulation rather a lot. For example, just about every mainstream jQuery plugin is encapsulated, as are all modules of the Module Pattern. The importance of encapsulation to tidy javascript cannot be overstated.Chorea
@Chorea you are right, there are several design patterns for encapsulating plugins and such. When you are making a plugin that is going to be used by other people you do care a lot about encapsulation. But in simple scripts that are only going to run on my own pages I usually don't try to encapsulate if it's going to generate a more hard to read code. Regardless I will rectify my answer as it may suggest that encapsulation is not important in Javascript.Orthodontia
@Chorea To be fair, it probably depends on one's background. (Seeing as many people still program JS as part of an application written in something else.) So a Python person likely wouldn't care about data encapsulation at all, but try and keep module namespaces uncluttered.Sephira
Hoffman, you take criticism on the chin like a gentleman sir!Chorea
@Chorea english is my second language so I tend to be a little too formal when writing in it, but thank you =)Orthodontia
I have family in Brazil ... stay safe.Chorea
A
1

@Juhana is right. Here's a handy tutorial on how closures work. http://www.javascriptkit.com/javatutors/closures.shtml

Anthodium answered 21/6, 2013 at 12:39 Comment(0)
K
1

What did the author want to say with this declaration?

That the m1 value should be reused for every call. In your closure-less alternative, it would instantiate a new SomeClass object with every call. It either is used simply for performance improvement, or (and) to maintain a common state in the m1 object.

Kamenskuralski answered 21/6, 2013 at 12:40 Comment(2)
That's my point. I do not understand the use of closure when an outer object is re-created with every call.Anthodium
But with the closure it is not recreated? Or what do you mean by "outer" object?Kamenskuralski

© 2022 - 2024 — McMap. All rights reserved.