JavaScript equivalente a printf / String.Format

1972

Estoy buscando un buen equivalente de JavaScript de C / PHP printf()o para programadores de C # / Java String.Format()( IFormatProviderpara .NET).

Mi requisito básico es un formato de mil separadores para números por ahora, pero algo que maneje muchas combinaciones (incluidas las fechas) sería bueno.

Me doy cuenta de que la biblioteca Ajax de Microsoft proporciona una versión de String.Format(), pero no queremos toda la sobrecarga de ese marco.

Chris S
fuente
2
Aparte de todas las excelentes respuestas a continuación, es posible que desee echar un vistazo a esta: stackoverflow.com/a/2648463/1712065, que IMO es la solución más eficiente para este problema.
Annie
1
Escribí uno barato que usa la sintaxis printf tipo C.
Braden Best
var search = [$ scope.dog, "1"]; var url = vsprintf (" tierra / Servicios / dogSearch.svc / FindMe /% s /% s ", buscar); *** Para el nodo, puede obtener su módulo "npm install sprintf-js"
Jenna Leaf
También he escrito una función simple para lograr esto; stackoverflow.com/a/54345052/5927126
AnandShanbhag

Respuestas:

1111

A partir de ES6, puede usar cadenas de plantilla:

let soMany = 10;
console.log(`This is ${soMany} times easier!`);
// "This is 10 times easier!

Vea la respuesta de Kim a continuación para más detalles.


De otra manera:

Prueba sprintf () para JavaScript .


Si realmente quiere hacer un método de formato simple por su cuenta, no haga los reemplazos sucesivamente, sino que los haga simultáneamente.

Debido a que la mayoría de las otras propuestas que se mencionan fallan cuando una cadena de reemplazo anterior también contiene una secuencia de formato como esta:

"{0}{1}".format("{1}", "{0}")

Normalmente esperaría que la salida fuera {1}{0}pero la salida real es {1}{1}. Entonces, haga un reemplazo simultáneo, como en la sugerencia de miedo .

Gumbo
fuente
16
Si solo se desea una simple conversión de número a cadena, ¡el num.toFixed()método podría ser suficiente!
heltonbiker
@MaksymilianMajer que parece ser algo enormemente diferente.
Evan Carroll
@EvanCarroll tienes razón. En el momento en que escribí el comentario, el repositorio de sprintf() for JavaScriptno estaba disponible. underscore.stringtiene más características además de sprintf que se basa en la sprintf() for JavaScriptimplementación. Aparte de eso, la biblioteca es un proyecto completamente diferente.
Maksymilian Majer
@MaksymilianMajer correcto, solo digo que esta respuesta está muerta, y el enlace ha decaído. Necesita ser totalmente purgado.
Evan Carroll
2
Esto ya no debería aceptarse como respuesta. A partir de ES6, esto está integrado en el lenguaje javascript (tanto en los navegadores como en NodeJS). Vea la respuesta de @Kim a continuación.
Ryan Shillington
1391

Sobre la base de las soluciones sugeridas anteriormente:

// First, checks if it isn't implemented yet.
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) { 
      return typeof args[number] != 'undefined'
        ? args[number]
        : match
      ;
    });
  };
}

"{0} is dead, but {1} is alive! {0} {2}".format("ASP", "ASP.NET")

salidas

¡ASP está muerto, pero ASP.NET está vivo! ASP {2}


Si prefiere no modificar Stringel prototipo:

if (!String.format) {
  String.format = function(format) {
    var args = Array.prototype.slice.call(arguments, 1);
    return format.replace(/{(\d+)}/g, function(match, number) { 
      return typeof args[number] != 'undefined'
        ? args[number] 
        : match
      ;
    });
  };
}

Te da el mucho más familiar:

String.format('{0} is dead, but {1} is alive! {0} {2}', 'ASP', 'ASP.NET');

con el mismo resultado:

¡ASP está muerto, pero ASP.NET está vivo! ASP {2}

miedo
fuente
12
el || el truco no funciona si args [número] es 0. Debería hacer un if () explícito para ver si (args [número] === indefinido).
fserb
44
en la instrucción else de la taquigrafía if, ¿por qué no simplemente hacer "match" en lugar de "'{' + number + '}'". partido debe ser igual a esa cadena.
mikeycgto
44
Si tiene varias cadenas agregadas entre sí (con el +operador), asegúrese de poner la cadena completa entre paréntesis: de lo ("asd {0}"+"fas {1}").format("first", "second");contrario, la función solo se aplicará a la última cadena que se agregó.
Lukas Knuth
3
Eso cambia leve y sutilmente el resultado. Imaginar 'foo {0}'.format(fnWithNoReturnValue()). Actualmente volvería foo {0}. Con sus cambios, volvería foo undefined.
Fearphage
2
@avenmore: / \ {(\ d +) \} / g
Hozuki
491

Es divertido porque Stack Overflow tiene su propia función de formateo para el Stringprototipo llamado formatUnicorn. ¡Intentalo! Entra en la consola y escribe algo como:

"Hello, {name}, are you feeling {adjective}?".formatUnicorn({name:"Gabriel", adjective: "OK"});

Firebug

Obtienes esta salida:

Hello, Gabriel, are you feeling OK?

¡Puedes usar objetos, matrices y cadenas como argumentos! Obtuve su código y lo reformulé para producir una nueva versión de String.prototype.format:

