1
\$\begingroup\$

I am working on a simple loop that when a user enters text into a textbox if certain words or phrases are entered, xyz is triggered. I have my code working as needed but am looking for way to improve / optimize it.

$("#input_test").on("keyup",function(){
    var common = false;

    var inputVal = $(this).val().toLowerCase().replace(/[^a-zA-Z ]/g, "");

    var triggerWords = ['hello there','world', 'hello world', 'how do I turn this on', 'general kenobi'];


        var common = triggerWords.some(function(v){
           return inputVal.indexOf(v) !== -1;
        });

        if(common){
            $('#showme').show(300);
        }else{
            $('#showme').hide(300);
        }

});

Working JSFiddle

\$\endgroup\$
  • \$\begingroup\$ triggerWordsLen feels unnecessary, but other than that its pretty good (although its badly indented at least here), shouldn´t need any optimization unless you are dealing with thousands of words/phrases to check \$\endgroup\$ – juvian Apr 3 '18 at 16:39
  • \$\begingroup\$ @juvian the main reason I have triggerWordsLen is so it doesn’t calculate .length on every iteration. I wanted to get into this habit so that I don't do something crazy like for(var i = 0; i < someComplexFunction(); i++) in the future :D \$\endgroup\$ – Ardz Apr 3 '18 at 16:53
  • \$\begingroup\$ length is a property and not a function so it won´t calculate on every iteration. For more details check stackoverflow.com/a/17995000/3308055. But yes, for a function call you would do that so you can get used to it that way \$\endgroup\$ – juvian Apr 3 '18 at 17:03
  • 1
    \$\begingroup\$ @juvian super intersting read, thank you for that! \$\endgroup\$ – Ardz Apr 3 '18 at 17:11
  • \$\begingroup\$ One of your trigger words have an upper case letter, but you are converting the input to lower case, so it can never match. \$\endgroup\$ – Kruga Apr 4 '18 at 14:24
1
\$\begingroup\$

Performance

I have my code working as needed but am looking for way to improve / optimize it.

If you want to improve the performances there are a few things you can do.

Testing

The first thing is that you can run tests and see how code style and logic can effect performance. Javascript testing is a little tricky. You can not just run the functions. You need to make sure you are not being tricked by the optimiser

I have added a basic Javascript performance tester to the bottom to highlight the following points.

Slow iterators

The Array iteration functions such as Array.some are generally slower than writing a standard for, for of or while loop. The reason is because functions incur a lot of additional overhead each time they are call. They need their own memory, special structures to hold the current scope, and closure values. All this is added to the JavaScript heap and called the function's "context".

On top of that the actual function Array.some has to do some checks to make sure that the arguments you pass are correct and can be used. This all adds to the time to run the function.

For of loops are faster

Using a for...of loop avoids all that overhead and for this example is about 60% faster (on Chrome)

