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.
Related
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() {
....
});
)();
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!
for some reason I do that every time because I find it clean. I declare variables on top to use them below. I do that even if I use them only once.
Here is an example (using jQuery framework) :
$("#tbListing").delegate("a.btnEdit", "click", function(e) {
var storeId = $(this).closest("tr").attr("id").replace("store-", ""),
storeName = $(this).closest("tr").find("td:eq(1)").html(),
$currentRow = $(this).closest("tr");
$currentRow.addClass("highlight");
$("#dialogStore")
.data("mode", "edit")
.data("storeId", storeId)
.data("storeName", storeName)
.dialog( "open" );
e.preventDefault();
});
I tend to do that in PHP too. Am I right if I believe it's not very memory efficient to do that ?
Edit: Thank you for all the answers. You have all given good answers. About that code optimisation now. Is that better now ?
$("#tbListing").delegate("a.btnEdit", "click", function(e) {
var $currentRow = $(this).closest("tr"),
storeId = this.rel, /*storing the storeId in the edit button's rel attribute now*/
storeName = $currentRow.find("td:eq(1)").html();
$currentRow.addClass("highlight");
$("#dialogStore")
.data("info", {
"mode" : "edit",
"storeId" : storeId,
"storeName" : storeName
}) /*can anyone confirm that overusing the data isn't very efficient*/
.dialog( "open" );
e.preventDefault();
});
Sorry, are you asking if it's OK to declare variables even if you're using them once?
Absolutely! It makes the code a million times more readable if you name things properly with a variable. Readability should be your primary concern. Memory efficiency should only be a concern if it proves problematic.
As Knuth said,
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
If you're asking more about declaring the variables at the beginning of the function, rather than where they are first used, then Emmett has it right - Crockford recommends doing this in JavaScript to avoid scope-related confusion. Whether it's worth it in PHP is a purely subjective question I'd say, but there's nothing wrong with keeping your PHP and JS coding styles similar.
One more CS quote (from Abelson and Sussman's SICP):
programs must be written for people to read, and only incidentally for machines to execute.
It's not bad practice.
The var statements should be the first statements in the function body.
JavaScript does not have block scope,
so defining variables in blocks can
confuse programmers who are
experienced with other C family
languages. Define all variables at the
top of the function.
http://javascript.crockford.com/code.html
Declaring variables at the top is a good thing to do. It makes the code more readable. In your particular example, you could replace $(this).closest('tr') witha variable, as suggested int eh comments, but in general I find code with descriptive variable names all in one place very readable.
nah, I'd say you're doing exactly the right thing.
As #Caspar says, you could simplify your code by setting $currentRow first and using that instead of $(this).closest("tr") in the other two lines. And there may be a few other things you could improve. But setting vars at the begining of a function the way you've done it is absolutely a good thing.
Particuarly good because you've done it inside the function, so they're local variables, which means they get thrown away at the end of the function, so no memory usage issues there.
If you'd set them as global vars, it might have been more of an issue, although to be honest even then, since you're just setting pointers to an existing object, it wouldn't be using a huge amount of memory even then (though it would be polluting the global namespace, which isn't good)
It totaly makes sense to me to use it here.
What would be the alternative? How can i generaly avoid to use them and most of all why is it bad according to jsLint to make use of globals.
(function($){
$(function(){
$body = $('body'); //this is the BAD Global
$.each(somearray ,function(){ $body.dosomething() });
if (something){
$body.somethingelse();
}
});
}(jQuery));
Can you help me understand this? And give me a better solution?
Globals are bad because they don't cause problems right away. Only later, after you have used them all over the place, they will cause very ugly problems - which you can't solve anymore without writing your code from scratch.
Example: You use $body to define some functions. That works fine. But eventually, you also need a value. So you use $body.foo. Works fine. Then you add $body.bar. And then, weeks later, you need another value so you add $body.bar.
You test the code and it seems to work. But in fact, you have "added" the same variable twice. This is no problem because JavaScript doesn't understand the concept of "create a new variable once." It just knows "create unless it already exists." So you use your code and eventually, one function will modify $body.bar breaking another function. Even to find the problem will take you a lot of time.
That's why it is better to make sure that variables can only been seen on an as needed basis. This way, one function can't break another. This becomes more important as your code grows.
you should define it with var $body, then it would be local in the scope of that function, without var it could be overwritten by everybody
(function($){
$(function(){
var $body = $('body'); //this is the local variable
$.each(somearray ,function(){ $body.dosomething() });
if (something){
$body.somethingelse();
}
});
}(jQuery));
Globale variables could clash with other Scripts or be overwritten. When you don't need a global, it's advisable to avoid them. Simply use var (or let if your JS-Version-Support is greater than 1.7):
(function() {
var foo = 'bar';
alert(foo);
})();
You could rewrite that as
var $body = $('body');
That (the use of the var keyword) would make it a local variable, which is enough for your purposes. It will still be within scope in your each callback.
The reason it's bad to use globals is that it can be overwritten by anything else. For your code to scale well, it becomes dependent on what other scripts you use. It's preferable to keep the script as self-sufficient as possible, with as little as possible dependencies pointing to the world outside of it.
jsLint is very stringent. It's probably not necessary to get too hung up about it.
But if you feel bad, you can do it just like how you scoped jQuery:
(function($){
$(function(){
$.each(somearray ,(function($body){ $body.dosomething() })($('body'));
if (something){
$('body').somethingelse();
}
});
}(jQuery));
I find myself doing a lot of this kind of JQuery:
$('.filter-topic-id').each(function () {
var me = $(this);
if (me.text() == topics[k]) {
me.parent().show();
}
});
I store $(this) in a variable called me because I'm afraid it will re-evaluate $(this) for no reason. Are the major JavaScript engines smart enough to know that it doesn't have to re-evaluate it? Maybe even JQuery is smart enough somehow?
They are not smart enough to know not to revaluate $(this) again, if that's what your code says. Caching a jQuery object in a variable is a best practice.
If your question refers to your way in the question compared to this way
$('.filter-topic-id').each(function () {
if ($(this).text() == topics[k]) { // jQuery object created passing in this
$(this).parent().show(); // another jQuery object created passing in this
}
});
your way is the best practice.
Are the major JavaScript engines smart enough to know that it doesn't have to re-evaluate it?
No. But if you are using jQuery you are presumably aiming for readability rather than necessarily maximum performance.
Write whichever version you find easiest to read and maintain, and don't worry about micro-optimisations like this until your page is too slow and you've exhausted other more significant sources of delay. There is not a lot of work involved in calling $(node).
You could try to profile your code with Firebug and see if using $(this) many times slows your app or not
There is no good way that a javascript can determine that the following is true:-
fn(x) == fn(x);
Even if this was possible not calling the second fn could only be valid if it could be guaraneed that fn has not have other side-effects. When there is other code between calls to fn then its even more difficult.
Hence Javascript engines have no choice but to actually call fn each time it is invoked.
The overhead of calling $() is quite small but not insignificant. I would certainly hold the result in a local variable as you are doing.
this is just a reference to the current DOM element in the iteration, so there's little or no overhead involved when calling $(this). It just creates a jQuery wrapper around the DOM element.
I think you'll find that calling the jQuery function by passing a dom element is perhaps the least-intensive of ways to construct the object. It doesn't have to do any look-ups or query the DOM, just wrap it and return it.
That said, it definitely doesn't hurt to use the method you're using there, and that's what I do all the time myself. It certainly helps for when you create nested closures:
$('div').click(function() {
var $this = $(this);
$this.find("p").each(function() {
// here, there's no reference to the div other than by using $this
alert(this.nodeName); // "p"
});
});