I'm trying to figure out what is best and fastest implementation of a browser-like find and highlight function inside a website using JavaScript. This function is for one HTML element without any children. Of course it can be expanded to elements with children with some simple loops. That is not part of question. I wrote this small code to make something working here:
CSS
span.highlight{background:yellow; padding:3px;}
HTML
<input type="search"/>
<p>The Ama...</p>
JavaScript
var s = document.querySelector('input[type="search"]'),
p = document.querySelector('p'),
find = function(){
var words = p.innerText.split(' ');
for(var i=0; i<words.length; i++){
if(words[i].toLowerCase() == s.value.toLowerCase()){
words[i] = '<span class="highlight">' + words[i] + "</span>";
p.innerHTML = words.join(' ');
}
else{
}
}
}
s.addEventListener('keydown', find , false);
s.addEventListener('keyup', find , false);
This code is working fine. Checkout live example here . But this is not fast and sometimes crash the browser. How can I make this functionality with a better approach?
Please note I don't want a full code review here. So don't remind me that querySelector
is not supported in IE7 and same things.
1 Answer 1
Let's start by optimising your code. Simply said, loops are the danger here.
- A
while
loop can be quicker than afor
loop, especially in cases like this. Get the length once. - Avoid looking into arrays. Do
word = words[i]
once, which is much faster. - You are joining in every iteration. You should join once only after you have updated your array.
- I guess the empty
else
isn't done yet.
var s = document.querySelector('input[type="search"]'),
p = document.querySelector('p'),
find = function(){
var words = p.innerText.split(' '),
i = words.length,
word = '';
while(--i) {
word = words[i];
if(word.toLowerCase() == s.value.toLowerCase()){
words[i] = '<span class="highlight">' + word + "</span>";
}
else{
}
}
p.innerHTML = words.join(' ');
}
s.addEventListener('keydown', find , false);
s.addEventListener('keyup', find , false);
Last but surely not least, the page you want to read.
-
\$\begingroup\$ I'm curious if it would be faster to use a stack.
while (words) { word = words.pop(); ...}
\$\endgroup\$kojiro– kojiro2011年11月28日 23:39:32 +00:00Commented Nov 28, 2011 at 23:39 -
\$\begingroup\$ There's really no need for another function call. You want to avoid those. Plus popping out the value doesn't benefit you. It's cute, but not faster I'm afraid :) \$\endgroup\$lcampanis– lcampanis2011年11月29日 09:43:17 +00:00Commented Nov 29, 2011 at 9:43
-
\$\begingroup\$ the negative while for speed is a good option likely due to its deterministic nature \$\endgroup\$Mark Schultheiss– Mark Schultheiss2016年02月06日 19:42:35 +00:00Commented Feb 6, 2016 at 19:42
find()
? Why not just do that once ononload
? My javascript-fu is pretty low, so even if it's something obvious please explain... \$\endgroup\$words
array with converting it to lower case. I'm keeping element's actual text in that array \$\endgroup\$Array.reduce
here. Something likep.innerHTML = p.innerText.split(' ').reduce(function (prev, cur) {return prev + ' ' + ((cur.toLowerCase() === s.value.toLowerCase()) ? '<span class="highlight">' + cur + "</span>" : c;}, "")
. While so many browsers have yet to implement a fast reduce it's not the best approach, so I'm not giving it as an answer, but food for thought. \$\endgroup\$