JavaScript equivalente a printf / String.Format

1970

Estou procurando um bom equivalente em JavaScript do C / PHP printf()ou para programadores de C # / Java String.Format()( IFormatProviderpara .NET).

Meu requisito básico é um formato separador de milhar para números por enquanto, mas algo que lide com muitas combinações (incluindo datas) seria bom.

Sei que a biblioteca Ajax da Microsoft fornece uma versão do String.Format(), mas não queremos toda a sobrecarga dessa estrutura.

Chris S
fonte
2
Além de todas as ótimas respostas abaixo, dê uma olhada nesta: stackoverflow.com/a/2648463/1712065, que IMO é a solução mais eficiente para esse problema.
Annie
1
Eu escrevi um barato que usa sintaxe printf do tipo C.
Braden Best
var search = [$ scope.dog, "1"]; var url = vsprintf (" terra / Serviços / dogSearch.svc / FindMe /% s /% s ", pesquisa); *** Para o nó, você pode obter seu módulo "npm install sprintf-js"
Jenna Leaf
Eu também escrevi uma função simples para conseguir isso; stackoverflow.com/a/54345052/5927126
AnandShanbhag

Respostas:

1110

A partir do ES6, você pode usar seqüências de caracteres de modelo:

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

Veja a resposta de Kim abaixo para obter detalhes.


De outra forma:

Experimente sprintf () para JavaScript .


Se você realmente deseja fazer um método de formato simples por conta própria, não faça as substituições sucessivamente, mas faça-as simultaneamente.

Como a maioria das outras propostas mencionadas falha quando uma string de substituição anterior também contém uma sequência de formatos como esta:

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

Normalmente você esperaria que a saída fosse, {1}{0}mas a saída real é {1}{1}. O mesmo acontece com uma substituição simultânea, como na sugestão de medo de ser destruída .

Gumbo
fonte
16
Se apenas uma conversão simples de número para string for desejada, o num.toFixed()método pode ser suficiente!
amigos estão dizendo sobre heltonbiker
@MaksymilianMajer que parece ser algo massivamente diferente.
Evan Carroll
@EvanCarroll você está certo. Na época em que escrevi o comentário, o repositório sprintf() for JavaScriptnão estava disponível. underscore.stringpossui mais recursos além do sprintf, que é baseado na sprintf() for JavaScriptimplementação. Fora isso, a biblioteca é um projeto totalmente diferente.
Maksymilian Majer
@MaksymilianMajer certo, apenas dizendo que esta resposta está morta, e o link se deteriorou. Ele precisa ser totalmente eliminado.
Evan Carroll
2
Esta resposta não deve mais ser aceita. A partir do ES6, isso é incorporado à linguagem javascript (nos navegadores e no NodeJS). Veja a resposta de @Kim abaixo.
Ryan Shillington
1391

Com base nas soluções 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")

saídas

O ASP está morto, mas o ASP.NET está vivo! ASP {2}


Se você preferir não modificar Stringo protótipo:

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
      ;
    });
  };
}

Dá a você muito mais familiar:

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

com o mesmo resultado:

O ASP está morto, mas o ASP.NET está vivo! ASP {2}

medo
fonte
12
o || O truque não funciona se args [número] for 0. Deveria fazer um explícito if () para ver se (args [número] === indefinido).
fserb
4
na declaração else da abreviação if, por que não apenas "match" em vez de "'{' + number + '}'". match deve ser igual a essa string.
Mikeycgto
4
Se você tiver várias strings anexadas uma à outra (com o +operador-), certifique-se de colocar a String completa entre parênteses: ("asd {0}"+"fas {1}").format("first", "second");caso contrário, a função será aplicada apenas à última string que foi anexada.
Lukas Knuth
3
Isso muda de maneira sutil e sutil o resultado. Imagine 'foo {0}'.format(fnWithNoReturnValue()). No momento, ele retornaria foo {0}. Com suas alterações, ele retornaria foo undefined.
Medo # f #
2
@avenmore: / \ {(\ d +) \} / g
Hozuki
491

É engraçado porque o Stack Overflow realmente tem sua própria função de formatação para o Stringprotótipo chamado formatUnicorn. Tente! Entre no console e digite algo como:

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

Firebug

Você obtém esta saída:

Hello, Gabriel, are you feeling OK?

Você pode usar objetos, matrizes e seqüências de caracteres como argumentos! Peguei o código e o reformulei para produzir uma nova versão do 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;
};

Observe a Array.prototype.slice.call(arguments)chamada inteligente - isso significa que, se você lançar argumentos que são seqüências de caracteres ou números, e não um único objeto no estilo JSON, obtém o String.Formatcomportamento do C # quase exatamente.

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

Isso porque Array's sliceforçarão tudo o que está em argumentsem um Array, se era originalmente ou não, e keyserá o índice (0, 1, 2 ...) de cada elemento da matriz coagidos a uma string (por exemplo, '0', de modo "\\{0\\}"para seu primeiro padrão de expressão regular).

Arrumado.

Gabriel Nahmias
fonte
402
É muito legal responder a uma pergunta sobre stackoverflow com o código de stackoverflow, +1
Sneakyness
5
@JamesManning O regex permite a flag global ( g), que pode substituir a mesma chave mais de uma vez. No exemplo acima, você pode usar {name}várias vezes na mesma frase e substituí-las todas.
KrekkieD
3
Isso parece terrivelmente frágil, para ser sincero. O que acontece, por exemplo, se nameé "blah {adjective} blah"?
sam hocevar 16/01
5
@ ruffin “um pouco hiperbólico”? O código que é enganado na interpretação dos dados do usuário como cadeias de formato é uma categoria inteira de vulnerabilidades . 98,44% está além da medíocre .
Sam Hocevar
3
@samhocevar Eu não posso acreditar em você, Little Bobby me apresentou. ;) Se você estiver executando um texto processado pelo JavaScript do cliente no servidor do banco de dados sem nenhuma verificação de segurança, a Heaven ajuda a todos nós. ; ^) Olha, não deve haver nada que qualquer usuário possa enviar de um cliente (por exemplo, Postman) que ultrapasse a segurança do servidor. E você deve assumir que qualquer coisa perigosa que possa ser enviada de um cliente será . Ou seja, se você precisar de 100% de segurança do código JavaScript do cliente, que é sempre editável pelo usuário, e você acha que essa função pode abrir um risco de segurança, você está jogando no jogo errado.
Ruffin
325

Formatação de números em JavaScript

Cheguei a esta página de perguntas na esperança de descobrir como formatar números em JavaScript, sem introduzir outra biblioteca. Aqui está o que eu encontrei:

Arredondando números de ponto flutuante

O equivalente sprintf("%.2f", num)em JavaScript parece ser num.toFixed(2), que formata numduas casas decimais, com arredondamento (mas consulte o comentário de @ ars265 sobre Math.roundabaixo).

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

Forma exponencial

O equivalente a sprintf("%.2e", num)é num.toExponential(2).

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

Bases hexadecimais e outras

Para imprimir números na base B, tente num.toString(B). O JavaScript suporta conversão automática de e para as bases 2 a 36 (além disso, alguns navegadores têm suporte limitado à codificação base64 ).

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

Páginas de referência

Tutorial rápido sobre formatação de número JS

Página de referência da Mozilla para toFixed () (com links para toPrecision (), toExponential (), toLocaleString (), ...)

rescdsk
fonte
23
Não seria melhor colocar o número literal entre parênteses, em vez de deixar um espaço em branco estranho lá?
Rmobis
7
Provavelmente isso pareceria melhor, verdade. Mas meu objetivo é apenas apontar a armadilha do erro de sintaxe.
Rescdsk # 1/12
4
Apenas uma observação: se você estiver usando um navegador mais antigo ou suportando navegadores mais antigos, alguns navegadores implementaram o toFixed incorretamente, usando o Math.round no lugar do toFixed é uma solução melhor.
Ars265
7
@Raphael_ e @rescdsk: ..também funciona:33333..toExponential(2);
Peter Jaric
Ou (33333) .toExponential (2)
Jonathan
245

A partir do ES6, você pode usar seqüências de caracteres de modelo :

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

