JavaScript & string length: why is this simple function slow as hell? - javascript

i'm implementing a charcounter in the UI, so a user can see how many characters are left for input.
To count, i use this simple function:
function typerCount(source, layerID)
{
outPanel = GetElementByID(layerID);
outPanel.innerHTML = source.value.length.toString();
}
source contains the field which values we want to meassure
layerID contains the element ID of the object we want to put the result in (a span or div)
outPanel is just a temporary var
If i activate this function, while typing the machine really slows down and i can see that FF is using one core at 100%. you can't write fluently because it hangs after each block of few letters.
The problem, it seems, may be the value.length() function call in the second line?
Regards

I can't tell you why it's that slow, there's just not enough code in your example to determine that. If you want to count characters in a textarea and limit input to n characters, check this jsfiddle. It's fast enough to type without obstruction.

It could be having problems with outPanel. Every time you call that function, it will look up that DOM node. If you are targeting the same DOM node, that's very expensive for the browser if it's doing that every single time you type a character.
Also, this is too verbose:
source.value.length.toString();
This is sufficient:
source.value.length;
JavaScript is dynamic. It doesn't need the conversion to a string.

I doubt your problem is with the use of innerHTML or getElementById().
I would try to isolate the problem by removing parts of the function and seeing how the cpu is used. For instance, try it all these ways:
var len;
function typerCount(source, layerID)
{
len = source.value.length;
}
function typerCount(source, layerID)
{
len = source.value.length.toString();
}
function typerCount(source, layerID)
{
outPanel = GetElementByID(layerID);
outPanel.innerHTML = "test";
}

As artyom.stv mentioned in the comments, cache the result of your GetElementByID call. Also, as a side note, what is GetElementByID doing? Is it doing anything else other than calling document.getElementById?
How would you cache this you say?
var outPanelsById = {};
function getOutPanelById(id) {
var panel = outPanelsById[id];
if (!panel) {
panel = document.getElementById(id);
outPanelsById[id] = panel;
}
return panel;
};
function typerCount(source, layerId) {
var panel = getOutPanelById(layerId);
panel.innerHTML = source.value.length.toString();
};
I'm thinking there has to be something else going on though, as even getElementById calls are extremely fast in FF.
Also, what is "source"? Is it a DOMElement? Or is it something else?

Related

Javascript: Flexibility of function parameters?

The description of Javascript function parameters on W3Schools wasn't very clear, so I just want to clarify.
From my understanding, there isn't a type restriction; the "real value" of the parameters are passed into the method. Is there a way to pass objects or elements? Or is that what is meant by "real value"?
For example:
The function displayText meant to take input text and set a display to show a new word in the given input text, going to the next word every time it's called.
function displayText() {
var text = document.getElementById("words").value;
// Since text is initialized
// every time the method is called,
// it will always start at the beginning of the text area.
// Not sure how to fix this since making `text`
// a global variable doesn't work
var list = text.split(/[ \t\n]+/);
displayNext(list, "display");
}
There is a "helper" method, displayNext, which is supposed to shift to the next word in the list and sets the display to that word.
function displayNext(list, textboxID) {
document.getElementById(textboxID).innerHTML = list.shift();
}
This isn't working as it is intended. I'm fairly sure it's because I've mucked up something with the parameters, since displayNext sets innerHTML to null. list must have not passed properly. I'm not sure how to fix this.
I'm sure there's a more efficient way to do this, but this is a good opportunity to learn how Javascript parameters actually work, so I thought I'd ask.
JSFiddle
Based on the comments in your code, it sounds like you want displayText() to display the "next" word each time. To do that, you have to create some place to store some state about which word is the next one to display. As you have it now, you create a new array every time and always display the first word.
The simplest way is to create a variable outside your function in some lasting scope where you store the word number:
var wordNum = 0;
function displayText() {
var text = document.getElementById("words").value;
var list = text.split(/\s+/);
if (list.length !== 0) {
// make sure we aren't off the end of the list
wordNum = wordNum % list.length;
displayNext(list[wordNum++], "display");
}
}
function displayNext(text, textboxID) {
document.getElementById(textboxID).innerHTML = text;
}
For a lot more info about arguments to Javascript functions and even how you can detect what arguments were passed or overload arguments, see this answer: How to overload functions in javascript? and for more info about how arguments are passed: Javascript by reference vs. by value

how to get the total number of unique fucntions in a JS script

