Javascript discussion: Variable scopes and functions
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Your post was blank...
Copy link to clipboard
Copied
Sorry It happens if I leave in the autoquote.
Copy link to clipboard
Copied
Sorry
It happens if I leave in the autoquote.
Yes...but what were you going to say?
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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:

