11 Replies Latest reply on Mar 12, 2012 11:25 AM by John Hawkinson

    Javascript discussion: Variable scopes and functions

    TᴀW Adobe Community Professional & MVP

      Hi,

       

      I'm wondering how people proceed regarding variable scope, and calling

      functions and things. For instance, it's finally sunk in that almost

      always a variable should be declared with var to keep it local.

       

      But along similar lines, how should one deal with functions? That is,

      should every function be completely self contained -- i.e. must any

      variables outside the scope of the function be passed to the function,

      and the function may not alter any variables but those that are local to

      it? Or is that not necessary to be so strict?

       

      Functions also seem to be limited in that they can only return a single

      variable. But what if I want to create a function that alters a bunch of

      variables?

       

      So I guess that's two questions:

       

      (1) Should all functions be self-contained?

      (2) What if the function needs to return a whole bunch of variables?

       

       

      Thanks,

      Ariel

       

        • 1. Re: Javascript discussion: Variable scopes and functions
          John Hawkinson Level 5

          Ariel:

            A lot depends on what you are doing. What is the size of your project?

           

            Are you running in a persistent session? If you are running in #targetengine main, for instance, it hardly matters whether you use excessive global variables, because it is quite difficult for two scripts to badly interact.

           

            Remember that there are more than two scopes. It is not just "global" and "local." Javascript scope blocks are defined with functions, so, for instance, this example has four scopes:

           

          var v1;
          function f1() {
            var v2;
          }
          (function f2() {
            var v3;
          
            function f3() {
              var v4;
            }
          })();
          

           

           

          Each of v1, v2, v3, and v4 are in a different scope. There are four different scopes, though v2 and v3 are at the same level of hierarchy.

           

          The really important key is to avoid the global scope, that is v1 (incidently, the name of the function f1 is also in the global scope, so that means two scripts in a persistent session that both have an f1() can collide.)

           

          Whether f3 should be allowed to modify v3 (which is in scope) really depends. It depends on how likely f3 is to be reused outside of f2 in another piece of code, and how complicated f2 and f3 are, and how annoying it is to try to manage the situation.

          But along similar lines, how should one deal with functions? That is,

          should every function be completely self contained -- i.e. must any

          variables outside the scope of the function be passed to the function,

          and the function may not alter any variables but those that are local to

          it? Or is that not necessary to be so strict?

          It is not necessary to be so strict, no.

          But those non-local variables ought not be global. They should be in some other scope.

           

           

          Functions also seem to be limited in that they can only return a single

          variable. But what if I want to create a function that alters a bunch of

          variables?

           

          Functions can easily return complex data structures. For instance:

          function returnsArray() {
            return [1,2,3,4,5];
          }
          function returnsObject() {
            return {
              alpha: "Alpha is the first letter of the greek alphabet",
              bet: "A bet is a complex hedge against future events."
            };
          }
          

           

          I underlined the word alters in your text, because I wonder what you mean. It's probably not good style to alter out of scope variables if you can simply return copies of them.

          Again, it depends. "What are you really trying to do?"

           

          It is hard to talk about this stuff in the abstract. We need solid clear cases, and every case can be different.

           

          Also, is anyone else going to read your code? :-)

           

          Hopefully this helps get you thinking.

          • 2. Re: Javascript discussion: Variable scopes and functions
            TᴀW Adobe Community Professional & MVP

            Hi John,

             

            Thanks for chipping in. (Incidentally, I couldn't find any way of

            marking answers correct when I visited the web forums a few days ago, so

            I gave up.)

             

            I find that I begin wondering about these things as soon as I've got

            more than half a dozen functions, or a couple of hundred lines. What I

            want is to write code that I can easily come back to a few months later

            and make changes/add features -- so it's only me that sees the code, but

            after long intervals it can almost be as though someone else has written

            it! So I was wondering if I would be doing myself a favour by being more

            strict about make functions independent etc. Also, I was noticing that

            in the sample scripts that accompany InDesign (written by Olav Kvern?)

            the functions seem always to require all variables to be passed to it.

             

            "Remember that there are more than two scopes. It is not just "global"

            and "local."

             

            That only recently came to my attention. Perhaps I should read a book to

            get some formal training about Javascript -- so far I'm relying on

            picking things up as I go along (and of course us almost-middle-age

            folks are likely to have had programming lessons at school, as well as

            programming the old Commodore-64 and even Vic-20 at home -- which is

            certainly my background in computing!) Anyway, I sunddenly realised

            that, as you say, if a variable is declared with var, and then within

            that scope a function is called, that variable is still accessible to

            the function so long as the function does not create its own

            similarly-labeled variable.

             

            I'm not 100% sure what you mean by persistent session. Most of my

            scripts are run once and then quit. However, some do create a modeless

            dialog (ie where you can interface with the UI while running it), which

            is the only time I need to use #targetengine.

             

            But I think you've answered one of my questions when you say that the

            thing to avoid is the "v1" scope. Although I don't really see what the

            problem is in the context of InDesign scripting (unless someone else is

            going to using your script as function in one of theirs). Probably in

            Web design it's more of an issue, because a web page could be running

            several scripts at the same time?

             

            Regarding functions altering variables: for example, I have a catalog

            script. myMasterPage is a variable that keeps track of which masterpage

            is being used. A function addPage() will add a page, but will need to

            update myMasterPage because many other functions in the script refer to

            that. However, addPage() also needs to update the total page count

            variable, the database-line-number-index-variable and several others,

            which are all used in most other functions. It seems laborious and

            unnecessary to pass them all to each function, then have the function

            alter them and return an array that would then need to be deciphered and

            applied back to the main variables. So in such a case I let the function

            alter these "global" (though not v1) variables. You're saying that's okay.

             

            However, the down-side is that intuitively I feel this makes the script

            more "messy" -- less legible and professional. (On the other hand, I

            recall reading that passing a lot of variables to functions comes with a

            performance penalty.)

             

            I knew a function could return an array. I'll have to read up on it

            returing an object. (I mean, I guess I intuitively knew that too -- I'm

            sure I've had functions return textFrames or what-have-you),

             

            I'm just wondering what professional programmers do to keep scripts of

            1000+ lines manageable.

             

            Thanks,

            Ariel

            • 3. Re: Javascript discussion: Variable scopes and functions
              John Hawkinson Level 5

              Ariel:

              (Incidentally, I couldn't find any way of  marking answers correct when I visited the web forums a few days ago, so I gave up.)

               

              It's there...as long as you're logged in at least, and are the poster of the thread.

              What I want is to write code that I can easily come back to a few months later

              and make changes/add features -- so it's only me that sees the code, but

              after long intervals it can almost be as though someone else has written

              it! So I was wondering if I would be doing myself a favour by being more

              strict about make functions independent etc. Also, I was noticing that

              in the sample scripts that accompany InDesign (written by Olav Kvern?)

              the functions seem always to require all variables to be passed to it.

              Where it is not impractical to do so, you should make functions independent and reusable. But there are plenty of cases where it is impractical, or at least very annoying to do so.

               

              The sample scripts for InDesign are written to be in parallel between three different languages, and have a certain lowest-common-denominator effect. They also make use of some practices I would consider Not Very Good. I would not recommend them as an example for how to learn to write a large Javascript project.

              I'm not 100% sure what you mean by persistent session. Most of my

              scripts are run once and then quit. However, some do create a modeless

              dialog (ie where you can interface with the UI while running it), which

              is the only time I need to use #targetengine.

              Any script that specifies a #targetengine other than "main" is in a persistent session. It means that variables (and functions) will persist from script invokation to invokation. If you have two scripts that run in #targetengine session, for instance, because of their user interfaces, they can have conficting global variables. (Some people will suggest you should give each script its own #targetengine. I am not convinced this is a good idea, but my reasons against it are mostly speculation about performance and memory issues, which are things I will later tell you to not worry about.)

              But I think you've answered one of my questions when you say that the

              thing to avoid is the "v1" scope. Although I don't really see what the

              problem is in the context of InDesign scripting (unless someone else is

              going to using your script as function in one of theirs). Probably in

              Web design it's more of an issue, because a web page could be running

              several scripts at the same time?

              It's more of an issue in web browsers, certainly (which I have ~no experience writing complex Javascript for, by the way), but it matters in ID, too. See above. It also complicates code reuse across projects.

              Regarding functions altering variables: for example, I have a catalog

              script. myMasterPage is a variable that keeps track of which masterpage

              is being used. A function addPage() will add a page, but will need to

              update myMasterPage because many other functions in the script refer to

              that. However, addPage() also needs to update the total page count

              variable, the database-line-number-index-variable and several others,

              which are all used in most other functions. It seems laborious and

              unnecessary to pass them all to each function, then have the function

              alter them and return an array that would then need to be deciphered and

              applied back to the main variables. So in such a case I let the function

              alter these "global" (though not v1) variables. You're saying that's okay.

              Yes, that is OK. It's not a good idea to call that scope "global," though, since you'll promote confusion. You could call it...outer function scope, maybe? Not sure; that assumes addPage() is nested within some other function whose scope is containing myMasterPage.

               

              It is definitely true that you should not individually pass them to the function and return them as an array and reassign them to the outer function's variables.

              I think it is OK for addPage() to change them, yes.

              Another approach would be something like:

               

              (function() {
                var MPstate = {
                  totalPages: 0,
                  dbline: -1
                };
              
                function addPage(state) {
                  state.totalPages++;
                  state.dbline=0;
                  return state;
                }
              
                ...
                MPstate = addPage(MPstate);
              })();
              

              I don't think this is a particularly good approach, though. It is clunky and also doesn't permit an easy way for addPage() to return success or failure.

              Of course it could instead do something like:

               

              ...
                  return { success: true, state: state };
                }
                ...
                var returnVal = addPage(MPstate);
                if (returnVal.success) { MPstate = returnVal.state; }
              ...
              

              but that's not very comforting either. Letting addPage() access it's parent functions variables works much much better, as you surmised.

              However, the down-side is that intuitively I feel this makes the script

              more "messy" -- less legible and professional. (On the other hand, I

              recall reading that passing a lot of variables to functions comes with a

              performance penalty.)

              I think that as long as you sufficiently clearly comment your code it is fine.

              Remember this sort of thing is part-and-parcel for a language that has classes and method functions inside those classes (e.g. Java, Python, ActionScript3, C++, etc.). It's totally reasonable for a class to define a bunch of variables that are scoped to that class and then implement a bunch of methods to modify those class variables. You should not sweat it.

               

              Passing lots of variables to functions does not incur any meaningful performance penalty at the level you should be worrying about. Premature optimization is almost always a bad idea. On the other hand, you should avoid doing so for a different reason -- it is hard to read and confusing to remember when the number of arguments to something is more than three or so. For instance, compare:

               

              addPage("iv", 3, "The rain in spain", 4, loremIpsumText);
              
              addPage({ name: "iv", insertAfter: 3, headingText: "The rain in spain",
                numberColumns: 4, bodyText: loremIpsumText});
              

              The latter is more verbose, but immensely more readable. And the order of parameters no longer matters.

              You should, in general, use Objects in this way when the number of parameters exceeds about three.

               

              I knew a function could return an array. I'll have to read up on it

              returing an object. (I mean, I guess I intuitively knew that too -- I'm

              sure I've had functions return textFrames or what-have-you),

              Remember that in Javascript, when we say Object we mean something like an associative array or dictionary in other languages. An arbitrary set of name/value pairs. This is confusing because it also means other kinds of objects, like DOM objects (textFrames, etc.), which are technically Javascript Objects too, because everything inherits from Object.prototype. But...that's not what I mean.

               

              So yes, read up on Objects. They are incredibly handy.

              • 6. Re: Javascript discussion: Variable scopes and functions
                TᴀW Adobe Community Professional & MVP

                Sorry It happens if I leave in the autoquote.

                • 7. Re: Javascript discussion: Variable scopes and functions
                  John Hawkinson Level 5

                  Sorry    It happens if I leave in the autoquote.

                   

                  Yes...but what were you going to say?

                  • 8. Re: Javascript discussion: Variable scopes and functions
                    TᴀW Adobe Community Professional & MVP

                    I was going to say the following, which I included in the previous

                    email. But I separated it from the apology with a line made up of

                    underscores. Looks like a line like that Jives things up and deletes all

                    subsequent text..... grr. (I miss the good old days when these forums

                    were accessible with NNTP).

                     

                    Thanks John. Food for thought.

                     

                    So I'm taking away from this that a well-commented script is allowed to

                    have functions that play with variables outside that function's scope --

                    although there is a preference for self-contained functions where

                    practical; and that using objects can be a good substitute for a

                    function that requires loads of parameters in a fixed order.

                     

                    I have tried to create proper classes with functions and properties to

                    keep things simple in a long script, but they can also get unwieldly if

                    one doesn't think very carefully about what goes in the class and what

                    stays out.

                     

                    I have to say that my admiration for InDesign's DOM rises -- I mean, how

                    on earth do they keep track of it all?!

                     

                    Ariel

                    • 9. Re: Javascript discussion: Variable scopes and functions
                      John Hawkinson Level 5

                      But I separated it from the apology with a line made up of underscores.

                      Underscores? See http://forums.adobe.com/markuphelpfull.jspa for details, but underscores begin and terminate underlining. I'm not sure why they would cause the message to be eaten, but I suppose you could fiddle in the Testing forum...

                       

                      So I'm taking away from this that a well-commented script is allowed to

                      have functions that play with variables outside that function's scope --

                      although there is a preference for self-contained functions where

                      practical; and that using objects can be a good substitute for a

                      function that requires loads of parameters in a fixed order.

                      "allowed"? Well, it all depends on your arbiter, these are rules of style.

                      I think you summarized me accurately, but I don't think you necessarily need to be well-commented to do such a thing, and you should always use good comments regardless!

                       

                      I'm not sure what you mean when you say "proper classes" since, of course, Javascript doesn't have classes. you could mean many things.

                       

                      The more time one spends looking at common design patterns for software, the easier it is to come up with natural organizations to address issues like this... but these kinds of software engineering issues rarely tend to arise with InDesign scripting, but most of it is just not that involved...

                       

                       

                      Edit: Use /markuphelpfull.jspa instead of the ancient JVD thing.

                      • 10. Re: Javascript discussion: Variable scopes and functions
                        TᴀW Adobe Community Professional & MVP

                        It's there...as long as you're logged in at least, and are the poster of the thread.

                        John Hawkinson wrote:

                         

                        Ariel:

                        (Incidentally, I couldn't find any way of  marking answers correct when I visited the web forums a few days ago, so I gave up.)

                         

                        It's there...as long as you're logged in at least, and are the poster of the thread.

                        Can't see them. Strange. I'm using the web interface now, logged in, and poster of the thread. They're nowhere to be seen.

                        • 11. Re: Javascript discussion: Variable scopes and functions
                          John Hawkinson Level 5

                          Can't see them. Strange. I'm using the web interface now, logged in, and poster of the thread. They're nowhere to be seen.

                          Oh, I see. It is because, at topic creation time, this thread was not marked as a Question. You can tell from the icons in the index:

                           

                          iconq.png