Esteja ciente de que as seqüências de caracteres do modelo são cercadas por backticks `em vez de aspas simples.

Para mais informações:

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

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

Nota: Verifique o site do mozilla para encontrar uma lista de navegadores suportados.

Kim
fonte
61
O problema com as seqüências de caracteres de modelo é que elas parecem ser executadas imediatamente, tornando seu uso como, digamos, uma tabela de seqüências de caracteres do tipo i18n completamente inútil. Não consigo definir a string no início e fornecer os parâmetros para usar mais tarde e / ou repetidamente.
precisa saber é o seguinte
4
@ Tustin2121 Você está certo de que eles não foram criados para serem atribuídos a uma variável, o que é um pouco preocupante, mas é fácil o suficiente trabalhar com as tendências de execução instantânea das seqüências de modelo, se você as esconder em uma função. Veja jsfiddle.net/zvcm70pa
inanutshellus
13
@ Tustin2121 não há diferença entre usar uma string de modelo ou concatenação de string de estilo antigo, seu açúcar para a mesma coisa. Você precisaria agrupar um gerador de string de estilo antigo em uma função simples e a mesma coisa funciona bem com modelos de string. const compile = (x, y) => `I can call this template string whenever I want.. x=${x}, y=${y}`...compile(30, 20)
cchamberlain
4
esta solução não funcionará para a sequência de caracteres de formato passada na variável (do servidor por exemplo)
user993954 15/16
1
@inanutshellus Isso funciona bem se a função de modelo estiver definida na mesma máquina em que é executada. Até onde eu sei, você não pode passar uma função como JSON, portanto, armazenar funções de modelo em um banco de dados não funciona bem.
Styfle
171

jsxt, Zippo

Esta opção se encaixa melhor.

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;
};

Com esta opção eu posso substituir strings como estas:

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

Com o seu código, o segundo {0} não seria substituído. ;)

Filipiz
fonte
3
gist.github.com/1049426 Atualizei seu exemplo com esta abordagem. Inúmeros benefícios, incluindo salvar a implementação nativa, se existir, restringir etc. Tentei remover expressões regulares, mas o tipo de welp é necessário para a substituição global. : - /
tbranyen
6
jsxt é licenciado sob GPL, infelizmente
AndiDog
109

Eu uso esta função simples:

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

Isso é muito semelhante ao string.format:

"{0} is dead, but {1} is alive!".format("ASP", "ASP.NET")
Zippoxer
fonte
1
por que +=?, deveriaformatted = this.replace("{" + arg + "}", arguments[arg]);
guilin #
2
Eu acho que o código ainda não está correto. O correto deve ser como Filipiz postou.
Wenqiang
3
Para referência, for...innão funcionará em todos os navegadores, como este código espera. Ele percorrerá todas as propriedades enumeráveis, que em alguns navegadores incluirão arguments.lengthe em outros nem sequer incluirão os próprios argumentos. De qualquer forma, se Object.prototypefor adicionado a, quaisquer acréscimos provavelmente serão incluídos no grupo. O código deve estar usando um forloop padrão , e não for...in.
cHao 6/02/11
3
Isso falhará se uma substituição anterior contiver também uma sequência de formato:"{0} is dead, but {1} is alive!".format("{1}", "ASP.NET") === "ASP.NET is dead, but ASP.NET is alive!"
Gumbo 28/03
6
A variável argé global. Você precisa fazer isso:for (var arg in arguments) {
Pauan
68

Para usuários do Node.js, existe util.formatuma funcionalidade semelhante à printf:

util.format("%s world", "Hello")
George Eracleous
fonte
1
Este não suporta% x como de v0.10.26 Node
Max Krohn
Não suporta larguras de alinhamento e modificadores de qualquer (por exemplo %-20s %5.2f)
MGF
Eu tive que rolar a página para ver esta resposta útil.
Donato
53

Estou surpreso que ninguém tenha usado reduce, esta é uma função JavaScript concisa e poderosa nativa.

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"

Como funciona:

reduzir aplica uma função contra um acumulador e cada elemento da matriz (da esquerda para a direita) para reduzi-la a um único 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
fonte
4
Aqui está uma versão que usa essa abordagem para criar uma printffunção simplificada : jsfiddle.net/11szrbx9
Dem Pilafian
1
E aqui está outro usando o ES6, em uma linha:(...a) => {return a.reduce((p: string, c: any) => p.replace(/%s/, c));
dtasev 22/02
Não há necessidade de String.prototype.formatnos ES6: ((a,b,c)=>`${a}, ${b} and ${c}`)(...['me', 'myself', 'I'])(note que este é um pouco redundante para melhor ajuste no seu exemplo)
Tino
Você precisaria implementar funções de substituição para cada um dos printfespecificadores de tipo e incluir lógica para prefixos de preenchimento. Iterar sobre a sequência de formatação de maneira sensata parece ser o menor desafio aqui, imho. Solução elegante, se você só precisa de substituições de cordas.
Collapsar
51

Aqui está uma implementação mínima do sprintf no JavaScript: ele faz apenas "% s" e "% d", mas deixei espaço para que ele fosse estendido. É inútil para o OP, mas outras pessoas que se deparam com esse segmento vindo do Google podem se beneficiar dele.

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;
    });
}

Exemplo:

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

Em contraste com soluções semelhantes nas respostas anteriores, esta realiza todas as substituições de uma só vez , portanto, não substituirá partes dos valores substituídos anteriormente.

Luke Madhanga
fonte
31

Programadores JavaScript podem usar String.prototype.sprintf em https://github.com/ildar-shaimordanov/jsxt/blob/master/js/String.js . Abaixo está um exemplo:

var d = new Date();
var dateStr = '%02d:%02d:%02d'.sprintf(
    d.getHours(), 
    d.getMinutes(), 
    d.getSeconds());
jsxt
fonte
@JasonMorgan, compartilhei o link de trabalho no GitHub.Veja a resposta corrigida.
Jsxt #
24

Adicionando à zippoxerresposta, eu uso esta função:

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.

Também tenho uma versão sem protótipo que uso com mais frequência para sua sintaxe semelhante a 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

Atualização do ES 2015

Todo o material interessante do ES 2015 facilita muito isso:

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."

Imaginei que, como essas, como as mais antigas, na verdade não analisam as letras, é melhor usar apenas um único token %%. Isso tem o benefício de ser óbvio e não dificultar o uso de um único %. No entanto, se você precisar, %%por algum motivo, precisará substituí-lo por ele mesmo:

format("I love percentage signs! %%", "%%");
// "I love percentage signs! %%"
Braden Best
fonte
3
essa resposta foi ótima para colar uma cópia rápida em uma função existente. Não requer nenhum download etc.
Nick
@ Nick sim, essa é a idéia :)
Braden Melhor
21

+1 Zippo, com a exceção de que o corpo da função precisa ser o seguinte ou anexa a cadeia atual a cada iteração:

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};
user437231
fonte
1
Não funcionou no Firefox. O depurador mostra arg como indefinido.
xiao啸
Não substitui o segundo caractere que 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); o resultado se torna The ASP is dead. Don't code {0}. Code PHP that is open source!. Mais uma coisa for(arg in arguments)não funciona no IE. Eu substituí por for (arg = 0; arg <arguments.length; arg++)
samarjit samanta
2
Para referência, for...innão funcionará em todos os navegadores, como este código espera. Ele percorrerá todas as propriedades enumeráveis, que em alguns navegadores incluirão arguments.lengthe em outros nem sequer incluirão os próprios argumentos. De qualquer forma, se Object.prototypefor adicionado a, quaisquer acréscimos provavelmente serão incluídos no grupo. O código deve estar usando um forloop padrão , e não for...in.
cHao 6/02/11
Você deve propor uma edição de resposta em vez de uma resposta duplicada. Isso duplicou esta resposta
RousseauAlexandre
19

Quero compartilhar minha solução para o 'problema'. Não reinventei a roda, mas tente encontrar uma solução com base no que o JavaScript já faz. A vantagem é que você recebe todas as conversões implícitas gratuitamente. Definir a propriedade prototype $ de String fornece uma sintaxe muito agradável e compacta (veja exemplos abaixo). Talvez não seja a maneira mais eficiente, mas na maioria dos casos, lidando com a saída, ela não precisa ser super otimizada.

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));
}

Aqui estão alguns exemplos:

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
fonte
infelizmente, pelo menos # e + não são implementados para carros alegóricos. aqui é uma referência para a função em c: tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Daniel
14

Eu uso uma pequena biblioteca chamada String.format para JavaScript, que suporta a maioria dos recursos de seqüência de caracteres de formato (incluindo formato de números e datas) e usa a sintaxe do .NET. O script em si é menor que 4 kB, portanto, não cria muita sobrecarga.

Sven N
fonte
Dei uma olhada nessa biblioteca e ela parece realmente ótima. Fiquei chateado quando vi que o download era um EXE. Mas afinal o que é que se passa? Não baixou.
Jessegavin #
Muitas vezes, um arquivo para download que é um EXE nada mais é do que um "ZIP com extração automática". Execute-o e ele será descompactado. Isso é bastante conveniente, mas, porque se parece muito com malware, o formato não é mais usado na Web com tanta frequência.
Chuck Kollars
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada.
starmole
@starmole o link é para uma biblioteca javascript (minificada) de 4 kB . Não acredito que colá-lo na resposta seja uma boa ideia.
ivarni
Você está colando corretamente, não seria melhor. Acabei de receber este comentário para revisão aleatória - e comentei antes de não gostar. Para mim, o stackoverflow é melhor ao fornecer explicações, em vez de soluções prontas (qual é o link). Também não quero incentivar as pessoas a postar ou baixar o código da caixa preta.
starmole
14

Muito 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}")

O crédito vai para (link quebrado) https://gist.github.com/0i0/1519811

lior hakim
fonte
Este é o único que lida com colchetes de escape {{0}}, bem como coisas assim {0}{1}.format("{1}", "{0}"). Deve estar no topo!
Juan
11

Se você deseja manipular o separador de milhares, realmente deve usar toLocaleString () do número JavaScript classe pois ele formatará a string da região do usuário.

A classe JavaScript Date pode formatar datas e horas localizadas.

17 de 26
fonte
1
É na verdade um conjunto pelo usuário como uma configuração na aplicação (não a máquina a sua por diante), mas eu vou dar uma olhada, graças
Chris S
adicione alguns exemplos para que todos possam entender rapidamente.
Bhushan Kawadkar
9

Eu uso este:

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

    return newStr;
}

Então eu chamo:

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

Eu tenho uma solução muito próxima da de Peter, mas lida com número e caso de objeto.

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);
    });
  };
}

Talvez possa ser ainda melhor lidar com todos os casos de aprofundamento, mas, para as minhas necessidades, está tudo bem.

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

PS: Essa função é muito legal se você estiver usando traduções em estruturas de modelos como o AngularJS :

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

Onde o en.json é algo como

{
    "hello-message": "Hello {name}, welcome.",
    "hello-by-name": "Hello {0}, welcome."
}
Thiago Mata
fonte
a parte [^}] no regexp não é necessária. Em vez disso, use {(. *?)} ou melhor {([\ s \ S] *?)} para corresponder à nova linha.
rawiro
7

Uma versão muito ligeiramente diferente, a que eu prefiro (essa usa tokens {xxx} em vez de {0} argumentos numerados, isso é muito mais documentado e se adapta muito melhor à localização):

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;
};

Uma variação seria:

  var formatted = l(this);

que chama uma função de localização l () primeiro.

Pedro
fonte
6

Para quem gosta do Node.JS e de seu util.formatrecurso, acabei de extraí-lo para o seu formato vanilla JavaScript (com apenas funções que o util.format usa):

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;
};

Colhido em: https://github.com/joyent/node/blob/master/lib/util.js

AT
fonte
6

Para formatação básica:

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

Eu tenho um formatador um pouco mais longo para JavaScript aqui ...

Você pode formatar várias maneiras:

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

Além disso, se você disser um ObjectBase.prototype.format (como no DateJS ), ele será usado.

Exemplos...

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})"

Eu também fiz um alias com .asFormat e tenho alguma detecção em vigor, caso já exista um string.format (como no MS Ajax Toolkit (eu odeio essa biblioteca).

Tracker1
fonte
5

Caso alguém precise de uma função para evitar poluir o escopo global, aqui está a função que faz o mesmo:

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

Podemos usar uma biblioteca de operações de string String.Format simples e leve para Typescript.

String.Format ():

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

Formato da string 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 string para objetos, incluindo 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
fonte
2

Eu não vi a 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
fonte
2

Para uso com as funções de sucesso jQuery.ajax (). Passe apenas um único argumento e sequência de caracteres substituídos pelas propriedades desse 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;
};

Exemplo:

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