I am looking at the source of pace.js and it has a pretty long source code of about a thousand lines. You can view the source here.
I need to debug this code. Is there any tool or method in JavaScript using which one can identify how many unique functions are there in a given plugin? I found one way which is:
Paste the code in a text editor
Identify each function individually
Paste a console.log("i am so and so function").
Run the script and copy paste the result from the console in a text editor
Count the number of functions
Is there a easier way to do this?
This approach first finds all the functions in the window object. Then passes those function references to 'getInnerFunction()' which matches the function against a regular expression to detect any inner functions. Finally the count of functions is returned.
However it will not be able to detect inner functions of native function present in the browser, since they return
function FUNCTION NAME {
[native code]
}
this as the to string output.
For other cases this should work. Just call fnCount() and you will receive the number of functions present (subtract 2 from the result to exclude these 2 functions).
** Please correct me if there is any problem with the function matching regular expression.
function fnCount(){
var keys = Object.keys(window);
var property;
var count = 0;
for(var i=0;i<keys.length; i++){
property = window[keys[i]];
if(typeof(property) === 'function'){
count += getInnerFunction(property);
}
}
return count;
}
function getInnerFunction(property){
var fn = property.toString();
var fnCount = fn.match(/function.*\(.*\).*{.*/g).length;
return fnCount;
}
Open notepad++, press CTRL+F, type function, click Find all...

Improving performance of javascript intervals on IE8

I'm using javascript loop (using setInterval) that runs through a list of search results, highlighting the search term by adding a css styled <span> around search hits as it goes. I'm using setInterval like this to release control of the browser while it does this.
In Chrome and Firefox this works well - even with a setInterval parameter of 10-20ms; and the user has full control of the browser (i.e. scrolling, clicking links etc.) while the results are rapidly highlighted:
mylooper = setInterval(function() {
// my functionality is here
},15); // 15ms
Unfortunately, when using the dreaded IE8, the browser locks up and takes a really long time to add the <span>'s and style the search results. It also takes a long time just to load the page in the first place - shortened a great deal when this script is removed.
So far I've tried:
changing the interval values (I've read that IE8 doesn't detect intervals of sub 15ms);
using setTimeout instead of setInterval;
removing the interval to check that this is in fact what is causing the slow-down (it is!); and
swearing about Internet Explorer a lot;
var highlightLoop;
var index = 0;
highlightLoop = setInterval(function () {
var regex = RegExp(regexPhrase, "gi"); // regexPhase created elsewhere
var searchResults = resultElements.eq(index).get(0); // run through resultElements which contain alll the nodes with search results in them.
findAndReplaceDOMText( // a function that does the searching and inserting of styling
regex,
searchResults,
function (fill, matchIndex) {
called = true;
var span = document.createElement("span");
span.className = "result-highlight";
span.innerHTML = fill;
return span;
}
);
if (index == resultElements.length || searchTermUpdated == true) { // stop interval loop when search term changes or we reach the end of results - variable set elsewhere.
searchTermUpdated = false;
clearInterval(highlightLoop); // stop the loop
}
index++
}
}, 50); // 50ms does not improve performance.
Any advice on workarounds for this kind of javascripting in IE would be massively appreciated. Thanks all.
I believe you may be able to improve the performance by tweaking findAndReplaceDOMText, and maybe its callback too. I suppose findAndReplaceDOMText appends the element returned by the callback to the DOM, from within a loop of all matches. If it's doing that inside a loop, try to move it outside the loop, and apply the all changes to the DOM at once. That should result in better performance, as repainting the page after each DOM update is expensive.
Try this recursive approach instead:
get a list of all elements to be acted upon into array X (one time cost)
while the array X has length, keep repeating the next actions
shift the first element off the array
process the single element
start this process again with the new array X (now Xn - 1 length) on a setTimeout
The code looks like this in general
function processArray(array) {
var element = array.shift();
processElement(element);
if (array)
setTimeout(function(){processArray(array);},15ms);
}
There might be something else to be done with this recursion, but it works fairly well in all browsers and never blocks, because you're only initiating the repeat when the last one has had time to finish.

Optimizing Javascript Loop for Wheel Game

