Errores de división de JavaScript:¡Corregidos!

El método String.prototype.split es muy útil, por lo que es una pena que si usa una expresión regular como delimitador, los resultados pueden ser tan diferentes entre navegadores que es probable que haya introducido errores en su código (a menos que usted sabe exactamente con qué tipo de datos está trabajando y puede evitar los problemas). Aquí hay un ejemplo de otras personas que se desahogan sobre los problemas. Las siguientes son las inconsistencias entre navegadores cuando se usan expresiones regulares con split :

  • Internet Explorer excluye casi todos los valores vacíos de la matriz resultante (por ejemplo, cuando dos delimitadores aparecen uno al lado del otro en los datos, o cuando aparece un delimitador al principio o al final de los datos). Esto no tiene ningún sentido para mí, ya que IE incluye valores vacíos cuando usa una cadena como delimitador.
  • Internet Explorer y Safari no empalman los valores de los paréntesis de captura en la matriz devuelta (esta funcionalidad puede ser útil con analizadores simples, etc.)
  • Firefox no empalma undefined valores en la matriz devuelta como resultado de grupos de captura no participantes.
  • Internet Explorer, Firefox y Safari tienen varios errores de casos extremos adicionales en los que no siguen la especificación dividida (que en realidad es bastante compleja).

La situación es tan mala que simplemente he evitado usar la división basada en expresiones regulares en el pasado.

Eso termina ahora.

La siguiente secuencia de comandos proporciona una implementación de String.prototype.split rápida y uniforme entre navegadores e intenta seguir con precisión las especificaciones relevantes (ECMA-262 v3 §15.5.4.14, pp.103,104).

También creé una página bastante rápida y sucia donde puedes probar el resultado de más de 50 usos de split de JavaScript. y compare rápidamente los resultados de su navegador con la implementación correcta. En la página de prueba, las líneas rosadas en la tercera columna resaltan los resultados incorrectos del split nativo método. La columna más a la derecha muestra los resultados del siguiente script. Todo es verde en todos los navegadores que he probado (IE 5.5 - 7, Firefox 2.0.0.4, Opera 9.21, Safari 3.0.1 beta y Swift 0.2).

Ejecute las pruebas en su navegador .

Aquí está el guión:

/*!
 * 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;

}();

Descárgalo .

Por favor, hágamelo saber si encuentra algún problema. ¡Gracias!

Actualización: Este script se ha convertido en parte de mi biblioteca XRegExp , que incluye muchas otras correcciones de compatibilidad entre navegadores de expresiones regulares de JavaScript.