在 Firefox 上开发的 Javascript 在 IE 上失败的典型原因是什么?

我开发了一些在最近的 Firefox 和 Safari 上运行良好的 javascript 增强页面。我错过了检查 Internet Explorer,现在我发现这些页面不能在 IE6和 IE7上工作(到目前为止)。这些脚本以某种方式没有执行,虽然执行了一些 javascript,但页面显示好像没有 javascript。我使用自己的库来进行 Dom 操作,从 YUI2开始我使用 YUI-Loader 和 XML-Http-Request,在一个页面上我使用“ pSupload”,它依赖于 JQuery。

我正在从 OfficeXP 安装 Microsoft 脚本编辑器,现在将进行调试。我还将编写特定的测试。

IE 的典型失败点是什么? 我可以保持警惕的方向是什么。

我找到了这个页面,它显示了一些不同之处

根据你的经验,你能说出一些我应该首先寻找的典型事物吗?

稍后我还会针对特定任务提出更多问题,但现在我对你们的经验很感兴趣,为什么 IE 在运行良好的 Firefox 脚本上通常会失败

编辑: 谢谢你所有这些伟大的回答!

与此同时,我对整个代码进行了修改,以便它也能与 Internet Explorer 一起工作。我现在集成了 jQuery,并在它的基础上构建了自己的类。这是我最基本的错误,我没有从一开始就在 jQuery 上构建我所有的东西。现在我知道了。

JSLint 也帮了我很多。

许多来自不同答案的单一问题都有所帮助。

7270 次浏览

If you stick to using jQuery or YUI as your post is tagged, you should have minimal differences between browsers...that's what the frameworks are for, to take care of these cross-browser differences for you.

For an example, look at the quirksmode DOM traversal page, according to it IE doesn't support most things...while true, the frameworks do, for example IE doesn't support elem.childElementCount, but in jQuery: $(elem).children().size() works to get this value, in every browser. You'll find there's something in the library to handle 99% of the unsupported cases across browsers, at least with script...with CSS you might have to move to plugins for the library, a common example of this is to get rounded corners working in IE...since it has no CSS support for such.