var triggerWords = ['hello there','world', 'hello world', 'how do I turn this on', 'general kenobi'];
for(const  word of triggerWords ){
     if(inputVal.indexOf(word) {
         $("#showme).show(300);
         return;  // all done exit the key up event
     }
}
$("#showme).hide(300);

Native power!

But the problem is still the iteration. Using JavaScript to check each string in turn is never going to utilize a modern CPU's true power. But there is a way to have the search done using pure native code.

RegExp will do the test in a fraction of the time.

Building the regular expression from array of string is expensive, so you would not use this method if the search string (triggerWords) changes each test. But if the the search is the same each time, you can build the regExp at the start and you get around a 1000% performance boost.

// create at start;
var triggerWords = ['hello there','world', 'hello world', 'how do I turn this on', 'general kenobi'];
const reg = new RegExp(triggerWords.join("|"),"gi")

// inside the key event
if(reg.test(inputVal)){
    $("#showme).show(300);
} else {
    $("#showme).hide(300);
}

The cost of libraries

The easiest way to get a performance boost is to drop the libraries.

Without jQuery the code looks like.

const wordSearch = /hello there|world|hello world|how do I turn this on|general kenobi/gi;
inputTest.addEventListener("keyup", (event) => {
    if( wordSearch.test(inputTest.value.replace(/[^a-z ]/gi,"")) ) { 
         showme.classList.remove('hide');
    }else {
         showme.classList.add('hide');
    }
 });

And add a hide class to the CSS.

 .hide { display : none; }

The next snippet shows it all put together, copied from your fiddle (a page needing well over 1Mbyte of javascript to run) to a total of less than 1000bytes for all the code, css, and markup

const wordSearch = /hello there|world|hello world|how do I turn this on|general kenobi/gi;
inputTest.addEventListener("keyup", (event) => {
    if( wordSearch.test(inputTest.value.replace(/[^a-z ]/gi,"")) ) { 
         showme.classList.remove('hide');
    }else {
         showme.classList.add('hide');
    }
 });
#showme{
  padding-top:15px;
}
.muted{
 padding-top:15px;
 font-size:12px;
 color:#ccc;
}
.hide {
 display : none;
}
<p>
Enter a word or phrase to search:
</p>
Search: <input id ="inputTest"  type="text" placeholder="Word / Phrase">
<div id="showme" class="hide">
<iframe src="https://giphy.com/embed/Nx0rz3jtxtEre" width="240" height="120" frameBorder="0" class="giphy-embed" allowFullScreen></iframe>
</div>
<div class="muted">Indexed words / phrases:
<ul>
<li>hello there</li>
<li>world</li>
<li>hello</li>
<li>hello world</li>
<li>how do I turn this on</li>
<li>general kenobi</li>
</ul>
</div>

Example performance comparisons.

The snipped tests the 3 search methods Array.some, for..of and regExp

It uses the worst case scenario and runs the tests many 1000's of times. The test order is random to stop the optimizer slewing one method over another, and the result of each search are socked up by a global variable to stop the optimizer from just removing the inner loops completely

Let the test run until the mean times start to stabilize then click the page to compare the performance results.

/*===========================================================================
  Set up test data.
  One big array with 

  ===========================================================================*/
const rand = (max) => Math.random() * max | 0;
const innerLoopCount = 1000;
var c = "aabbaa";
var b = "skjht kjhlvuh svrtgkj abcdef ghijklmn opqrst uvwxyz1 234567890 ";
var a = [];
for(i = 0; i < 10; i ++){ a.push(b.substr(rand(b.length -10), rand(8) + 2)) };
var reg = new RegExp(a.join("|"),"gi");
document.addEventListener("click",() => stop = true )
var stop = false;

var soak = 0; // this is to prevent optimiser removing test code. It soaks up entropy generated by the functions :P
function testA(){
    var i = 0, found;
    const now = performance.now()
    while(i++ < innerLoopCount){
      var found = a.some(function(word) {
          return c.indexOf(word) > -1;
      })
    
      soak += found ? 1:0;
    }
    return performance.now() - now
}
function testB(){
    const now = performance.now()
    var i = 0, found;
    while(i++ < innerLoopCount){
      found = false;
      for(const word of a){
          if(c.indexOf(word) > -1) {
              found = true;
              break;
          }
      }
      soak += found ? 1:0;
    }
    return performance.now() - now;
}
function testC(){
    const now = performance.now()
    var i = 0, found;
    while(i++ < innerLoopCount){

      found = reg.test(c);
      //for(const word of a){
      //    if(c.indexOf(word) > -1) {
      //        found = true;
      //        break;
      //    }
     // }
      soak += found ? 1:0;
    }
    return performance.now() - now;
}


const tests = [
    { count : 0, time : 0, run : testA, status : statusElementA, name : " 'Array.some' : "},
    { count : 0, time : 0, run : testB, status : statusElementB, name : " 'for...of' : "},
    { count : 0, time : 0, run : testC, status : statusElementC, name : " 'regExp' : "},    
];
function testing() {
    var testsCount = tests.length;

    for(var i = 0;i < 100; i ++){
        var selTest = tests[Math.random() * testsCount | 0];
        selTest.time += selTest.run();
        selTest.count += 1;
    }
    for(const test of tests){
        test.mean = test.time / test.count;
        test.status.textContent = `${test.name} mean ${test.mean.toFixed(3)}ms over ${test.count} cycles.`;
    }
    if(!stop){
        
        setTimeout(testing,100);
    }else{
      tests.sort((a,b) => a.mean - b.mean);
      const slowest = tests[tests.length -1].mean;
      var win = " 🐇 <span class='winner'>Fastest</span>";
      let test;
      for(test of tests){      
          test.status.textContent += (test.mean / slowest * 100).toFixed(1) + "%"
          if( win ) {
              test.status.innerHTML += win;
              test.status.className = "champ";
              win = undefined;
          }
      }       
      test.status.innerHTML += " 🐢 <span class='looser'>Slowest</span>"
      test.status.className = "lost";
    }
}
setTimeout(testing,100);
body {
    font-family : arial;
    font size : large;
    text-align : center;
}
h4 {
   font-size : small;
}
.winner {
   color : green;

}
.looser {
   color : red;
}
.champ {
   background : #CFC;
   font-weight : 700;

   transition: all 0.5s ease-out;   
}
.lost {
   background : #FCC;
   transition: all 0.5s ease-out;   
}
    
  
<h3>Javascript Array.some V for...of V regExp</h3>
<h4>Click on the page to stop testing.</h4>
<div id="statusElementA"></div>
<div id="statusElementB"></div>
<div id="statusElementC"></div>

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ The regex should not have the global flag, since subsequent calls to .test will be wrong. Documentation \$\endgroup\$ – Kruga Apr 4 '18 at 14:19
  • \$\begingroup\$ This is a crazy amount of info! I love it! Thank you so much for helping me understand. \$\endgroup\$ – Ardz Apr 4 '18 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.