Chyby rozdělení JavaScriptu:Opraveno!

Metoda String.prototype.split je velmi užitečná, takže je škoda, že pokud jako oddělovač použijete regulární výraz, výsledky mohou být v různých prohlížečích tak divoce odlišné, že je pravděpodobné, že jste do svého kódu právě vnesli chyby (pokud přesně víte, s jakými daty pracujete, a dokážete se problémům vyhnout). Zde je jeden příklad toho, jak ostatní lidé ventilují problémy. Níže jsou uvedeny nekonzistence mezi prohlížeči při použití regulárních výrazů s split :

  • Internet Explorer vyloučí z výsledného pole téměř všechny prázdné hodnoty (např. když se v datech objeví dva oddělovače vedle sebe nebo když se oddělovač objeví na začátku nebo na konci dat). To mi nedává žádný smysl, protože IE při použití řetězce jako oddělovače obsahuje prázdné hodnoty.
  • Internet Explorer a Safari nespojují hodnoty zachycení závorek do vráceného pole (tato funkce může být užitečná u jednoduchých analyzátorů atd.)
  • Firefox nespojuje undefined hodnoty do vráceného pole jako výsledek nezúčastněných zachycovacích skupin.
  • Internet Explorer, Firefox a Safari mají různé další chyby typu edge-case, které nedodržují specifikaci rozdělení (což je ve skutečnosti poměrně složité).

Situace je tak špatná, že jsem se v minulosti jednoduše vyhýbal použití rozdělení založeného na regulárních výrazech.

To teď končí.

Následující skript poskytuje rychlou a jednotnou implementaci String.prototype.split napříč prohlížeči a snaží se přesně dodržovat příslušné specifikace (ECMA-262 v3 §15.5.4.14, str. 103,104).

Vytvořil jsem také poměrně rychlou a špinavou stránku, kde můžete otestovat výsledek více než 50 použití JavaScriptu split a rychle porovnejte výsledky vašeho prohlížeče se správnou implementací. Na testovací stránce růžové čáry ve třetím sloupci zvýrazňují nesprávné výsledky z nativního split metoda. Sloupec zcela vpravo zobrazuje výsledky níže uvedeného skriptu. V každém prohlížeči, který jsem testoval, je vše zelené (IE 5.5 – 7, Firefox 2.0.0.4, Opera 9.21, Safari 3.0.1 beta a Swift 0.2).

Spusťte testy ve svém prohlížeči .

Zde je skript:

/*!
 * Cross-Browser Split 1.1.1
 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
 * Available under the MIT License
 * ECMAScript compliant, uniform cross-browser split method
 */

/**
 * Splits a string into an array of strings using a regex or string separator. Matches of the
 * separator are not included in the result array. However, if `separator` is a regex that contains
 * capturing groups, backreferences are spliced into the result each time `separator` is matched.
 * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
 * cross-browser.
 * @param {String} str String to split.
 * @param {RegExp|String} separator Regex or string to use for separating the string.
 * @param {Number} [limit] Maximum number of items to include in the result array.
 * @returns {Array} Array of substrings.
 * @example
 *
 * // Basic use
 * split('a b c d', ' ');
 * // -> ['a', 'b', 'c', 'd']
 *
 * // With limit
 * split('a b c d', ' ', 2);
 * // -> ['a', 'b']
 *
 * // Backreferences in result array
 * split('..word1 word2..', /([a-z]+)(\d+)/i);
 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
 */
var split;

// Avoid running twice; that would break the `nativeSplit` reference
split = split || function (undef) {

    var nativeSplit = String.prototype.split,
        compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
        self;

    self = function (str, separator, limit) {
        // If `separator` is not a regex, use `nativeSplit`
        if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
            return nativeSplit.call(str, separator, limit);
        }
        var output = [],
            flags = (separator.ignoreCase ? "i" : "") +
                    (separator.multiline  ? "m" : "") +
                    (separator.extended   ? "x" : "") + // Proposed for ES6
                    (separator.sticky     ? "y" : ""), // Firefox 3+
            lastLastIndex = 0,
            // Make `global` and avoid `lastIndex` issues by working with a copy
            separator = new RegExp(separator.source, flags + "g"),
            separator2, match, lastIndex, lastLength;
        str += ""; // Type-convert
        if (!compliantExecNpcg) {
            // Doesn't need flags gy, but they don't hurt
            separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
        }
        /* Values for `limit`, per the spec:
         * If undefined: 4294967295 // Math.pow(2, 32) - 1
         * If 0, Infinity, or NaN: 0
         * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
         * If negative number: 4294967296 - Math.floor(Math.abs(limit))
         * If other: Type-convert, then use the above rules
         */
        limit = limit === undef ?
            -1 >>> 0 : // Math.pow(2, 32) - 1
            limit >>> 0; // ToUint32(limit)
        while (match = separator.exec(str)) {
            // `separator.lastIndex` is not reliable cross-browser
            lastIndex = match.index + match[0].length;
            if (lastIndex > lastLastIndex) {
                output.push(str.slice(lastLastIndex, match.index));
                // Fix browsers whose `exec` methods don't consistently return `undefined` for
                // nonparticipating capturing groups
                if (!compliantExecNpcg && match.length > 1) {
                    match[0].replace(separator2, function () {
                        for (var i = 1; i < arguments.length - 2; i++) {
                            if (arguments[i] === undef) {
                                match[i] = undef;
                            }
                        }
                    });
                }
                if (match.length > 1 && match.index < str.length) {
                    Array.prototype.push.apply(output, match.slice(1));
                }
                lastLength = match[0].length;
                lastLastIndex = lastIndex;
                if (output.length >= limit) {
                    break;
                }
            }
            if (separator.lastIndex === match.index) {
                separator.lastIndex++; // Avoid an infinite loop
            }
        }
        if (lastLastIndex === str.length) {
            if (lastLength || !separator.test("")) {
                output.push("");
            }
        } else {
            output.push(str.slice(lastLastIndex));
        }
        return output.length > limit ? output.slice(0, limit) : output;
    };

    // For convenience
    String.prototype.split = function (separator, limit) {
        return self(this, separator, limit);
    };

    return self;

}();

Stáhněte si to .

Pokud narazíte na nějaké problémy, dejte mi prosím vědět. Díky!

Aktualizace: Tento skript se stal součástí mé knihovny XRegExp , která zahrnuje mnoho dalších oprav kompatibility mezi prohlížeči s regulárními výrazy JavaScriptu.