If however you start doing things directly, like document.XXX(thing), then you're not in the library, you're doing javascript directly (it's all javascript, but you get the point :), and this might or might not cause issues, depending on how drunk the IE team was when implementing that particular function.

With IE you're more likely to fail on styling coming out right than raw javascript issues, animations a few pixels off and that sort of thing, much more-so in IE6 of course.

There are loads of things, but one trap I used to fall in was that many browsers accepts JSON without quoted names, while ie6 and ie7 does not.

{ name: "Jakob" } // will often work, but not in ie6/ie7
{ "name": "Jakob" } // Better!

Edit: To clarify, this is only an issue when actual JSON is required, as opposed to an object literal. JSON is a subset of the object literal syntax and is meant as a data exchange format (like XML) which is why it's designed to be pickier.

Check also for commas such as these or similar if any in your code

var o={
'name1':'value1',
'name2':'value2',
}

the last comma (following value2) will be tolerated by Firefox, but not IE

Trailing commas in arrays and object literals used to be a problem, haven't checked recently (meaning IE8):

var a = [ 1, 2, 3, ];
var o = { a:1, b:2, c:3, };

This would cause some extra code when generating such structures server side.

Some native objects are read-only without really seeming to be so (you can write to them but it has no effect). For example, a common advanced javascript is based on extending the Element object by overriding system methods, say, changing Element.prototype.appendChild() to do more than appending a child node - say, initialize it with parent's data. This will fail silently on IE6 - original method will be invoked on new objects instead of the new one.

Some browsers (I don't remember which now) consider newlines between HTML tags to be text nodes, while others don't. So childNodes(n), nextSibling(), firstChild() and the like will behave very differently.

Extra commas and missing commas used to be usual problem on IE while it works smoothly on FF.

Please feel free to update this list if you see any errors/omissions etc.

Note: IE9 fixes many of the following issues, so a lot of this only applies to IE8 and below and to a certain extent IE9 in quirks mode. For example, IE9 supports SVG, <canvas>, <audio> and <video> natively, however you must enable standards compliance mode for them to be available.


##General:

  • Problems with partially loaded documents: It’s a good idea to add your JavaScript in a window.onload or similar event as IE doesn’t support many operations in partially loaded documents.

  • Differing attributes: In CSS, it's elm.style.styleFloat in IE vs elm.style.cssFloat in Firefox. In <label> tags the for attribute is accessed with elm.htmlFor in IE vs elm.for in Firefox. Note that for is reserved in IE so elm['for'] is probably a better idea to stop IE from raising an exception.


##Base JavaScript language:

  • Access characters in strings: 'string'[0] isn’t supported in IE as it’s not in the original JavaScript specifications. Use 'string'.charAt(0) or 'string'.split('')[0] noting that accessing items in arrays is significantly faster than using charAt with strings in IE (though there's some initial overhead when split is first called.)

  • Commas before the end of objects: e.g. {'foo': 'bar',} aren't allowed in IE.


##Element-specific issues:

  • Getting the document of an IFrame:

    • Firefox and IE8+: IFrame.contentDocument (IE started supporting this from version 8.)
    • IE: IFrame.contentWindow.document
    • (IFrame.contentWindow refers to the window in both browsers.)

  • Canvas: Versions of IE before IE9 don't support the <canvas> element. IE does support VML which is a similar technology however, and explorercanvas can provide an in-place wrapper for <canvas> elements for many operations. Be aware that IE8 in standards compliance mode is many times slower and has many more glitches than when in quirks mode when using VML.

  • SVG: IE9 supports SVG natively. IE6-8 can support SVG, but only with external plugins with only some of those plugins supporting JavaScript manipulation.

  • <audio> and <video>: are only supported in IE9.

  • Dynamically creating radio buttons: IE <8 has a bug which makes radio buttons created with document.createElement uncheckable. See also How do you dynamically create a radio button in Javascript that works in all browsers? for a way to get around this.

  • Embedded JavaScript in <a href> tags and onbeforeunload conflicts in IE: If there's embedded JavaScript in the href part of an a tag (e.g. <a href="javascript: doStuff()"> then IE will always show the message returned from onbeforeunload unless the onbeforeunload handler is removed beforehand. See also Ask for confirm when closing a tab.

  • <script> tag event differences: onsuccess and onerror aren't supported in IE and are replaced by an IE-specific onreadystatechange which is fired regardless of whether the download succeeded or failed. See also JavaScript Madness for more info.


##Element size/position/scrolling and mouse position:

  • Getting element size/position: width/height of elements is sometimes elm.style.pixelHeight/Width in IE rather than elm.offsetHeight/Width, but neither is reliable in IE, especially in quirks mode, and sometimes one gives a better result than the other.

elm.offsetTop and elm.offsetLeft are often incorrectly reported, leading to finding positions of elements being incorrect, which is why popup elements etc are a few pixels off in a lot of cases.

Also note that if an element (or a parent of the element) has a display of none then IE will raise an exception when accessing size/position attributes rather than returning 0 as Firefox does.

  • Get the screen size (Getting the viewable area of the screen):

    • Firefox: window.innerWidth/innerHeight
    • IE standards mode: document.documentElement.clientWidth/clientHeight
    • IE quirks mode: document.body.clientWidth/clientHeight

  • Document scroll position/mouse position: This one is actually not defined by the w3c so is non-standard even in Firefox. To find the scrollLeft/scrollTop of the document:

    • Firefox and IE in quirks mode: document.body.scrollLeft/scrollTop

    • IE in standards mode: document.documentElement.scrollLeft/scrollTop

    • NOTE: Some other browsers use pageXOffset/pageYOffset as well.

        function getDocScrollPos() {
      var x = document.body.scrollLeft ||
      document.documentElement.scrollLeft ||
      window.pageXOffset || 0,
      y = document.body.scrollTop ||
      document.documentElement.scrollTop ||
      window.pageYOffset || 0;
      return [x, y];
      };
      

    In order to get the position of the mouse cursor, evt.clientX and evt.clientY in mousemove events will give the position relative to the document without adding the scroll position so the previous function will need to be incorporated:

     var mousepos = [0, 0];
    document.onmousemove = function(evt) {
    evt = evt || window.event;
    if (typeof evt.pageX != 'undefined') {
    // Firefox support
    mousepos = [evt.pageX, evt.pageY];
    } else {
    // IE support
    var scrollpos = getDocScrollPos();
    mousepos = [evt.clientX+scrollpos[0], evt.clientY+scrollpos[1]];
    };
    };
    

##Selections/ranges:


##Getting elements by ID:

  • document.getElementById can also refer to the name attribute in forms (depending which is defined first in the document) so it's best not to have different elements which have the same name and id. This dates back to the days when id wasn't a w3c standard. document.all (a proprietary IE-specific property) is significantly faster than document.getElementById, but it has other problems as it always prioritizes name before id. I personally use this code, falling back with additional checks just to be sure:

     function getById(id) {
    var e;
    if (document.all) {
    e = document.all[id];
    if (e && e.tagName && e.id === id) {
    return e;
    };
    };
    e = document.getElementById(id);
    if (e && e.id === id) {
    return e;
    } else if (!e) {
    return null;
    } else {
    throw 'Element found by "name" instead of "id": ' + id;
    };
    };
    

##Problems with read only innerHTML:

  • IE does colGroup2 setting the innerHTML of col, colGroup, frameSet, html, head, style, table, tBody, tFoot, tHead, colGroup0, and colGroup1 elements. Here's a function which works around that for table-related elements:

     function setHTML(elm, html) {
    // Try innerHTML first
    try {
    elm.innerHTML = html;
    } catch (exc) {
    function getElm(html) {
    // Create a new element and return the first child
    var e = document.createElement('div');
    e.innerHTML = html;
    return e.firstChild;
    };
    function replace(elms) {
    // Remove the old elements from 'elm'
    while (elm.children.length) {
    elm.removeChild(elm.firstChild);
    }
    // Add the new elements from 'elms' to 'elm'
    for (var x=0; x<elms.children.length; x++) {
    elm.appendChild(elms.children[x]);
    };
    };
    // IE 6-8 don't support setting innerHTML for
    // TABLE, TBODY, TFOOT, THEAD, and TR directly
    var tn = elm.tagName.toLowerCase();
    if (tn === 'table') {
    replace(getElm('<table>' + html + '</table>'));
    } else if (['tbody', 'tfoot', 'thead'].indexOf(tn) != -1) {
    replace(getElm('<table><tbody>' + html + '</tbody></table>').firstChild);
    } else if (tn === 'tr') {
    replace(getElm('<table><tbody><tr>' + html + '</tr></tbody></table>').firstChild.firstChild);
    } else {
    throw exc;
    };
    };
    };
    

    Also note that IE requires adding a <tbody> to a <table> before appending <tr>s to that <tbody> element when creating using document.createElement, for example:

     var table = document.createElement('table');
    var tbody = document.createElement('tbody');
    var tr = document.createElement('tr');
    var td = document.createElement('td');
    table.appendChild(tbody);
    tbody.appendChild(tr);
    tr.appendChild(td);
    // and so on
    

##Event differences:

  • Getting the event variable: DOM events aren't passed to functions in IE and are accessible as window.event. One common way of getting the event is to use e.g.
    elm.onmouseover = function(evt) {evt = evt||window.event}
    which defaults to window.event if evt is undefined.

  • Key event code differences: Key event codes vary wildly, though if you look at Quirksmode or JavaScript Madness, it's hardly specific to IE, Safari and Opera are different again.

  • Mouse event differences: the button attribute in IE is a bit-flag which allows multiple mouse buttons at once:

    • Left: 1 (var isLeft = evt.button & 1)
    • Right: 2 (var isRight = evt.button & 2)
    • Center: 4 (var isCenter = evt.button & 4)

    The W3C model (supported by Firefox) is less flexible than the IE model is, with only a single button allowed at once with left as 0, right as 2 and center as 1. Note that, as Peter-Paul Koch mentions, this is very counter-intuitive, as 0 usually means 'no button'.

    offsetX and offsetY are problematic and it's probably best to avoid them in IE. A more reliable way to get the offsetX and offsetY in IE would be to get the position of the relatively positioned element and subtract it from clientX and clientY.

    Also note that in IE to get a double click in a click event you'd need to register both a click and dblclick event to a function. Firefox fires click as well as dblclick when double clicking, so IE-specific detection is needed to have the same behaviour.

  • Differences in the event handling model: Both the proprietary IE model and the Firefox model support handling of events from the bottom up, e.g. if there are events in both elements of <div><span></span></div> then events will trigger in the span then the div rather than the order which they're bound if a traditional e.g. elm.onclick = function(evt) {} was used.

    "Capture" events are generally only supported in Firefox etc, which will trigger the div then the span events in a top down order. IE has elm.setCapture() and elm.releaseCapture() for redirecting mouse events from the document to the element (elm in this case) before processing other events, but they have a number of performance and other issues so should probably be avoided.

    • Attach: elm.addEventListener(type, listener, useCapture [true/false])
      Detach: elm.removeEventListener(type, listener, useCapture)
      (type is e.g. 'mouseover' without the on)

    • IE: Only a single event of a given type on an element can be added in IE - an exception is raised if more than one event of the same type is added. Also note that the this refers to window rather than the bound element in event functions (so is less useful):

      Attach: elm.attachEvent(sEvent, fpNotify)
      Detach: elm.detachEvent(sEvent, fpNotify)
      (sEvent is e.g. 'onmouseover')

  • Event attribute differences:

    • Stop events from being processed by any other listening functions:

      Firefox: evt.stopPropagation()
      IE: evt.cancelBubble = true

    • Stop e.g. key events from inserting characters or stopping checkboxes from getting checked:

      Firefox: evt.preventDefault()
      IE: evt.returnValue = false
      Note: Just returning false in keydown, keypress, mousedown, mouseup, click and reset will also prevent default.

    • Get the element which triggered the event:

      Firefox: evt.target
      IE: evt.srcElement

    • Getting the element the mouse cursor moved away from: evt.fromElement in IE is evt.target in Firefox if in an onmouseout event, otherwise evt.relatedTarget

    • Getting the element the mouse cursor moved to: evt.toElement in IE is evt.relatedTarget in Firefox if in an onmouseout event, otherwise evt.target

    • Note: evt.currentTarget (the element to which the event was bound) has no equivalent in IE.

IE is not a modern browser and only follows ECMAScript loosely.

IE is very strict about missing ";" so is usually that.

getElementbyID will also match against the name attribute in IE, but not other browsers, and IE will select whichever it finds first.

example:

<script>
var foo = document.getElementById('bar');
</script>


....
<input name="bar" type="text" />  //IE will get this element
<span id="bar"> Hello, World! </span>  //FF,Safari,Chrome will get this element

I just found one this morning, a co-worker set the script tag as: <script type="application/javascript"> because his ide autocomplete had that before "text/javascript"

But, it turns out that IE just ignores the entire script if you use "application/javascript", you need to use "text/javascript"

You mentioned jQuery which I'm less familiar with but for general reference, and specifically with Prototype, one thing to watch out for is reserved words / method names in IE. I know what often gets me is things like:

someElement.appendChild(new Element('label',{ **for**: someInput.id }).update( someLabelText );

(new Element(tagName, propertyHash) is how new elements are created in Protitype). In IE, for: must be 'for':, because for is a reserved word. Which makes complete sense -- but FireFox will tolerate this.

Another example:

someElement.wrap('div').addClassName('someClass')

(the wrap method in Prototype wraps one element in another) -- In IE, on textareas, wrap is a property, and Element.wrap() must be used instead of the methodized version

These are two examples which come to mind from my experience. They're based on prototype but the core issue isn't: Watch out for any methods/labels/identifiers which IE considers reserved words but FireFox or Safari will tolerate.

The fact is that IE doesn't support JavaScript... It supports his own implementation of ECMAScript : JScript... which is kind of different...

Using console.log() for outputting errors to the Firefox error console will cause your scripts to fail in IE. Got to remember to take those out when you test in IE.

I found an odd quirk just the other day with Internet Explorer. I was using YUI, and replacing the contents of a table body () by setting the innerHTML

Y.one('#elementId').set('innerHTML', '<tr><td>Column 1</td></tr>');

This would work in all browsers EXCEPT IE. I finally discovered that you couldn't replace the innerHTML of a table in IE. I had to create a node using YUI and then append that node.

var myNode = Y.node.create('<tr><td>Column 1</td></tr>');
Y.one('#elementId').append(myNode);

That was a fun one to figure out!

Differing JavaScript Support

IE doesn't support (most of) the extensions added to JavaScript since 1.5.

New in 1.6

  • Array Methods - indexOf(), lastIndexOf(), every(), filter(), forEach(), map(), some()
  • for each ... in - iterates values instead of property names.

New in 1.7

New in 1.8

  • Array Methods - reduce(), reduceRight()
  • Shortcuts for defining functions.

Some of these things require you to specify a version number of JavaScript to run under (which will break under IE), but some things like [1,2,3].indexOf(2) might not seem like that big a deal, until you try to run it in IE

The major differences between JavaScript in IE and JavaScript in modern browsers (ex, Firefox) can be attributed to the same reasons behind the differences in CSS/(X)HTML cross-browser. Back in the day there was no de facto standard; IE/Netscape/Opera fought a turf war, implementing most of the specs, but also omitting some as well as making proprietary specs to gain advantages over each other. I could go on at length, but lets skip ahead to the release of IE8: JavaScript was avoided/scorned for years, and with the rise of FF and the contempt of webcomm, IE chose to focus mostly on advancing their CSS from IE6 on. And basically left DOM support behind. IE8's DOM support might as well be IE6's, which rolled out in 2001....so IE's DOM support is nearly a decade behind modern browsers. If you are having JavaScript discrepancies particular to a layout engine, you're best bet is to attack it the same way we took on the CSS problems; Targeting that browser. DON'T USE BROWSER SNIFFING, use feature detection to sniff out your browser/it's level of DOM support.

JScript is not IE's own implementation of ECMAScript; JScript was IE's answer to Netscape's JavaScript, both of which came into existence before ECMAScript.

As far as type attributes on the script element, type="text/javascript" is the default standard (at least in HTML5), so you don't ever need a type attribute unless your script isn't JavaScript.

As far as IE not supporting innerHTML...innerHTML was invented by IE and is still today NOT a DOM standard. Other browsers have adopted it because it's useful, which is why you can use it cross-browser. As far as dynamically changing tables, MSDN says "because of the specific structure required by tables, the innerText and innerHTML properties of the table and tr objects are read-only." I don't know how much of that was true initially, but clearly the modern browsers have figured it out while dealing with the complexities of table-layout.

I highly recommend reading PPK on JavaScript Jeremy Keith's DOM Scripting Douglas Crockford's JavaScript: The Good Parts and Christian Hellman's Beginning JavaScript with DOM Scripting and Ajax to get a strong grasp on JavaScript.

As far as Frameworks/Libraries are concerned, if you don't have a strong grasp on JavaScript yet, you should avoid them. 2 years ago I fell into the jQuery trap, and while I was able to pull off magnificent feats, I never learned a damn thing about coding JavaScript properly. In hindsight, jQuery is a wicked awesome DOM Toolkit, but my failure to learn proper closures, prototypical inheritance, etc., not only set my personal knowledge back, my work starting taking huge performance hits because I had no clue wtf I was doing.

JavaScript is the language of the browser; if you are client-side/front-end engineer it is of upmost importance that you command JavaScript. Node.js is bringing JavaScript full tilt, I see immense strides taken daily in its development; Server-side JavaScript will be a standard in the very near future. I'm mentioning this to further emphasize just how important JavaScript is now and will be.

JavaScript is going to make more waves than Rails.

Happy Scripting!

For what it's worth I just came across this nasty issue in < IE9

say you have some html like this:

<table><tr><td>some content...</td></tr></table>

and for some reason (I had a good one) you need to retrieve all HTML in the table before the last closing TR you might try something like this:

var tableHtml = document.getElementById('thetable').innerHTML;
var fragment = tableHtml.substring(0, tableHtml.lastIndexOf('</tr>'));

< IE9 will return nothing (-1) here because the tableHtml variable contains all html tags upper-cased and lastIndexOf is case sensitive. To get around this I had to throw in a toLowerCase() before lastIndexOf.