String.prototype.formatUnicorn = String.prototype.formatUnicorn ||
function () {
    "use strict";
    var str = this.toString();
    if (arguments.length) {
        var t = typeof arguments[0];
        var key;
        var args = ("string" === t || "number" === t) ?
            Array.prototype.slice.call(arguments)
            : arguments[0];

        for (key in args) {
            str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
        }
    }

    return str;
};

Tenga en cuenta la Array.prototype.slice.call(arguments)llamada inteligente : eso significa que si agrega argumentos que son cadenas o números, no un solo objeto de estilo JSON, obtendrá el String.Formatcomportamiento de C # casi exactamente.

"a{0}bcd{1}ef".formatUnicorn("foo", "bar"); // yields "aFOObcdBARef"

Esto se debe a Array's sliceobligarán lo que esté en argumentsen una Array, ya sea en su origen fue o no, y el keyserá el índice (0, 1, 2 ...) de cada elemento de la matriz forzada a tener una cadena (por ejemplo, '0', por lo"\\{0\\}" para tu primer patrón regexp).

Ordenado.

Gabriel Nahmias
fuente
402
Es genial responder una pregunta sobre stackoverflow con el código de stackoverflow, +1
Sneakyness
55
@JamesManning La expresión regular permite la bandera global ( g), que puede reemplazar la misma clave más de una vez. En el ejemplo anterior, podría usar {name}varias veces en la misma oración y reemplazarlas todas.
KrekkieD
3
Esto parece terriblemente frágil, para ser honesto. ¿Qué sucede, por ejemplo, si namees así "blah {adjective} blah"?
sam hocevar
55
@ruffin "un poco hiperbólico"? El código que se deja engañar para interpretar los datos del usuario como cadenas de formato es una categoría completa de vulnerabilidades . 98.44% está más allá de mediocre .
sam hocevar
3
@samhocevar No puedo creer que Little Bobby me haya presentado. ;) Si está ejecutando texto procesado por JavaScript del lado del cliente en su servidor de base de datos sin ninguna verificación de seguridad, el cielo nos ayudará a todos. ; ^) Mire, no debería haber nada que un usuario pueda enviar desde un cliente (por ejemplo, Postman) que supere la seguridad de su servidor. Y debe asumir que cualquier cosa peligrosa que pueda ser enviada desde un cliente será . Es decir, si necesita un 100% de seguridad del código JavaScript del lado del cliente que siempre es editable por el usuario, y cree que esta función podría abrir un riesgo de seguridad, está jugando en el juego equivocado.
ruffin
325

Formato de número en JavaScript

Llegué a esta página de preguntas con la esperanza de encontrar cómo formatear números en JavaScript, sin presentar otra biblioteca. Esto es lo que he encontrado:

Redondeo de números de coma flotante

sprintf("%.2f", num)Parece ser el equivalente de en JavaScript num.toFixed(2), que se formatea numa 2 decimales, con redondeo (pero vea el comentario de @ ars265 a Math.roundcontinuación).

(12.345).toFixed(2); // returns "12.35" (rounding!)
(12.3).toFixed(2); // returns "12.30" (zero padding)

Forma exponencial

El equivalente de sprintf("%.2e", num)es num.toExponential(2).

(33333).toExponential(2); // "3.33e+4"

Hexadecimales y otras bases.

Para imprimir números en la base B, intente num.toString(B). JavaScript admite la conversión automática hacia y desde las bases 2 a 36 (además, algunos navegadores tienen soporte limitado para la codificación base64 ).

(3735928559).toString(16); // to base 16: "deadbeef"
parseInt("deadbeef", 16); // from base 16: 3735928559

Páginas de referencia

Tutorial rápido sobre formato de número JS

Página de referencia de Mozilla para toFixed () (con enlaces a toPrecision (), toExponential (), toLocaleString (), ...)

rescdsk
fuente
23
¿No sería mejor encerrar el número literal entre paréntesis, en lugar de dejar un espacio en blanco extraño allí?
rmobis
77
Eso probablemente se vería mejor, cierto. Pero mi objetivo es solo señalar la trampa de error de sintaxis.
rescdsk
44
Solo una nota al margen si está utilizando un navegador antiguo o si admite navegadores antiguos, algunos navegadores implementados en Fijo incorrectamente, usando Math.round en lugar de toFixed es una mejor solución.
ars265
77
@Raphael_ y @rescdsk: ..también funciona:33333..toExponential(2);
Peter Jaric
O (33333) .toExponencial (2)
Jonathan
245

A partir de ES6, puede usar cadenas de plantilla :

let soMany = 10;
console.log(`This is ${soMany} times easier!`);
// "This is 10 times easier!

