Controlling variable scope - javascript

I've been able to find a few similar questions but I feel that the answers provided within do not fully expel my confusion.
I have come across this question whilst playing with jQuery, but I guess that this is more of a JS question than jQuery specific.
I feel like in the below example these variables that I wish to define would be good candidates to be global, they have a wide-ranging use outside of a few functions, but I feel that I want to limit the exposure.
$(function() {
$containers = $('.container');
$childContainer = $('#childContainer');
//Removed extra code
var $aButton = $('#childButton');
//Removed extra code
$containers.hide();
$childContainer.show(400);
$aButton.on('click', clickChildButton);
//Removed extra code
};
function clickChildButton() {
$containers.hide(400);
$childContainer.show(400);
}
I will have a number of buttons showing/hiding various containers. In all cases the $containers variable will need to be visible to the other functions to allow it to be hidden.
Should I be using global variables (or perhaps a namespacing global object hack) or is there another way that I can limit the scope of the $containers variable?
I'm not too keen on using anonymous functions to handle the click events as they are going to start getting a bit more complex (and contain more than just the two lines shown in the clickChildButton function.
Note: In this particular example it might be better to refactor the code and create a hideContainers function, but I am more interested in how to control the scope of variables in general rather than this particular example.
Thanks

In JavaScript (prior to ES6), all variables are function-scoped. Consequently, the only way to scope a variable is to make it local to a function.
You have two basic choices here. One is to make clickChildButton local to $(function(...) {...}), as it is the only place where it is relevant:
$(function() {
var $containers, $childContainer;
function clickChildButton() {
$containers.hide(400);
$childContainer.show(400);
}
...
});
If you need the scope to actually be wider but not too wide, the other choice is to wrap everything into an IIFE:
(function() {
$(function() {
...
});
function clickChildButton() {
....
});
)();

Related

Splitting code up into functions

Due to performance and other issues, I want to split my code into seperate functions as before it was just one big ".ready" function.
I am new to javaScript/jquery but I thought I would give it a go myself. I have done exactly the way I thought it was done but my console is telling me things are undefined so I am guessing I have got things out of scope. I have read up on it in more detail, but still have not got anywhere.
My code works OK at the moment but I want to get into the habbit of clean coding. Can someone point out where I am going wrong so I can carry on myself?
Here is an example of what I have so far
//Global variables
var randomWord = [];
var listOfWords = [];
var populationNumber = [];
var attemptNumber = [];
var completionNumber = [];
var gridSize = [];
generateGrid();
startMusic();
randomizeReward();
//Click event to start the game
$(".start-btn-wrapper").click(function () {
startplay();
});
//Click event to restart the game
$(".restart-btn").click(function () {
restartplay();
});
Thanks
Fiddle: http://jsfiddle.net/QYaGP/
Fiddle with HTML: http://jsfiddle.net/QYaGP/1/
You need to start passing some information into the functions you're defining. If your functions all have no arguments, then you will have to use globally defined variables, hardcoded references to jquery selections etc in order to get anything done.
So as an example, you have a function
function replaySound() {
$("#hintSound").attr('src', listOfWords[randomWord].hintSound);
hintSound.play();
}
This is actually going to play the sound detailed in listOfWords[randomWord] via the element #hintSound. You could do that via:
function playSound(selector, wordlistEntry) {
$(selector).attr('src', wordlistEntry.hintSound);
$(selector)[0].play();
}
And then instead of calling replaySound(), you'd call:
playSound('#hintSound', listOfWords[randomWord]);
This way the behaviour that you want is wrapped up in the function, but the specifics, i.e. the data you need for it, are passed in via the arguments. That allows you to reuse the function to play any sound using any selector, not just #hintSound.
You'll find as you do that that you need to start choosing what a function will act on in the code that calls it, rather than in the function. That's good, because the context of what you're trying to achieve is there in the calling code, not in the function. This is known as 'separation of concerns'; you try to keep logic about a given thing confined to one area, rather than spreading it about in lots of functions. But you still want functions to allow you to encapsulate behaviour. This allows you to change behaviour cleanly and easily, without having to rewrite everything every time some part of the logic changes.
The result should be that you find several functions actually did the same thing, but with different specifics, so you can just have one function instead and reuse it. That is the Don't Repeat Yourself principle, which is also important.
If you are concerned about performance, I would look into using an framework such as AngularJS. You can inject modularized code. Even better, with MVC your view is bound to your model so by changing the model the view automatically updates itself.
Also, stop using class selectors. Use ID selectors. They are much faster. You also want to preload selectors (even with class selectors). That way you are only searching the DOM once:
var ele = $('#elementId');
$(ele).doSomething();
This way, you have a reference to the DOM element. You can use a datastructure to store all of your references outside of the global scope:
var elementReferences = {}; //declaration
elementReferences.mainPage = {}; //use
elementReferences.mainPage.root = $('#mainPage'); //load the root div of a page segment
elementReferences.mainPage.title = $(elementReferences.mainPage.root).children('#title'); //load the title
elementReference.mainPage.form = $(elementReferences.mainPage.root).children('#form'); //load the form
Now you can do this:
$(elementReference.mainPage.form).whatever();
and it doesn't have to search the DOM for the element. This is especially useful for larger apps.
If you put a function within document.ready, as per your fiddle, you are only able to access that function within the scope of the document.ready call. You really want to be able to load/unload functions as needed dynamically within the scope that they are required in, which is where angularjs comes into play.
You also, for the most part, want to remove your functions and variables from the global scope and put them into containers that are sorted by their dependencies and use. This is Object Oriented Programming 101. Instead of having a bunch of arrays sitting within the global scope where they could be overwritten by mistake by another developer, you want to put them within a container:
var arrays = {}; //create the object
arrays.whatever1 = [];
arrays.whatever2 = [];
Obviously, you will probably want a more descriptive name than "arrays". Functions work the same manner:
var ajax = {}; //ajax object
var ajax.get = function(){
};
var ajax.post = function(){
};
var ajax.delete = function(){
};
This generally promotes cleaner code that is more reusable and easier to maintain. You want to spend a good portion of your time writing a spec that fully documents the overall architecture before actually beginning development. NEVER jump the gun if you can help it. Spend time thoroughly researching and planning out the big picture and how everything fits together rather than trying to wing it and figure it out as you go. You spend less time having to reinvent the wheel when you do it this way.
It's developed by google, so it should be around for quite a while. I'm not sure if you are the guy in charge of your system's architecture, but if performance/reusability is an issue at your company it is definitely worth taking a look at. I'd be more than happy to give you a walkthrough regarding most of what I know in terms of software architecture and engineering. Just MSG me if you are interested. Always happy to help!

multiple functions in one file?

Is this the correct way to split out code to smaller functions?
$(document).ready(function(){
$("form#create_form").submit(function() {
...
var is_okay = check_values(...);
...
});
});
function check_values() {
...
}
Is this the correct way to split out code to smaller functions?
Not really, since your check_values function is now part of the global window object. Leaking objects into the global space is badm, mkay?
Unfortunately there are so many ways that it could be done that it's hard to know where to start.
If your code is small it would be best just to leave it all within the closure inside your $(document).ready() function:
$(document).ready(function(){
function check_values() {
...
}
$("form#create_form").submit(function() {
...
var is_okay = check_values(...);
...
});
});
It is a way to split code into smaller functions.
One thing you should watch for is how many functions you assign in the global scope. If you can group your functions under a common global, for example, you will find you have less to worry about (in terms of maintenance and potential name clashes).

Multiple Javascript conflict

I'm trying to get 2 scripts to run on the same page but they won't play nice with each other. One is called TabTop http://www.isdntek.com/tagbot/tabtop.htm and the other is Clic*Pic http://www.isdntek.com/tagbot/gallery.htm, both by isdntek. I can get either one of them to run fine all by themselves, but not both together. I looked around and tried to find the answer to this problem by myself, but to no avail.
I would greatly appreciate any help that can be provided.
Thanks!
You can wrap each script in a self calling function:
(function(){
//As long as you don't use global variables
//the content here is protected from any interaction with the outside
})();
Now, if both codes use global variables, the task will be unfortunately harder.
The RainbowCodeModule6.js file is used by both pages, it sets a very large number of global variables (quite a few because it doesn't declare local variables within functions), so it is quite possible that with two scripts trying to use the same set of globals, they are getting conflicts. e.g. (my wrapping for posting here)
function changeShades(color){ //--update the vertical column of light/dark shades
var ymax=paletteymax
if (!color){return}
for (i=0; i<ymax; i++){
document.getElementById('colorShades'+i).
style.backgroundColor=colorBrightness(color,(ymax-1-i)/(ymax-1))
}
}
The above doesn't keep it's counter i local and depends on the global paletteymax. I can't say if that's your problem, but it is indicative of poor programming and application architecture. Another example:
function dec2hex(R,G,B) { //--Converts three R-G-B components to
// a single internet hex color
var hexTest="0123456789ABCDEF";
Rd_hi=R/16; Rd_lo=R%16;
Rd=hexTest.substr(Rd_hi,1)+hexTest.substr(Rd_lo,1)
Gn_hi=G/16; Gn_lo=G%16;
Gn=hexTest.substr(Gn_hi,1)+hexTest.substr(Gn_lo,1)
Bu_hi=B/16; Bu_lo=B%16;
Bu=hexTest.substr(Bu_hi,1)+hexTest.substr(Bu_lo,1)
hexval='#'+Rd+Gn+Bu
return hexval;
}
Why they decided to keep hexTest local but let all the other variables go global is beyond me. Variables R, G and B are also global, but here they are kept local because they are formal parameters in the function declaration.
It also uses document.write to write a table in parts, which is never a good idea. I think it's just a poorly written script, find something else.

Setting variables on Javascript namespace from ASP.NET Webform code behind

I am trying to clean up some older code, and it currently uses variables set with ScriptManager for use in the Javascript code to show/hide parts of the page.
The variables are currently just _showNameDiv, etc.
I'd like to put these all onto a common namespace, to make things a little bit cleaner, such as MyCompany.Toggles.ShowNameDiv.
I tried to create a namespace
var MyCompany = {
Toggles: {}
}
And within the code behind do this:
JavaScriptRegistrar javaScriptRegistrar = GetJavaScriptRegistrar();
javaScriptRegistrar.Register("MyCompany.Toggles.ShowNameDiv", true);
But I only get 'undefined' on that variable. (GetJavaScriptRegistrar is a wrapper for ScriptManager).
Is there a way to do what I am trying to do?
Am I going about this the wrong way?
Is there a better alternative that will get me the same benefit?
Keep in mind this is old code, and I cannot do a whole page rewrite. I am trying to make an incremental step that I might use to use as an example that I can show to my coworkers.
Well it's difficult to figure what could be the problem without knowing what those pieces of code exactly do (expecially JavascriptRegistrar).
One possible problem is maybe the scope of the javascript "namespace" you declare.
By using the "var" keyword, you're not making the namespace global, it's only defined in the closure it's defined into.
I suppose the javascript code that the JavascriptRegistrar class registers expects a global variable and does not find any so it returns undefined.
Here a piece of code to "expose" gloabally you're variable:
(function(window, undefined) {
var myCompany = {
Toggles: {}
};
window.MyCompany = myCompany;
})(window);

Update variables inside the jQuery $(document).ready() base scope?

I'm trying to find a way to minimize the number of Selector look-ups. My issue is that I have a variable defined with base $(document).ready() that needs to be updated inside functions nested inside $(document).ready()
Consider this example (EDIT: I updated it to be more explanatory)
<script>
//var $current_page = $home_page; **<--I DONT want to do this, going global
and of course it doesn't work since
$home_page isn't defined yet.**
$(document).ready(function() {
var $home_page = $("#home-page");
var $portfolio_page = $("#portfolio-page");
var $about_page = $("#about-page");
var $current_page = $home_page; // **<--This variable, in this scope level,
// is what I want updated**
$("#home-btn").click(function () {
$current_page.stop()
$current_page.show()
$current_page.animate({
duration: 360,
easing: 'easeInCirc',
complete: function() {
$(this).css({ top: -700 });
}
);
$current_page = $home_page;
});
$("#portfolio-btn").click(function () {
$current_page.stop()
$current_page.show()
$current_page.animate({
duration: 360,
easing: 'easeInCirc',
complete: function() {
$(this).css({ top: -700 });
}
);
$current_page = $portfolio_page; //<--This needs to somehow update the
// variable in the $(document).ready
// scope, but not global**
});
});
<script>
How can I update the variable $current_page without making it a global variable?
EDIT:
This is done to animate out the current page div when you click on a menu item. Yes, it's missing things, yes it may not make sense. It's just an example, not the actual application.
I understand this example is still trivial for performance, just ignore that fact. I just want to know how to do achieve this, not a lesson on whether it's the best practice or performance. Thanks guys.
The inner function creates a closure, capturing the variables in the scope it is defined in. So you already have what you're asking for...
...whether that's a good idea or not is another matter.
For starters, you're not actually modifying the value in the code you listed - you're assigning $current_page the same value it was already initialized with.
But assuming you just omitted the code that you would normally use to pick a different value for $current_page, you need to ask yourself: is this really even necessary? You're performing a lookup based on an element ID and caching a reference to that element in a variable without knowing if or when you'll actually need it again. At best, this results in a potentially-unnecessary lookup; at worst, it can result in a memory leak. Why not just keep track of the ID itself, and look up the element when and where you actually need it? Don't worry about performance until you actually encounter a performance problem... or you may find that your premature optimization has caused more problems than it solves.
Same goes for $home_page, $portfolio_page and $about_page - you're making your page load (slightly) more slowly on the off-chance that you'll need a reference to those elements later on, when you could just as well look them up as-needed.
"How can I update the variable $current_page without making it a global variable?"
You can update it right now. The inner click-handler function can modify $current_page.
"I'm trying to find a way to minimize the number of Selector look-ups."
But it seems that in fact you're about to make more, if you're changing $current_page with another selector.
But it isn't really clear what you're really trying to do.

Categories