why does replacing InnerHTML with innerText causes >15X drop in performance - javascript

this question arises from my previous post why a tiny reordering of DOM Read/Write operations causes a huge performance difference .
consider the following code:
function clearHTML(divs) {
Array.prototype.forEach.call(divs, function (div) {
contents.push(div.innerHTML);
div.innerHTML = "";
});
}
function clearText(divs) {
Array.prototype.forEach.call(divs, function (div) {
contents.push(div.innerText); //innerText in place of innerHTML
div.innerHTML = "";
});
}
http://jsfiddle.net/pindexis/ZZrYK/
My test results for n=100:
ClearHTML: ~1ms
ClearText: ~15ms
for n=1000:
ClearHTML: ~4ms
ClearText: ~1000ms
I tested the code on google chrome and IE and get similar results (Firefox does not support innerText).
Edit :
the difference between the two functions is not caused by the slowness of innerText function compared to innerHTML, that's for sure ( I tried removing div.innerHTML ="" and got boost in performance), there's strange browser reflow taking place here.

As MDN explains:
As innerText is aware of CSS styling, it will trigger a reflow, whereas textContent will not.
Using textContent instead of innerText does not cause reflow and is also fast. IE9+ also supports it as does FFX/Chrome.

The difference almost certainly comes from the extra effort it takes to get the InnerText (which I believe removes extraneous tags and just returns the text within an element). InnerHTML on the other hand simply returns data that has already been parsed and understood by the browser.

Related

Looping through arrays with JavaScript on Firefox issue [duplicate]