Tenga en cuenta que las cadenas de plantilla están rodeadas por comillas invertidas comillas `en lugar de comillas (simples).

Para mayor información:

https://developers.google.com/web/updates/2015/01/ES6-Template-Strings

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings

Nota: Consulte el sitio de mozilla para encontrar una lista de navegadores compatibles.

Kim
fuente
61
El problema con las cadenas de plantillas es que parecen ejecutarse de inmediato, haciendo que su uso como, por ejemplo, una tabla de cadenas tipo i18n sea completamente inútil. No puedo definir la cadena desde el principio y proporcionar los parámetros para usar más tarde y / o repetidamente.
Tustin2121
44
@ Tustin2121 Tienes razón en que no están diseñados para ser asignados a una variable, lo cual es un poco alucinante, pero es bastante fácil trabajar con tendencias de ejecución instantánea de cadenas con plantillas si las ocultas en una función. Ver jsfiddle.net/zvcm70pa
inanutshellus
13
@ Tustin2121 no hay diferencia entre usar una cadena de plantilla o una concatenación de cadenas de estilo antiguo, su azúcar es lo mismo. Tendría que envolver un generador de cadenas de estilo antiguo en una función simple y lo mismo funciona bien con las plantillas de cadenas. const compile = (x, y) => `I can call this template string whenever I want.. x=${x}, y=${y}`...compile(30, 20)
cchamberlain
44
esta solución no funcionará para cadenas de formato pasadas en variable (desde el servidor, por ejemplo)
user993954
1
@inanutshellus Eso funciona bien si la función de su plantilla se define en la misma máquina donde se ejecuta. Hasta donde sé, no puede pasar una función como JSON, por lo que almacenar funciones de plantilla en una base de datos no funciona bien.
styfle
171

jsxt, Zippo

Esta opción se ajusta mejor.

String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};

Con esta opción puedo reemplazar cadenas como estas:

'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP');

Con su código, el segundo {0} no sería reemplazado. ;)

Filipiz
fuente
3
gist.github.com/1049426 Actualicé su ejemplo con este enfoque. Numerosos beneficios, incluyendo guardar la implementación nativa si existe, encadenar, etc. Intenté eliminar las expresiones regulares, pero fue muy necesario para el reemplazo global. : - /
tbranyen
66
jsxt tiene licencia GPL desafortunadamente
AndiDog
109

Yo uso esta simple función:

String.prototype.format = function() {
    var formatted = this;
    for( var arg in arguments ) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};

Eso es muy similar a string.format:

"{0} is dead, but {1} is alive!".format("ASP", "ASP.NET")
Zippoxer
fuente
1
¿ +=Por qué ?, ¿debería?formatted = this.replace("{" + arg + "}", arguments[arg]);
Guilin 桂林
2
Creo que el código aún no es correcto. El correcto debería ser como lo publicó Filipiz .
wenqiang el
3
Como referencia, for...inno funcionará en todos los navegadores ya que este código lo espera. Recorrerá todas las propiedades enumerables, que en algunos navegadores incluirán arguments.length, y en otros ni siquiera incluirán los argumentos. En cualquier caso, si Object.prototypese agrega a, las adiciones probablemente se incluirán en el grupo. El código debe usar un forbucle estándar , en lugar de for...in.
cHao
3
Esto falla si un reemplazo anterior también contiene una cadena de formato:"{0} is dead, but {1} is alive!".format("{1}", "ASP.NET") === "ASP.NET is dead, but ASP.NET is alive!"
Gumbo
66
La variable arges global. En su lugar, debe hacer esto:for (var arg in arguments) {
Pauan
68

Para los usuarios de Node.js existe la util.formatque tiene una funcionalidad similar a printf:

util.format("%s world", "Hello")
George Eracleous
fuente
1
Esto no admite% x a partir del Nodo v0.10.26
Max Krohn
Tampoco admite modificadores de ancho y alineación (p %-20s %5.2f. Ej. )
MGF
Tuve que desplazarme por la página para ver esta útil respuesta.
Donato
53

Me sorprende que nadie lo haya usado reduce, esta es una función nativa, concisa y poderosa de JavaScript.

ES6 (EcmaScript2015)

String.prototype.format = function() {
  return [...arguments].reduce((p,c) => p.replace(/%s/,c), this);
};

console.log('Is that a %s or a %s?... No, it\'s %s!'.format('plane', 'bird', 'SOman'));

<ES6

function interpolate(theString, argumentArray) {
    var regex = /%s/;
    var _r=function(p,c){return p.replace(regex,c);}
    return argumentArray.reduce(_r, theString);
}

interpolate("%s, %s and %s", ["Me", "myself", "I"]); // "Me, myself and I"

Cómo funciona:

reduce aplica una función contra un acumulador y cada elemento de la matriz (de izquierda a derecha) para reducirlo a un solo valor.

var _r= function(p,c){return p.replace(/%s/,c)};

console.log(
  ["a", "b", "c"].reduce(_r, "[%s], [%s] and [%s]") + '\n',
  [1, 2, 3].reduce(_r, "%s+%s=%s") + '\n',
  ["cool", 1337, "stuff"].reduce(_r, "%s %s %s")
);

CPHPython
fuente
44
Aquí hay una versión que utiliza este enfoque para crear una printffunción simplificada : jsfiddle.net/11szrbx9
Dem Pilafian
1
Y aquí es otra usando ES6, en una línea:(...a) => {return a.reduce((p: string, c: any) => p.replace(/%s/, c));
dtasev
No es necesario String.prototype.formaten ES6: ((a,b,c)=>`${a}, ${b} and ${c}`)(...['me', 'myself', 'I'])(tenga en cuenta que esto es un poco redundante para adaptarse mejor a su ejemplo)
Tino
Tendría que implementar funciones de reemplazo para cada uno de printflos especificadores de tipo e incluir lógica para los prefijos de relleno. Iterar sobre la cadena de formato de manera sensata parece ser el desafío menor aquí, en mi humilde opinión. Sin embargo, una solución ordenada si solo necesita reemplazos de cadenas.
collapsar
51

Aquí hay una implementación mínima de sprintf en JavaScript: solo hace "% s" y "% d", pero he dejado espacio para que se extienda. Es inútil para el OP, pero otras personas que se topan con este hilo proveniente de Google podrían beneficiarse de él.

function sprintf() {
    var args = arguments,
    string = args[0],
    i = 1;
    return string.replace(/%((%)|s|d)/g, function (m) {
        // m is the matched format, e.g. %s, %d
        var val = null;
        if (m[2]) {
            val = m[2];
        } else {
            val = args[i];
            // A switch statement so that the formatter can be extended. Default is %s
            switch (m) {
                case '%d':
                    val = parseFloat(val);
                    if (isNaN(val)) {
                        val = 0;
                    }
                    break;
            }
            i++;
        }
        return val;
    });
}

Ejemplo:

alert(sprintf('Latitude: %s, Longitude: %s, Count: %d', 41.847, -87.661, 'two'));
// Expected output: Latitude: 41.847, Longitude: -87.661, Count: 0

A diferencia de soluciones similares en respuestas anteriores, esta realiza todas las sustituciones de una vez , por lo que no reemplazará partes de valores reemplazados previamente.

Luke Madhanga
fuente
31

Los programadores de JavaScript pueden usar String.prototype.sprintf en https://github.com/ildar-shaimordanov/jsxt/blob/master/js/String.js . A continuación se muestra un ejemplo:

var d = new Date();
var dateStr = '%02d:%02d:%02d'.sprintf(
    d.getHours(), 
    d.getMinutes(), 
    d.getSeconds());
revs jsxt
fuente
@JasonMorgan, he compartido el enlace de trabajo en GitHub. Vea la respuesta corregida.
jsxt
24

Agregando a zippoxerla respuesta de, uso esta función:

String.prototype.format = function () {
    var a = this, b;
    for (b in arguments) {
        a = a.replace(/%[a-z]/, arguments[b]);
    }
    return a; // Make chainable
};

var s = 'Hello %s The magic number is %d.';
s.format('world!', 12); // Hello World! The magic number is 12.

También tengo una versión no prototipo que uso con más frecuencia por su sintaxis tipo Java:

function format() {
    var a, b, c;
    a = arguments[0];
    b = [];
    for(c = 1; c < arguments.length; c++){
        b.push(arguments[c]);
    }
    for (c in b) {
        a = a.replace(/%[a-z]/, b[c]);
    }
    return a;
}
format('%d ducks, 55 %s', 12, 'cats'); // 12 ducks, 55 cats

Actualización de ES 2015

Todas las novedades en ES 2015 hacen que esto sea mucho más fácil:

function format(fmt, ...args){
    return fmt
        .split("%%")
        .reduce((aggregate, chunk, i) =>
            aggregate + chunk + (args[i] || ""), "");
}

format("Hello %%! I ate %% apples today.", "World", 44);
// "Hello World, I ate 44 apples today."

Pensé que, dado que esto, como los más antiguos, no analiza las letras, también podría usar un solo token %%. Esto tiene el beneficio de ser obvio y no dificultar el uso de uno solo %. Sin embargo, si lo necesita %%por algún motivo, deberá reemplazarlo por sí mismo:

format("I love percentage signs! %%", "%%");
// "I love percentage signs! %%"
Braden Best
fuente
3
Esta respuesta fue excelente para una copia rápida pegar en una función existente. No requiere ninguna descarga, etc.
Nick
@ Nick sí, esa es la idea :)
Braden Best
21

+1 Zippo con la excepción de que el cuerpo de la función debe ser el siguiente o, de lo contrario, agrega la cadena actual en cada iteración:

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};
usuario437231
fuente
1
No funcionó en Firefox. El depurador muestra arg como indefinido.
xiao 啸
No reemplaza el segundo carácter en 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); el que se convierte el resultado The ASP is dead. Don't code {0}. Code PHP that is open source!. Una cosa más for(arg in arguments)no funciona en IE. Lo reemplacé con for (arg = 0; arg <arguments.length; arg++)
samarjit samanta
2
Como referencia, for...inno funcionará en todos los navegadores ya que este código lo espera. Recorrerá todas las propiedades enumerables, que en algunos navegadores incluirán arguments.length, y en otros ni siquiera incluirán los argumentos. En cualquier caso, si Object.prototypese agrega a, cualquier agregado probablemente se incluirá en el grupo. El código debe usar un forbucle estándar , en lugar de for...in.
cHao
Debería proponer una edición de respuesta en lugar de una respuesta duplicada. Este duplicado esta respuesta
RousseauAlexandre
19

Quiero compartir mi solución para el 'problema'. No he reinventado la rueda, pero trato de encontrar una solución basada en lo que JavaScript ya hace. La ventaja es que obtienes todas las conversiones implícitas de forma gratuita. Establecer la propiedad prototipo $ de String proporciona una sintaxis muy agradable y compacta (ver ejemplos a continuación). Tal vez no sea la forma más eficiente, pero en la mayoría de los casos tratar con la salida no tiene que estar súper optimizado.

String.form = function(str, arr) {
    var i = -1;
    function callback(exp, p0, p1, p2, p3, p4) {
        if (exp=='%%') return '%';
        if (arr[++i]===undefined) return undefined;
        exp  = p2 ? parseInt(p2.substr(1)) : undefined;
        var base = p3 ? parseInt(p3.substr(1)) : undefined;
        var val;
        switch (p4) {
            case 's': val = arr[i]; break;
            case 'c': val = arr[i][0]; break;
            case 'f': val = parseFloat(arr[i]).toFixed(exp); break;
            case 'p': val = parseFloat(arr[i]).toPrecision(exp); break;
            case 'e': val = parseFloat(arr[i]).toExponential(exp); break;
            case 'x': val = parseInt(arr[i]).toString(base?base:16); break;
            case 'd': val = parseFloat(parseInt(arr[i], base?base:10).toPrecision(exp)).toFixed(0); break;
        }
        val = typeof(val)=='object' ? JSON.stringify(val) : val.toString(base);
        var sz = parseInt(p1); /* padding size */
        var ch = p1 && p1[0]=='0' ? '0' : ' '; /* isnull? */
        while (val.length<sz) val = p0 !== undefined ? val+ch : ch+val; /* isminus? */
       return val;
    }
    var regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g;
    return str.replace(regex, callback);
}

String.prototype.$ = function() {
    return String.form(this, Array.prototype.slice.call(arguments));
}

Aquí están algunos ejemplos:

String.format("%s %s", [ "This is a string", 11 ])
console.log("%s %s".$("This is a string", 11))
var arr = [ "12.3", 13.6 ]; console.log("Array: %s".$(arr));
var obj = { test:"test", id:12 }; console.log("Object: %s".$(obj));
console.log("%c", "Test");
console.log("%5d".$(12)); // '   12'
console.log("%05d".$(12)); // '00012'
console.log("%-5d".$(12)); // '12   '
console.log("%5.2d".$(123)); // '  120'
console.log("%5.2f".$(1.1)); // ' 1.10'
console.log("%10.2e".$(1.1)); // '   1.10e+0'
console.log("%5.3p".$(1.12345)); // ' 1.12'
console.log("%5x".$(45054)); // ' affe'
console.log("%20#2x".$("45054")); // '    1010111111111110'
console.log("%6#2d".$("111")); // '     7'
console.log("%6#16d".$("affe")); // ' 45054'
Rtlprmft
fuente
desafortunadamente al menos # y + no se implementan para flotadores. Aquí hay una referencia para la función en c: tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Daniel
14

Utilizo una pequeña biblioteca llamada String.format para JavaScript que admite la mayoría de las capacidades de cadena de formato (incluido el formato de números y fechas), y utiliza la sintaxis .NET. El script en sí es más pequeño que 4 kB, por lo que no crea mucha sobrecarga.

Sven N
fuente
Eché un vistazo a esa biblioteca y se ve muy bien. Estaba cabreado cuando vi que la descarga era un EXE. ¿De qué diablos se trata eso? No se descargó.
jessegavin
A menudo, un archivo descargable que es un EXE no es más que un "ZIP autoextraíble". Ejecútelo y se descomprimirá. Esto es bastante conveniente PERO porque se parece mucho al malware, el formato ya no se usa en la web con tanta frecuencia.
Chuck Kollars 01 de
Si bien este enlace puede responder la pregunta, es mejor incluir aquí las partes esenciales de la respuesta y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden volverse inválidas si la página vinculada cambia.
Starmole
@starmole el enlace es a una biblioteca de JavaScript (minificada) de 4 kB . No creo que pegarlo en la respuesta sea una buena idea.
ivarni
Tienes razón pegando que no sería mejor. Acabo de recibir este comentario para una revisión aleatoria, y lo comenté antes de que no me gustara. Para mí, stackoverflow es mejor al proporcionar explicaciones en lugar de soluciones preparadas (que es el enlace). Tampoco quiero alentar a las personas a publicar o descargar código de recuadro negro.
Starmole
14

Muy elegante:

String.prototype.format = function (){
    var args = arguments;
    return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (curlyBrack, index) {
        return ((curlyBrack == "{{") ? "{" : ((curlyBrack == "}}") ? "}" : args[index]));
    });
};

// Usage:
"{0}{1}".format("{1}", "{0}")

El crédito va a (Enlace roto) https://gist.github.com/0i0/1519811

lior hakim
fuente
Este es el único que maneja los corchetes de escape {{0}}, así como cosas similares {0}{1}.format("{1}", "{0}"). Debería estar en la cima!
Juan
11

Si está buscando manejar el separador de miles, realmente debería usar toLocaleString () de la clase de Número JavaScript ya que formateará la cadena para la región del usuario.

La clase de fecha de JavaScript puede formatear fechas y horas localizadas.

17 de 26
fuente
1
En realidad, es un conjunto del usuario como una configuración en la aplicación (no en la máquina en la que está encendida), pero lo echaré un vistazo, gracias
Chris S
agregue algunos ejemplos para que todos puedan entenderlo rápidamente.
Bhushan Kawadkar
9

El proyecto PHPJS ha escrito implementaciones de JavaScript para muchas de las funciones de PHP. Dado que la sprintf()función de PHP es básicamente la misma que la de C printf(), su implementación de JavaScript debería satisfacer sus necesidades.

Spudley
fuente
9

Yo uso este:

String.prototype.format = function() {
    var newStr = this, i = 0;
    while (/%s/.test(newStr))
        newStr = newStr.replace("%s", arguments[i++])

    return newStr;
}

Entonces lo llamo:

"<h1>%s</h1><p>%s</p>".format("Header", "Just a test!");
Steven Penny
fuente
9

Tengo una solución muy cercana a la de Peter, pero se trata de números y objetos.

if (!String.prototype.format) {
  String.prototype.format = function() {
    var args;
    args = arguments;
    if (args.length === 1 && args[0] !== null && typeof args[0] === 'object') {
      args = args[0];
    }
    return this.replace(/{([^}]*)}/g, function(match, key) {
      return (typeof args[key] !== "undefined" ? args[key] : match);
    });
  };
}

Tal vez podría ser aún mejor tratar con todos los casos profundos, pero para mis necesidades esto está bien.

"This is an example from {name}".format({name:"Blaine"});
"This is an example from {0}".format("Blaine");

PD: esta función es muy buena si está utilizando traducciones en marcos de plantillas como AngularJS :

<h1> {{('hello-message'|translate).format(user)}} <h1>
<h1> {{('hello-by-name'|translate).format( user ? user.name : 'You' )}} <h1>

Donde el en.json es algo así

{
    "hello-message": "Hello {name}, welcome.",
    "hello-by-name": "Hello {0}, welcome."
}
Thiago Mata
fuente
la parte [^}] en la expresión regular es innecesaria ... use {(. *?)} en su lugar, o mejor {([\ s \ S] *?)} para que coincida con la nueva línea también.
rawiro 01 de
7

Una versión ligeramente diferente, la que prefiero (esta usa {xxx} tokens en lugar de {0} argumentos numerados, esto es mucho más autodocumentado y se adapta mucho mejor a la localización):

String.prototype.format = function(tokens) {
  var formatted = this;
  for (var token in tokens)
    if (tokens.hasOwnProperty(token))
      formatted = formatted.replace(RegExp("{" + token + "}", "g"), tokens[token]);
  return formatted;
};

Una variación sería:

  var formatted = l(this);

que llama primero a una función de localización l ().

Peter
fuente
6

Para aquellos a quienes les gusta Node.JS y su util.formatcaracterística, lo acabo de extraer en su formulario JavaScript vainilla (con solo funciones que utiliza util.format):

exports = {};

function isString(arg) {
    return typeof arg === 'string';
}
function isNull(arg) {
    return arg === null;
}
function isObject(arg) {
    return typeof arg === 'object' && arg !== null;
}
function isBoolean(arg) {
    return typeof arg === 'boolean';
}
function isUndefined(arg) {
    return arg === void 0;
}
function stylizeNoColor(str, styleType) {
    return str;
}
function stylizeWithColor(str, styleType) {
    var style = inspect.styles[styleType];

    if (style) {
        return '\u001b[' + inspect.colors[style][0] + 'm' + str +
            '\u001b[' + inspect.colors[style][3] + 'm';
    } else {
        return str;
    }
}
function isFunction(arg) {
    return typeof arg === 'function';
}
function isNumber(arg) {
    return typeof arg === 'number';
}
function isSymbol(arg) {
    return typeof arg === 'symbol';
}
function formatPrimitive(ctx, value) {
    if (isUndefined(value))
        return ctx.stylize('undefined', 'undefined');
    if (isString(value)) {
        var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
                .replace(/'/g, "\\'")
                .replace(/\\"/g, '"') + '\'';
        return ctx.stylize(simple, 'string');
    }
    if (isNumber(value)) {
        // Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
        // so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
        if (value === 0 && 1 / value < 0)
            return ctx.stylize('-0', 'number');
        return ctx.stylize('' + value, 'number');
    }
    if (isBoolean(value))
        return ctx.stylize('' + value, 'boolean');
    // For some reason typeof null is "object", so special case here.
    if (isNull(value))
        return ctx.stylize('null', 'null');
    // es6 symbol primitive
    if (isSymbol(value))
        return ctx.stylize(value.toString(), 'symbol');
}
function arrayToHash(array) {
    var hash = {};

    array.forEach(function (val, idx) {
        hash[val] = true;
    });

    return hash;
}
function objectToString(o) {
    return Object.prototype.toString.call(o);
}
function isDate(d) {
    return isObject(d) && objectToString(d) === '[object Date]';
}
function isError(e) {
    return isObject(e) &&
        (objectToString(e) === '[object Error]' || e instanceof Error);
}
function isRegExp(re) {
    return isObject(re) && objectToString(re) === '[object RegExp]';
}
function formatError(value) {
    return '[' + Error.prototype.toString.call(value) + ']';
}
function formatPrimitiveNoColor(ctx, value) {
    var stylize = ctx.stylize;
    ctx.stylize = stylizeNoColor;
    var str = formatPrimitive(ctx, value);
    ctx.stylize = stylize;
    return str;
}
function isArray(ar) {
    return Array.isArray(ar);
}
function hasOwnProperty(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
    var name, str, desc;
    desc = Object.getOwnPropertyDescriptor(value, key) || {value: value[key]};
    if (desc.get) {
        if (desc.set) {
            str = ctx.stylize('[Getter/Setter]', 'special');
        } else {
            str = ctx.stylize('[Getter]', 'special');
        }
    } else {
        if (desc.set) {
            str = ctx.stylize('[Setter]', 'special');
        }
    }
    if (!hasOwnProperty(visibleKeys, key)) {
        name = '[' + key + ']';
    }
    if (!str) {
        if (ctx.seen.indexOf(desc.value) < 0) {
            if (isNull(recurseTimes)) {
                str = formatValue(ctx, desc.value, null);
            } else {
                str = formatValue(ctx, desc.value, recurseTimes - 1);
            }
            if (str.indexOf('\n') > -1) {
                if (array) {
                    str = str.split('\n').map(function (line) {
                        return '  ' + line;
                    }).join('\n').substr(2);
                } else {
                    str = '\n' + str.split('\n').map(function (line) {
                        return '   ' + line;
                    }).join('\n');
                }
            }
        } else {
            str = ctx.stylize('[Circular]', 'special');
        }
    }
    if (isUndefined(name)) {
        if (array && key.match(/^\d+$/)) {
            return str;
        }
        name = JSON.stringify('' + key);
        if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
            name = name.substr(1, name.length - 2);
            name = ctx.stylize(name, 'name');
        } else {
            name = name.replace(/'/g, "\\'")
                .replace(/\\"/g, '"')
                .replace(/(^"|"$)/g, "'")
                .replace(/\\\\/g, '\\');
            name = ctx.stylize(name, 'string');
        }
    }

    return name + ': ' + str;
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
    var output = [];
    for (var i = 0, l = value.length; i < l; ++i) {
        if (hasOwnProperty(value, String(i))) {
            output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
                String(i), true));
        } else {
            output.push('');
        }
    }
    keys.forEach(function (key) {
        if (!key.match(/^\d+$/)) {
            output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
                key, true));
        }
    });
    return output;
}
function reduceToSingleString(output, base, braces) {
    var length = output.reduce(function (prev, cur) {
        return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
    }, 0);

    if (length > 60) {
        return braces[0] +
            (base === '' ? '' : base + '\n ') +
            ' ' +
            output.join(',\n  ') +
            ' ' +
            braces[1];
    }

    return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}
function formatValue(ctx, value, recurseTimes) {
    // Provide a hook for user-specified inspect functions.
    // Check that value is an object with an inspect function on it
    if (ctx.customInspect &&
        value &&
        isFunction(value.inspect) &&
            // Filter out the util module, it's inspect function is special
        value.inspect !== exports.inspect &&
            // Also filter out any prototype objects using the circular check.
        !(value.constructor && value.constructor.prototype === value)) {
        var ret = value.inspect(recurseTimes, ctx);
        if (!isString(ret)) {
            ret = formatValue(ctx, ret, recurseTimes);
        }
        return ret;
    }

    // Primitive types cannot have properties
    var primitive = formatPrimitive(ctx, value);
    if (primitive) {
        return primitive;
    }

    // Look up the keys of the object.
    var keys = Object.keys(value);
    var visibleKeys = arrayToHash(keys);

    if (ctx.showHidden) {
        keys = Object.getOwnPropertyNames(value);
    }

    // This could be a boxed primitive (new String(), etc.), check valueOf()
    // NOTE: Avoid calling `valueOf` on `Date` instance because it will return
    // a number which, when object has some additional user-stored `keys`,
    // will be printed out.
    var formatted;
    var raw = value;
    try {
        // the .valueOf() call can fail for a multitude of reasons
        if (!isDate(value))
            raw = value.valueOf();
    } catch (e) {
        // ignore...
    }

    if (isString(raw)) {
        // for boxed Strings, we have to remove the 0-n indexed entries,
        // since they just noisey up the output and are redundant
        keys = keys.filter(function (key) {
            return !(key >= 0 && key < raw.length);
        });
    }

    // Some type of object without properties can be shortcutted.
    if (keys.length === 0) {
        if (isFunction(value)) {
            var name = value.name ? ': ' + value.name : '';
            return ctx.stylize('[Function' + name + ']', 'special');
        }
        if (isRegExp(value)) {
            return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
        }
        if (isDate(value)) {
            return ctx.stylize(Date.prototype.toString.call(value), 'date');
        }
        if (isError(value)) {
            return formatError(value);
        }
        // now check the `raw` value to handle boxed primitives
        if (isString(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[String: ' + formatted + ']', 'string');
        }
        if (isNumber(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[Number: ' + formatted + ']', 'number');
        }
        if (isBoolean(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
        }
    }

    var base = '', array = false, braces = ['{', '}'];

    // Make Array say that they are Array
    if (isArray(value)) {
        array = true;
        braces = ['[', ']'];
    }

    // Make functions say that they are functions
    if (isFunction(value)) {
        var n = value.name ? ': ' + value.name : '';
        base = ' [Function' + n + ']';
    }

    // Make RegExps say that they are RegExps
    if (isRegExp(value)) {
        base = ' ' + RegExp.prototype.toString.call(value);
    }

    // Make dates with properties first say the date
    if (isDate(value)) {
        base = ' ' + Date.prototype.toUTCString.call(value);
    }

    // Make error with message first say the error
    if (isError(value)) {
        base = ' ' + formatError(value);
    }

    // Make boxed primitive Strings look like such
    if (isString(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[String: ' + formatted + ']';
    }

    // Make boxed primitive Numbers look like such
    if (isNumber(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[Number: ' + formatted + ']';
    }

    // Make boxed primitive Booleans look like such
    if (isBoolean(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[Boolean: ' + formatted + ']';
    }

    if (keys.length === 0 && (!array || value.length === 0)) {
        return braces[0] + base + braces[1];
    }

    if (recurseTimes < 0) {
        if (isRegExp(value)) {
            return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
        } else {
            return ctx.stylize('[Object]', 'special');
        }
    }

    ctx.seen.push(value);

    var output;
    if (array) {
        output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
    } else {
        output = keys.map(function (key) {
            return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
        });
    }

    ctx.seen.pop();

    return reduceToSingleString(output, base, braces);
}
function inspect(obj, opts) {
    // default options
    var ctx = {
        seen: [],
        stylize: stylizeNoColor
    };
    // legacy...
    if (arguments.length >= 3) ctx.depth = arguments[2];
    if (arguments.length >= 4) ctx.colors = arguments[3];
    if (isBoolean(opts)) {
        // legacy...
        ctx.showHidden = opts;
    } else if (opts) {
        // got an "options" object
        exports._extend(ctx, opts);
    }
    // set default options
    if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
    if (isUndefined(ctx.depth)) ctx.depth = 2;
    if (isUndefined(ctx.colors)) ctx.colors = false;
    if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
    if (ctx.colors) ctx.stylize = stylizeWithColor;
    return formatValue(ctx, obj, ctx.depth);
}
exports.inspect = inspect;


// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = {
    'bold': [1, 22],
    'italic': [3, 23],
    'underline': [4, 24],
    'inverse': [7, 27],
    'white': [37, 39],
    'grey': [90, 39],
    'black': [30, 39],
    'blue': [34, 39],
    'cyan': [36, 39],
    'green': [32, 39],
    'magenta': [35, 39],
    'red': [31, 39],
    'yellow': [33, 39]
};

// Don't use 'blue' not visible on cmd.exe
inspect.styles = {
    'special': 'cyan',
    'number': 'yellow',
    'boolean': 'yellow',
    'undefined': 'grey',
    'null': 'bold',
    'string': 'green',
    'symbol': 'green',
    'date': 'magenta',
    // "name": intentionally not styling
    'regexp': 'red'
};


var formatRegExp = /%[sdj%]/g;
exports.format = function (f) {
    if (!isString(f)) {
        var objects = [];
        for (var j = 0; j < arguments.length; j++) {
            objects.push(inspect(arguments[j]));
        }
        return objects.join(' ');
    }

    var i = 1;
    var args = arguments;
    var len = args.length;
    var str = String(f).replace(formatRegExp, function (x) {
        if (x === '%%') return '%';
        if (i >= len) return x;
        switch (x) {
            case '%s':
                return String(args[i++]);
            case '%d':
                return Number(args[i++]);
            case '%j':
                try {
                    return JSON.stringify(args[i++]);
                } catch (_) {
                    return '[Circular]';
                }
            default:
                return x;
        }
    });
    for (var x = args[i]; i < len; x = args[++i]) {
        if (isNull(x) || !isObject(x)) {
            str += ' ' + x;
        } else {
            str += ' ' + inspect(x);
        }
    }
    return str;
};

Cosechado de: https://github.com/joyent/node/blob/master/lib/util.js

A
fuente
6

Para formateo básico:

var template = jQuery.validator.format("{0} is not a valid value");
var result = template("abc");
Evgeny Gerbut
fuente
5

Tengo un formateador un poco más largo para JavaScript aquí ...

Puede formatear de varias maneras:

  • String.format(input, args0, arg1, ...)
  • String.format(input, obj)
  • "literal".format(arg0, arg1, ...)
  • "literal".format(obj)

Además, si ha dicho un ObjectBase.prototype.format (como con DateJS ) lo usará.

Ejemplos ...

var input = "numbered args ({0}-{1}-{2}-{3})";
console.log(String.format(input, "first", 2, new Date()));
//Outputs "numbered args (first-2-Thu May 31 2012...Time)-{3})"

console.log(input.format("first", 2, new Date()));
//Outputs "numbered args(first-2-Thu May 31 2012...Time)-{3})"

console.log(input.format(
    "object properties ({first}-{second}-{third:yyyy-MM-dd}-{fourth})"
    ,{
        'first':'first'
        ,'second':2
        ,'third':new Date() //assumes Date.prototype.format method
    }
));
//Outputs "object properties (first-2-2012-05-31-{3})"

También me he alias con .asFormat y tengo algo de detección en caso de que ya haya un string.format (como con MS Ajax Toolkit (Odio esa biblioteca).

Rastreador1
fuente
5

En caso de que alguien necesite una función para evitar contaminar el alcance global, aquí está la función que hace lo mismo:

  function _format (str, arr) {
    return str.replace(/{(\d+)}/g, function (match, number) {
      return typeof arr[number] != 'undefined' ? arr[number] : match;
    });
  };
Afshin Mehrabani
fuente
3

Podemos usar una biblioteca simple de operación de cadenas String.Format ligera para Typecript .

String.Format ():

var id = image.GetId()
String.Format("image_{0}.jpg", id)
output: "image_2db5da20-1c5d-4f1a-8fd4-b41e34c8c5b5.jpg";

Formato de cadena para especificadores:

var value = String.Format("{0:L}", "APPLE"); //output "apple"

value = String.Format("{0:U}", "apple"); // output "APPLE"

value = String.Format("{0:d}", "2017-01-23 00:00"); //output "23.01.2017"


value = String.Format("{0:s}", "21.03.2017 22:15:01") //output "2017-03-21T22:15:01"

value = String.Format("{0:n}", 1000000);
//output "1.000.000"

value = String.Format("{0:00}", 1);
//output "01"

Formato de cadena para objetos que incluyen especificadores:

var fruit = new Fruit();
fruit.type = "apple";
fruit.color = "RED";
fruit.shippingDate = new Date(2018, 1, 1);
fruit.amount = 10000;

String.Format("the {type:U} is {color:L} shipped on {shippingDate:s} with an amount of {amount:n}", fruit);
// output: the APPLE is red shipped on 2018-01-01 with an amount of 10.000
Murtaza Hussain
fuente
2

No vi la String.formatvariante:

String.format = function (string) {
    var args = Array.prototype.slice.call(arguments, 1, arguments.length);
    return string.replace(/{(\d+)}/g, function (match, number) {
        return typeof args[number] != "undefined" ? args[number] : match;
    });
};
Jerone
fuente
2

Para usar con las funciones de éxito jQuery.ajax (). Pase solo un argumento único y la cadena de reemplazo con las propiedades de ese objeto como {propertyName}:

String.prototype.format = function () {
    var formatted = this;
    for (var prop in arguments[0]) {
        var regexp = new RegExp('\\{' + prop + '\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[0][prop]);
    }
    return formatted;
};

Ejemplo:

var userInfo = ("Email: {Email} - Phone: {Phone}").format({ Email: "[email protected]", Phone: "123-123-1234" });
Raymond Powell
fuente