I have a game I'm creating where lights run around the outside of a circle, and you must try and stop the light on the same spot three times in a row. Currently, I'm using the following code to loop through the lights and turn them "on" and "off":
var num_lights = 20;
var loop_speed = 55;
var light_index = 0;
var prevent_stop = false; //If true, prevents user from stopping light
var loop = setTimeout(startLoop, loop_speed);
function startLoop() {
prevent_stop = false;
$(".light:eq(" + light_index + ")").css("background-color", "#fff");
light_index++;
if(light_index >= num_lights) {
light_index = 0;
}
$(".light:eq(" + light_index + ")").css("background-color", "red");
loop = setTimeout(startLoop, loop_speed);
}
function stopLoop() {
clearTimeout(loop);
}
For the most part, the code seems to run pretty well, but if I have a video running simultaneously in another tab, the turning on and off of the lights seems to chug a bit. Any input on how I could possibly speed this up would be great.
For an example of the code from above, check out this page: http://ericditmer.com/wheel
When optimizing the thing to look at first is not doing twice anything you only need to do once. Looking up an element from the DOM can be expensive and you definitely know which elements you want, so why not pre-fetch all of them and void doing that multiple times?
What I mean is that you should
var lights = $('.light');
So that you can later just say
lights.eq(light_index).css("background-color", "red");
Just be sure to do the first thing in a place which keeps lights in scope for the second.
EDIT: Updated per comment.
I would make a global array of your selector references, so they selector doesn't have to be executed every time the function is called. I would also consider swapping class names, rather than attributes.
Here's some information of jQuery performance:
http://www.componenthouse.com/article-19
EDIT: that article id quite old though and jQuery has evolved a lot since. This is more recent: http://blog.dynatrace.com/2009/11/09/101-on-jquery-selector-performance/
You could try storing the light elements in an array instead of using a selector each time. Class selectors can be a little slow.
var elements = $('.light');
function startLoop() {
prevent_stop = false;
$(elements[light_index]).css('background-color', '#fff');
...
}
This assumes that the elements are already in their intended order in the DOM.
One thing I will note is that you have used a setTimeout() and really just engineered it to behave like setInterval().
Try using setInterval() instead. I'm no js engine guru but I would like to think the constant reuse of setTimeout has to have some effect on performance that would not be present using setInterval() (which you only need to set once).
Edit:
Curtousy of Diodeus, a related post to back my statement:
Related Stack Question - setTimeout() vs setInterval()
OK, this includes some "best practice" improvements, if it really optimizes the execution speed should be tested. At least you can proclaim you're now coding ninja style lol
// create a helper function that lend the array reverse function to reverse the
// order of a jquery sets. It's an object by default, not an array, so using it
// directly would fail
$.fn.reverse = Array.prototype.reverse;
var loop,
loop_speed = 55,
prevent_stop = false,
// prefetch a jquery set of all lights and reverses it to keep the right
// order when iterating backwards (small performance optimization)
lights = $('.light').reverse();
// this named function executes as soon as it's initialized
// I wrapped everything into a second function, so the variable prevent_stop is
// only set once at the beginning of the loop
(function startLoop() {
// keep variables always in the scope they are needed
// changed the iteration to count down, because checking for 0 is faster.
var num_lights = light_index = lights.length - 1;
prevent_stop = false;
// This is an auto-executing, self-referencing function
// which avoids the 55ms delay when starting the loop
loop = setInterval((function() {
// work with css-class changing rather than css manipulation
lights.eq( light_index ).removeClass('active');
// if not 0 iterate else set to num_lights
light_index = (light_index)? --light_index:num_lights;
lights.eq( light_index ).addClass('active');
// returns a referenze to this function so it can be executed by setInterval()
return arguments.callee;
})(), loop_speed);
})();
function stopLoop() {
clearInterval(loop);
}
Cheers neutronenstern

IE Issue with Javascript Regex replacement

r = r.replace(/<TR><TD><\/TD><\/TR>/gi, rider_html);
...does not work in IE but works in all other browsers.
Any ideas or alternatives?
I've come to the conclusion that the variable r must not have the value in it you expect because the regex replacement should work fine if there is actually a match. You can see in this jsFiddle that the replace works fine if "r" actually has a match in it.
This is the code from fiddle and it shows the proper replacement in IE.
var r = "aa<TR><TD></TD></TR>bb";
var rider_html = " foo ";
r = r.replace(/<TR><TD><\/TD><\/TR>/gi, rider_html);
alert(r);
So, we can't really go further to diagnose without knowing what the value of "r" is and where it came from or knowing something more specific about the version of IE that you're running in (in which case you can just try the fiddle in that version yourself).
If r came from the HTML of the document, then string matching on it is a bad thing because IE does not keep the original HTML around. Instead it reconstitutes it when needed from the parsed page and it puts some things in different order (like attributes), different or no quotes around attributes, different capitalization, different spacing, etc...
You could do something like this:
var rows = document.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
var children = rows[i].children;
if (children.length === 1 && children[0].nodeName.toLowerCase() === 'td') {
children[0].innerHTML = someHTMLdata
}
}
Note that this sets the value of the table cell, rather than replacing the whole row. If you want to do something other than this, you'll have to use DOM methods rather than innerHTML and specify exactly what you actually want.

Categories

Resources