I have some JavaScript code that works in IE containing the following:
myElement.innerText = "foo";
However, it seems that the 'innerText' property does not work in Firefox. Is there some Firefox equivalent? Or is there a more generic, cross browser property that can be used?
Update: I wrote a blog post detailing all the differences much better.
Firefox uses W3C standard Node::textContent, but its behavior differs "slightly" from that of MSHTML's proprietary innerText (copied by Opera as well, some time ago, among dozens of other MSHTML features).
First of all, textContent whitespace representation is different from innerText one. Second, and more importantly, textContent includes all of SCRIPT tag contents, whereas innerText doesn't.
Just to make things more entertaining, Opera - besides implementing standard textContent - decided to also add MSHTML's innerText but changed it to act as textContent - i.e. including SCRIPT contents (in fact, textContent and innerText in Opera seem to produce identical results, probably being just aliased to each other).
textContent is part of Node interface, whereas innerText is part of HTMLElement. This, for example, means that you can "retrieve" textContent but not innerText from text nodes:
var el = document.createElement('p');
var textNode = document.createTextNode('x');
el.textContent; // ""
el.innerText; // ""
textNode.textContent; // "x"
textNode.innerText; // undefined
Finally, Safari 2.x also has buggy innerText implementation. In Safari, innerText functions properly only if an element is
neither hidden (via style.display == "none") nor orphaned from the document. Otherwise, innerText results in an empty string.
I was playing with textContent abstraction (to work around these deficiencies), but it turned out to be rather complex.
You best bet is to first define your exact requirements and follow from there. It is often possible to simply strip tags off of innerHTML of an element, rather than deal with all of the possible textContent/innerText deviations.
Another possibility, of course, is to walk the DOM tree and collect text nodes recursively.
Firefox uses the W3C-compliant textContent property.
I'd guess Safari and Opera also support this property.
If you only need to set text content and not retrieve, here's a trivial DOM version you can use on any browser; it doesn't require either the IE innerText extension or the DOM Level 3 Core textContent property.
function setTextContent(element, text) {
while (element.firstChild!==null)
element.removeChild(element.firstChild); // remove all existing content
element.appendChild(document.createTextNode(text));
}
jQuery provides a .text() method that can be used in any browser. For example:
$('#myElement').text("Foo");
As per Prakash K's answer Firefox does not support the innerText property. So you can simply test whether the user agent supports this property and proceed accordingly as below:
function changeText(elem, changeVal) {
if (typeof elem.textContent !== "undefined") {
elem.textContent = changeVal;
} else {
elem.innerText = changeVal;
}
}
A really simple line of Javascript can get the "non-taggy" text in all main browsers...
var myElement = document.getElementById('anyElementId');
var myText = (myElement.innerText || myElement.textContent);
Note that the Element::innerText property will not contain the text which has been hidden by CSS style "display:none" in Google Chrome (as well it will drop the content that has been masked by other CSS technics (including font-size:0, color:transparent, and a few other similar effects that cause the text not to be rendered in any visible way).
Other CSS properties are also considered :
First the "display:" style of inner elements is parsed to determine if it delimits a block content (such as "display:block" which is the default of HTML block elements in the browser's builtin stylesheet, and whose behavior as not been overriden by your own CSS style); if so a newline will be inserted in the value of the innerText property. This won't happen with the textContent property.
The CSS properties that generate inline contents will also be considered : for example the inline element <br \> that generates an inline newline will also generate an newline in the value of innerText.
The "display:inline" style causes no newline either in textContent or innerText.
The "display:table" style generates newlines around the table and between table rows, but"display:table-cell" will generate a tabulation character.
The "position:absolute" property (used with display:block or display:inline, it does not matter) will also cause a line break to be inserted.
Some browsers will also include a single space separation between spans
But Element::textContent will still contain ALL contents of inner text elements independantly of the applied CSS even if they are invisible. And no extra newlines or whitespaces will be generated in textContent, which just ignores all styles and the structure and inline/block or positioned types of inner elements.
A copy/paste operation using mouse selection will discard the hidden text in the plain-text format that is put in the clipboard, so it won't contain everything in the textContent, but only what is within innerText (after whitespace/newline generation as above).
Both properties are then supported in Google Chrome, but their content may then be different. Older browsers still included in innetText everything like what textContent now contains (but their behavior in relation with then generation of whitespaces/newlines was inconsistant).
jQuery will solve these inconsistencies between browsers using the ".text()" method added to the parsed elements it returns via a $() query. Internally, it solves the difficulties by looking into the HTML DOM, working only with the "node" level. So it will return something looking more like the standard textContent.
The caveat is that that this jQuery method will not insert any extra spaces or line breaks that may be visible on screen caused by subelements (like <br />) of the content.
If you design some scripts for accessibility and your stylesheet is parsed for non-aural rendering, such as plugins used to communicate with a Braille reader, this tool should use the textContent if it must include the specific punctuation signs that are added in spans styled with "display:none" and that are typically included in pages (for example for superscripts/subscripts), otherwise the innerText will be very confusive on the Braille reader.
Texts hidden by CSS tricks are now typically ignored by major search engines (that will also parse the CSS of your HTML pages, and will also ignore texts that are not in contrasting colors on the background) using an HTML/CSS parser and the DOM property "innerText" exactly like in modern visual browsers (at least this invisible content will not be indexed so hidden text cannot be used as a trick to force the inclusion of some keywords in the page to check its content) ; but this hidden text will be stil displayed in the result page (if the page was still qualified from the index to be included in results), using the "textContent" property instead of the full HTML to strip the extra styles and scripts.
IF you assign some plain-text in any one of these two properties, this will overwrite the inner markup and styles applied to it (only the assigned element will keep its type, attributes and styles), so both properties will then contain the same content. However, some browsers will now no longer honor the write to innerText, and will only let you overwrite the textContent property (you cannot insert HTML markup when writing to these properties, as HTML special characters will be properly encoded using numeric character references to appear literally, if you then read the innerHTML property after the assignment of innerText or textContent.
myElement.innerText = myElement.textContent = "foo";
Edit (thanks to Mark Amery for the comment below): Only do it this way if you know beyond a reasonable doubt that no code will be relying on checking the existence of these properties, like (for example) jQuery does. But if you are using jQuery, you would probably just use the "text" function and do $('#myElement').text('foo') as some other answers show.
innerText has been added to Firefox and should be available in the FF45 release: https://bugzilla.mozilla.org/show_bug.cgi?id=264412
A draft spec has been written and is expected to be incorporated into the HTML living standard in the future: http://rocallahan.github.io/innerText-spec/, https://github.com/whatwg/html/issues/465
Note that currently the Firefox, Chrome and IE implementations are all incompatible. Going forward, we can probably expect Firefox, Chrome and Edge to converge while old IE remains incompatible.
See also: https://github.com/whatwg/compat/issues/5
This has been my experience with innerText, textContent, innerHTML, and value:
// elem.innerText = changeVal; // works on ie but not on ff or ch
// elem.setAttribute("innerText", changeVal); // works on ie but not ff or ch
// elem.textContent = changeVal; // works on ie but not ff or ch
// elem.setAttribute("textContent", changeVal); // does not work on ie ff or ch
// elem.innerHTML = changeVal; // ie causes error - doesn't work in ff or ch
// elem.setAttribute("innerHTML", changeVal); //ie causes error doesn't work in ff or ch
elem.value = changeVal; // works in ie and ff -- see note 2 on ch
// elem.setAttribute("value", changeVal); // ie works; see note 1 on ff and note 2 on ch
ie = internet explorer, ff = firefox, ch = google chrome.
note 1: ff works until after value is deleted with backspace - see note by Ray Vega above.
note 2: works somewhat in chrome - after update it is unchanged then you click away and click back into the field and the value appears.
The best of the lot is elem.value = changeVal; which I did not comment out above.
As in 2016 from Firefox v45, innerText works on firefox, take a look at its support: http://caniuse.com/#search=innerText
If you want it to work on previous versions of Firefox, you can use textContent, which has better support on Firefox but worse on older IE versions: http://caniuse.com/#search=textContent
What about something like this?
//$elem is the jQuery object passed along.
var $currentText = $elem.context.firstChild.data.toUpperCase();
** I needed to make mine uppercase.
Just reposting from comments under the original post. innerHTML works in all browsers. Thanks stefita.
myElement.innerHTML = "foo";
found this here:
<!--[if lte IE 8]>
<script type="text/javascript">
if (Object.defineProperty && Object.getOwnPropertyDescriptor &&
!Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get)
(function() {
var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
Object.defineProperty(Element.prototype, "textContent",
{ // It won't work if you just drop in innerText.get
// and innerText.set or the whole descriptor.
get : function() {
return innerText.get.call(this)
},
set : function(x) {
return innerText.set.call(this, x)
}
}
);
})();
</script>
<![endif]-->
It's also possible to emulate innerText behavior in other browsers:
if (((typeof window.HTMLElement) !== "undefined") && ((typeof HTMLElement.prototype.__defineGetter__) !== "undefined")) {
HTMLElement.prototype.__defineGetter__("innerText", function () {
if (this.textContent) {
return this.textContent;
} else {
var r = this.ownerDocument.createRange();
r.selectNodeContents(this);
return r.toString();
}
});
HTMLElement.prototype.__defineSetter__("innerText", function (str) {
if (this.textContent) {
this.textContent = str;
} else {
this.innerHTML = str.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/\n/g, "<br />\n");
}
});
}

Jquery to pure javascript and how the intepreter looks up with dom for elements

I have a couple of questions about the inner workings of JavaScript and how the interpreter handles certain queries
The following JQuery will correctly get all the images that contain the word "flowers" in the src
$("img[src*='flowers']");
Jquery makes this very simple but what the pure javascript version?
We have a very large DOM. I take it if I do $("*[src*='flowers']") this will greatly affect performance (wildcard element). I'm interested in what the Javascript interpreter does differently between $("img[src*='flowers']") and $("*[src*='flowers']")
Well, the clearest way to explain the difference is to show you how you'd write both DOM queries in plain JS:
jQuery's $("img[src*='flowers']"):
var images = document.getElementsByTagName('img');//gets all img tags
var result = [];
for (var i = 0; i < images.length;i++)
{
if (images[i].getAttribute('src').indexOf('flowers') !== -1)
{//if img src attribute contains flowers:
result.push(images[i]);
}
}
So as you can see, you're only searching through all img elements, and checking their src attribute. If the src attribute contains the substring "flowers", the add it to the result array.
Whereas $("[src*='flowers']") equates to:
var all = document.getElementsByTagName('*');//gets complete DOM
var result = [];
for (var i =0; i <all.length; i++)
{
if (all[i].hasAttribute('src') && all[i].getAttribute('src').indexOf('flowers') !== -1)
{//calls 2 methods, for each element in DOM ~= twice the overhead
result.push(all[i]);
}
}
So the total number of nodes will be a lot higher than just the number of img nodes. Add to that the fact that you're calling two methods (hasAttribute and getAttibute) for all img elements (thanks to short-circuit evaluation, all elements that don't have an src attribute, the getAttribute method won't be called) there's just a lot more going on behind the scenes in order for you to get the same result.
note:
I'm not saying that this is exactly how jQuery translates the DOM queries for you, it's a simplified version, but the basic principle stands. The second version (slower version) just deals with a lot more elements than the first. That's why it's a lot slower, too.
When you use *[src..] you will try to find all elements from the page, but when you use $("img[src..]") the search is restricted to img elements, like this: imgs = document.getElementsByTagName("img")
Heres a JSFiddle getting those images using pure javascript.
Edit:
turn console on so you can see the return from console.log
The direct JavaScript methods are document.querySelector or document.querySelectorAll. The problem with those is that they are not supported in all browsers, jQuery (through SizzleJS) provides a browser compatible way of doing these things. SizzleJS delegates to document.querySelectorAll if it is available, and it falls back on other mechanisms when it is not available. So unless you want to write the fall back code yourself, it's probably best to stick with something like SizzleJS, which provides the selector functionality without the overhead of jQuery.

Javascript substring and indexOf not working in IE9

I have this Javascript here:
function getTxt(obj) {
var first = obj.innerHTML.substring(0, obj.innerHTML.indexOf('<span class=\"item2\">'));
var second = obj.innerHTML.substring(obj.innerHTML.indexOf('<span class=\"item2\">'));
var f = first.replace(/(<([^>]+)>)/ig,'');
var s = second.replace(/(<([^>]+)>)/ig,'');
alert(first + "\n" + second + "\n" + f + "\n" + s);
}
and the HTML:
<span class="item" onclick="getTxt(this)"><span class="item1">MyName</span><span class="item2">555-555-5555</span></span>
In most browsers (FireFox, Chrome, Safari, Opera) it will alert:
<span class="item1">MyName</span>
<span class="item2">555-555-5555</span>
MyName
555-555-5555
as expected. However, in IE9 it alerts:
<span class="item1">MyName</span><span class="item2">555-555-5555</span>
MyName555-555-5555
So it puts the vars "first" and "second" together into var "first", and puts "f" and "s" together into var "f".
I would like to know if there is anyway to correct this for IE9 (and probably other version of IE also) to work as it does in the other browsers.
Pattern matching innerHTML is particularly a problem in IE and is generally a bad idea. IE often does NOT return to you the same HTML that was originally in the page. It often requotes or removes quotes, changes the order of attributes, changes case, etc... IE is clearly reconstituting the HTML rather than give you back what was originally in the page. As such, you cannot reliably pattern match innerHTML in IE. There are some specific things you can probably match (the start of tags), but you can't expect attributes to be in a specific spot or to have a specific format.
If you console.log(obj.innerHTML) in IE, you will likely see what I'm talking about. It will look different.
A more robust solution is to use the DOM functions to navigate the specific elements or CSS selectors to find specific objects and then change attributes or innerHTML on a single specific element. Let the DOM navigation find the right element for you rather than parsing the HTML yourself.
If you provide a desired before and after sample of the HTML and describe what you're trying to accomplish, folks here can probably help you get the job done with DOM manipulation rather than HTML parsing.
I don't know which selector libraries you have available to you or which browsers you're targeting, but in jQuery, you could do this like this:
function getText(obj) {
return $(obj).find(".item1").text();
}
In plain javascript, in IE8 and above and all other modern browsers, you can use this:
function getText(obj) {
return obj.querySelectorAll(".item1").innerHTML;
}
If you had to support back to IE6 or IE7, I'd suggest getting the Sizzle library and use that for your queries:
function getText(obj) {
return Sizzle(".item1", obj)[0].innerHTML;
}
It happens because of "Quirky mode" in Internet Explorer. It's a huge pain in the ass, but you can disable it in IE DevTools, or by adding this metatag to your page:
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

How to clean up divs contents?

I heared that simply to say document.getElementById('divId').innerHTML = ""; is not safe for future divId contents editing via JS so how to clean up divs contents safely?
What should always work is:
var div = document.getElementById('divId');
while(div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
innerHTML is not part of any specification but is widely supported by browsers.
I wrote something similar to Felix Kling, only diffirence is that I wanted to pass the id or the reference. So I wrote it like this:
function removeAllChildNodes(node) {
i = (typeof(node) == "object") ? node : document.getElementById(node);
while (i.hasChildNodes()) {
i.removeChild(i.firstChild);
}
}
Setting the innerHTML attribute to nothing will eliminate any DOM nodes withing the specified DIV. There is no risk of "safety".
You can do this safely :P
I believe this is where JS frameworks, such as jQuery, come to light... they are optimized for cross-browser security - which includes secure DOM element removal, although you will loose a bit of computing speed (setting innerHTML is always the fastest way)
as for others wondering about security of this, some browsers (notably MSIE) have flaws that would crash the browser or allow malicious procedures to be executed on client's side under certain conditions, so that's why setting innerHTML might not be as secure as people use to think ;-)

This javascript works in every browser EXCEPT for internet explorer!

The webpage is here:
http://develop.macmee.com/testdev/
I'm talking about when you click the ? on the left, it is supposed to open up a box with more content in it. It does that in every browser except IE!
function question()
{
$('.rulesMiddle').load('faq.php?faq=rules_main',function(){//load page into .rulesMiddle
var rulesa = document.getElementById('rulesMiddle').innerHTML;
var rules = rulesa.split('<div class="blockbody">');//split to chop off the top above rules
var rulesT = rules[1].split('<form class="block');//split to chop off below rules
rulesT[0] = rulesT[0].replace('class=','vbclass');//get rid of those nasty vbulletin defined classes
document.getElementById('rulesMiddle').innerHTML = rulesT[0];//readd the content back into the DIV
$('.rulesMain').slideToggle();//display the DIV
$('.rulesMain').center();//center DIV
$('.rulesMain').css('top','20px');//align with top
});
}
IE converts innerHTML contents into upper case, so you probably are not able to split the string this way, as string operations are case sensitive. Check what the contents really looks like by running
alert(rulesa);
Andris is right. And that's not all. It'll also throw away the quotes in attributes.
It is completely unreliable to make any assumptions about the format of the string you get from innerHTML; the browser may output it in a variety of forms — some of which, in IE's case, are not even valid HTML. The chances of you getting back the same string that was originally parsed are very low.
In general: HTML-string-hacking is a shonky waste of time. Modify HTML elements using their node objects instead. You seem to be using jQuery, so you've got loads of utility functions to help you.
In any case you should not be loading the whole HTML page into #rulesMiddle. It includes a load of scripts and stylesheets and other header nonsense that can't go in there. jQuery allows you to pick which part of the document to insert; you seem to just want the first .blockbody element, so pick that:
$('#rulesMiddle').load('faq.php?faq=rules_main .blockbody:first', function(){
$('#rulesMiddle .blockrow').attr('class', '');
$('.rulesMain').slideToggle();
$('.rulesMain').css('top', '20px');
});
My IE debugger throws an error on your script when I click that button. On this line:
var rulesT = rules[1].split('<form class="block');//split to chop off below rules
IE stops processing the Javascript and says '1' is null or not an object
Don't know if you solve it, but it work's on my Ugly IE ... (its an v8)
Btw: It's me, or does pop-up widows wen open are really, really, really slowing down that platform ?

Categories