JavaScript odpowiednik printf / String.Format

1970

Szukam dobrego odpowiednika JavaScript dla C / PHP printf()lub dla programistów C # / Java String.Format()( IFormatProviderdla .NET).

Moje podstawowe wymaganie to na razie format separatora tysięcy dla liczb, ale dobrze byłoby coś, co obsługuje wiele kombinacji (łącznie z datami).

Zdaję sobie sprawę, że biblioteka Ajax Microsoftu zawiera wersję String.Format(), ale nie chcemy całego narzutu tego frameworka.

Chris S.
źródło
2
Oprócz wszystkich świetnych odpowiedzi poniżej, możesz rzucić okiem na to: stackoverflow.com/a/2648463/1712065, który IMO jest najbardziej wydajnym rozwiązaniem tego problemu.
Annie
1
Napisałem tani, który używa składni printf podobnej do C.
Braden Best
var search = [$ scope.dog, „1”]; var url = vsprintf (" earth / Services / dogSearch.svc / FindMe /% s /% s ", szukaj); *** W przypadku węzła moduł można uzyskać, wykonując polecenie „npm install sprintf-js”
Jenna Leaf,
Napisałem również prostą funkcję, aby to osiągnąć; stackoverflow.com/a/54345052/5927126
AnandShanbhag

Odpowiedzi:

1109

Od wersji ES6 możesz używać ciągów szablonów:

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

Zobacz szczegóły odpowiedzi Kim poniżej.


Inaczej:

Spróbuj sprintf () dla JavaScript .


Jeśli naprawdę chcesz samodzielnie wykonać prostą metodę formatowania, nie rób zamienników kolejno, ale rób to jednocześnie.

Ponieważ większość innych wymienionych propozycji kończy się niepowodzeniem, gdy ciąg zastępujący poprzedniej zamiany zawiera również sekwencję formatowania taką jak ta:

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

Normalnie można oczekiwać, że wynik będzie, {1}{0}ale rzeczywisty wynik to {1}{1}. Więc rób jednocześnie zamianę, jak w sugestii strachu .

Gumbo
źródło
16
Jeśli pożądana jest tylko prosta konwersja liczb na ciąg, num.toFixed()metoda może wystarczyć!
heltonbiker,
@MaksymilianMajer, który wydaje się być czymś zupełnie innym.
Evan Carroll
@EvanCarroll masz rację. W momencie pisania komentarza repozytorium sprintf() for JavaScriptnie było dostępne. underscore.stringma więcej funkcji oprócz sprintf, który jest oparty na sprintf() for JavaScriptimplementacji. Poza tym biblioteka to zupełnie inny projekt.
Maksymilian Majer
@MaksymilianMajer racja, po prostu mówię, że ta odpowiedź jest martwa, a link się rozpadł. Musi być całkowicie oczyszczony.
Evan Carroll
2
Nie należy już przyjmować odpowiedzi. Począwszy od ES6 jest on wbudowany w język javascript (zarówno w przeglądarkach, jak i NodeJS). Zobacz odpowiedź @Kim poniżej.
Ryan Shillington
1390

Opierając się na wcześniej sugerowanych rozwiązaniach:

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

wyjścia

ASP nie żyje, ale ASP.NET żyje! ASP {2}


Jeśli wolisz nie modyfikować Stringprototypu:

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

Daje ci znacznie bardziej znane:

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

z tym samym wynikiem:

ASP nie żyje, ale ASP.NET żyje! ASP {2}

strach
źródło
12
|| trick nie działa, jeśli argument [liczba] wynosi 0. Powinieneś zrobić jawne if (), aby sprawdzić, czy (args [liczba] === undefined).
fserb
4
w zdaniu else skróconego słowa, jeśli po prostu „dopasuj” zamiast „” {„+ liczba +”} ”. dopasowanie powinno być równe temu ciągowi.
mikeycgto,
4
Jeśli masz wiele łańcuchów dołączonych do siebie (za pomocą +-operatora), pamiętaj o umieszczeniu całego łańcucha w nawiasach: ("asd {0}"+"fas {1}").format("first", "second");W przeciwnym razie funkcja zostanie zastosowana tylko do ostatniego łańcucha, który został dołączony.
Lukas Knuth
3
To nieznacznie i subtelnie zmienia wynik. Wyobraź sobie 'foo {0}'.format(fnWithNoReturnValue()). Obecnie wróci foo {0}. Po wprowadzeniu zmian wróci foo undefined.
strach przed
2
@avenmore: / \ {(\ d +) \} / g
Hozuki
490

To zabawne, ponieważ Stack Overflow faktycznie ma własną funkcję formatowania Stringzwanego prototypem formatUnicorn. Spróbuj! Wejdź do konsoli i wpisz coś takiego:

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

Firebug

Otrzymasz ten wynik:

Hello, Gabriel, are you feeling OK?

Jako argumentów możesz używać obiektów, tablic i ciągów! Mam jego kod i przerobiłem go, aby utworzyć nową wersję 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;
};

Zwróć uwagę na sprytne Array.prototype.slice.call(arguments)wywołanie - oznacza to, że jeśli wrzucisz argumenty, które są ciągami lub liczbami, a nie pojedynczym obiektem w stylu JSON, String.Formatzachowanie C # jest prawie dokładne.

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

To dlatego, że Arraynic nie slicezmusi wszystko co w argumentsw produkt Array, czy to pierwotnie, czy nie, i keybędzie indeks (0, 1, 2 ...) każdego elementu tablicy zmuszane do łańcucha (na przykład «0», a więc "\\{0\\}"dla pierwszego wzorca wyrażenia regularnego).

Schludny.

Gabriel Nahmias
źródło
401
Fajnie jest odpowiedzieć na pytanie o stackoverflow z kodem z stackoverflow, +1
Sneakyness
5
@JamesManning Wyrażenie regularne umożliwia globalną flagę ( g), która może zastąpić ten sam klucz więcej niż jeden raz. W powyższym przykładzie możesz użyć {name}wiele razy w tym samym zdaniu i wszystkie je zastąpić.
KrekkieD
3
Szczerze mówiąc, wydaje się to okropnie kruche. Co się dzieje na przykład, jeśli namejest "blah {adjective} blah"?
sam hocevar,
5
@ruffin „trochę hiperboliczny”? Kod oszukany w interpretowaniu danych użytkownika jako ciągów formatujących to cała kategoria luk w zabezpieczeniach . 98,44% jest przeciętne .
sam hocevar
3
@samhocevar Nie mogę ci uwierzyć Mały Bobby przedstawił mnie. ;) Jeśli uruchamiasz tekst przetwarzany przez skrypt JavaScript po stronie klienta na serwerze bazy danych bez żadnych kontroli bezpieczeństwa, nieba, pomóż nam wszystkim. ; ^) Spójrz, żaden użytkownik nie powinien wysyłać niczego od klienta (np. Postman), który przekroczyłby bezpieczeństwo twojego serwera. I powinny zakładać niczego niebezpiecznego, które mogą być wysyłane z klientem będzie być. Oznacza to, że jeśli potrzebujesz 100% bezpieczeństwa od kodu JavaScript po stronie klienta, który jest zawsze edytowalny przez użytkownika, i uważasz, że ta funkcja może otworzyć ryzyko bezpieczeństwa, grasz w niewłaściwą grę.
ruffin
325

Formatowanie liczb w JavaScript

Doszedłem do tej strony pytań, mając nadzieję na znalezienie sposobu formatowania liczb w JavaScript, bez wprowadzania kolejnej biblioteki. Oto, co znalazłem:

Zaokrąglanie liczb zmiennoprzecinkowych

Wydaje się, że odpowiednikiem sprintf("%.2f", num)w JavaScript jest num.toFixed(2)formatowanie numdo 2 miejsc po przecinku z zaokrąglaniem (ale patrz komentarz @ ars265 na temat Math.roundponiżej).

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

Forma wykładnicza

Odpowiednikiem sprintf("%.2e", num)jest num.toExponential(2).

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

Podstawy szesnastkowe i inne

Aby wydrukować liczby w bazie B, spróbuj num.toString(B). JavaScript obsługuje automatyczną konwersję do i od zasad od 2 do 36 (ponadto niektóre przeglądarki mają ograniczoną obsługę kodowania base64 ).

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

Strony referencyjne

Szybki samouczek na temat formatowania liczb JS

Strona referencyjna Mozilla dla toFixed () (z linkami do toPrecision (), toExponential (), toLocaleString (), ...)

rescdsk
źródło
23
Czy nie lepiej byłoby zawrzeć literał liczbowy w nawiasie, zamiast pozostawić tam dziwną białą przestrzeń?
rmobis
7
To prawdopodobnie wyglądałoby lepiej, prawda. Ale moim celem jest tylko wskazanie pułapki błędu składniowego.
rescdsk,
4
Tylko na marginesie, jeśli używasz starszej przeglądarki lub obsługujesz starsze przeglądarki, niektóre przeglądarki nieprawidłowo zaimplementowane doFixed, użycie Math.round zamiast toFixed jest lepszym rozwiązaniem.
ars265,
7
@Raphael_ i @rescdsk: ..działa również:33333..toExponential(2);
Peter Jaric
Lub (33333) .toExponential (2)
Jonathan
245

Od wersji ES6 możesz używać ciągów szablonów :

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

Należy pamiętać, że ciągi szablonów są otoczone znakami wstecznymi `zamiast (pojedynczych) cudzysłowów.

W celu uzyskania dalszych informacji:

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

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

Uwaga: Sprawdź stronę mozilla, aby znaleźć listę obsługiwanych przeglądarek.

Kim
źródło
61
Problem z ciągami szablonów polega na tym, że wydaje się, że są one wykonywane natychmiast, przez co ich użycie jako, powiedzmy, tabeli ciągów podobnej do i18n jest całkowicie bezwartościowe. Nie mogę wcześnie zdefiniować łańcucha i podać parametrów, które będą używane później i / lub wielokrotnie.
Tustin2121,
4
@ Tustin2121 Masz rację, że nie są one zbudowane, aby przypisać je do zmiennej, co jest nieco zawrotne, ale łatwo jest pracować z tendencjami ciągów szablonów natychmiastowego wykonania, jeśli ukryjesz je w funkcji. Zobacz jsfiddle.net/zvcm70pa
inanutshellus
13
@ Tustin2121 nie ma różnicy między używaniem łańcucha szablonu lub łączeniem łańcucha starego stylu, czyli cukru dla tego samego. Będziesz musiał owinąć generator ciągów starego stylu w prostą funkcję i to samo działa dobrze z szablonami ciągów. const compile = (x, y) => `I can call this template string whenever I want.. x=${x}, y=${y}`...compile(30, 20)
cchamberlain
4
to rozwiązanie nie będzie działać dla łańcucha formatu przekazanego w zmiennej (na przykład z serwera)
użytkownik993954,
1
@inanutshellus Działa to dobrze, jeśli funkcja szablonu jest zdefiniowana na tym samym komputerze, na którym jest wykonywana. O ile mi wiadomo, nie można przekazać funkcji jako JSON, więc przechowywanie funkcji szablonów w bazie danych nie działa dobrze.
styfle
171

jsxt, Zippo

Ta opcja pasuje lepiej.

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

Dzięki tej opcji mogę zastąpić ciągi takie jak te:

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

Z twoim kodem drugi {0} nie zostałby zastąpiony. ;)

Filipiz
źródło
3
gist.github.com/1049426 Zaktualizowałem twój przykład tym podejściem. Liczne korzyści, w tym zapisywanie natywnej implementacji, jeśli istnieje, tworzenie łańcuchów itp. Próbowałem usunąć wyrażenia regularne, ale przydał mi się rodzaj globalnej zamiany. : - /
tbranyen
6
jsxt jest niestety licencjonowany na licencji GPL
AndiDog,
109

Używam tej prostej funkcji:

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

Jest to bardzo podobne do string.format:

"{0} is dead, but {1} is alive!".format("ASP", "ASP.NET")
Zippoxer
źródło
1
dlaczego +=? powinienemformatted = this.replace("{" + arg + "}", arguments[arg]);
guilin 桂林
2
Myślę, że kod nadal nie jest poprawny. Prawidłowy powinien być jak opublikowany Filipiz .
wenqiang 11.01.11
3
Dla porównania, for...innie będzie działać w każdej przeglądarce, ponieważ oczekuje tego kod. Pętla obejmie wszystkie wyliczalne właściwości, które w niektórych przeglądarkach będą zawierać arguments.length, a w innych nawet nie uwzględni samych argumentów. W każdym razie, jeśli Object.prototypezostanie dodany do, wszelkie dodatki prawdopodobnie zostaną zawarte w grupie. Kod powinien używać standardowej forpętli zamiast for...in.
cHao
3
Nie powiedzie się to, jeśli poprzedni zamiennik zawiera również ciąg formatu:"{0} is dead, but {1} is alive!".format("{1}", "ASP.NET") === "ASP.NET is dead, but ASP.NET is alive!"
Gumbo
6
Zmienna argma charakter globalny. Musisz to zrobić zamiast tego:for (var arg in arguments) {
Pauan,
68

Dla użytkowników Node.js istnieje util.formatfunkcja podobna do printf:

util.format("%s world", "Hello")
George Eracleous
źródło
1
To nie obsługuje% x od Node v0.10.26
Max Krohn
Nie obsługuje także modyfikatorów szerokości i wyrównania (np. %-20s %5.2f)
FGM
Musiałem przewinąć całą stronę w dół, aby zobaczyć tę przydatną odpowiedź.
Donato
53

Dziwię się, że nikt tego nie używał reduce, jest to natywna zwięzła i potężna funkcja 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"

Jak to działa:

redukcja stosuje funkcję względem akumulatora i każdego elementu w tablicy (od lewej do prawej), aby zredukować go do pojedynczej wartości.

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
źródło
4
Oto wersja, która wykorzystuje to podejście do tworzenia uproszczonej printffunkcji: jsfiddle.net/11szrbx9
Dem Pilafian
1
A oto kolejny przy użyciu ES6, w jednej linii:(...a) => {return a.reduce((p: string, c: any) => p.replace(/%s/, c));
dtasev
Nie ma takiej potrzeby String.prototype.formatw ES6: ((a,b,c)=>`${a}, ${b} and ${c}`)(...['me', 'myself', 'I'])(zauważ, że jest to trochę zbędne, aby lepiej pasować do twojego przykładu)
Tino
Będziesz musiał zaimplementować funkcje zastępcze dla każdego ze printfspecyfikatorów typów i dołączyć logikę do przedrostków wypełniania. Wydaje się, że iteracja po łańcuchu formatu w rozsądny sposób jest niewielkim wyzwaniem, imho. Świetne rozwiązanie, jeśli potrzebujesz tylko zamiany łańcucha.
Collapsar
51

Oto minimalna implementacja sprintf w JavaScript: robi to tylko „% s” i „% d”, ale zostawiłem miejsce na rozszerzenie. Jest to bezużyteczne dla OP, ale inne osoby, które natkną się na ten wątek pochodzący od Google, mogą z niego skorzystać.

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

Przykład:

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

W przeciwieństwie do podobnych rozwiązań w poprzednich odpowiedziach, ta dokonuje wszystkich zamian za jednym razem , więc nie zastąpi części wcześniej zastąpionych wartości.

Luke Madhanga
źródło
24

Dodając do zippoxerodpowiedzi, używam tej funkcji:

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.

Mam również nieprototypową wersję, której używam częściej ze względu na jej składnię podobną do języka 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

Aktualizacja ES 2015

Wszystkie nowe, fajne rzeczy w ES 2015 sprawiają, że jest to o wiele łatwiejsze:

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

Uznałem, że ponieważ tak jak starsze, tak naprawdę nie analizuje liter, równie dobrze może użyć tylko jednego tokena %%. Ma to tę zaletę, że jest oczywiste i nie utrudnia korzystania z jednego %. Jeśli jednak %%z jakiegoś powodu potrzebujesz, musisz go zastąpić samym:

format("I love percentage signs! %%", "%%");
// "I love percentage signs! %%"
Braden Best
źródło
3
ta odpowiedź była świetna do szybkiego wklejenia do istniejącej funkcji. Nie wymaga pobierania itp.
Nick
@Nick tak, to jest pomysł :)
Braden Best
21

+1 Zippo z wyjątkiem tego, że treść funkcji musi być taka, jak poniżej, w przeciwnym razie dołącza bieżący ciąg na każdej iteracji:

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};
437231
źródło
1
To nie działało w przeglądarce Firefox. Debugger pokazuje arg jako niezdefiniowany.
xiao 啸
Nie zastępuje on drugiego znaku, 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); którym staje się wynik The ASP is dead. Don't code {0}. Code PHP that is open source!. Jeszcze jedna rzecz for(arg in arguments)nie działa w IE. Zastąpiłem for (arg = 0; arg <arguments.length; arg++)
samarjit samanta
2
Dla porównania, for...innie będzie działać w każdej przeglądarce, ponieważ oczekuje tego kod. Pętla obejmie wszystkie wyliczalne właściwości, które w niektórych przeglądarkach będą zawierać arguments.length, a w innych nawet nie uwzględni samych argumentów. W każdym razie, jeśli Object.prototypezostanie dodany do, wszelkie dodatki prawdopodobnie zostaną zawarte w grupie. Kod powinien używać standardowej forpętli zamiast for...in.
cHao
Powinieneś zaproponować edycję odpowiedzi zamiast duplikatu odpowiedzi. To powiela tę odpowiedź
RousseauAlexandre
19

Chcę podzielić się moim rozwiązaniem problemu. Nie wynalazłem koła na nowo, ale próbuję znaleźć rozwiązanie oparte na tym, co już robi JavaScript. Zaletą jest to, że wszystkie niejawne konwersje są bezpłatne. Ustawienie właściwości prototypowej $ String daje bardzo ładną i zwartą składnię (patrz przykłady poniżej). Być może nie jest to najskuteczniejszy sposób, ale w większości przypadków w przypadku produkcji nie musi ona być superoptymalizowana.

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

Oto kilka przykładów:

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
źródło
niestety co najmniej # i + nie są zaimplementowane dla pływaków. tutaj jest odwołanie do funkcji w c: tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Daniel
14

Korzystam z małej biblioteki o nazwie String.format dla JavaScript, która obsługuje większość możliwości formatowania ciągu znaków (w tym formatu liczb i dat) i wykorzystuje składnię .NET. Sam skrypt jest mniejszy niż 4 kB, więc nie powoduje dużego obciążenia.

Sven N.
źródło
Rzuciłem okiem na tę bibliotekę i wygląda naprawdę świetnie. Byłem wkurzony, gdy zobaczyłem, że pobieranie to plik EXE. O co tu do cholery chodzi? Nie pobrano
jessegavin
Często archiwum do pobrania, które jest EXE, jest niczym więcej niż „samorozpakowującym się ZIP”. Wykonaj go, a sam się rozpakuje. Jest to dość wygodne, ALE ponieważ wygląda tak podobnie jak złośliwe oprogramowanie, format nie jest już tak często używany w sieci.
Chuck Kollars
Chociaż ten link może odpowiedzieć na pytanie, lepiej dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
starmole
@starmole link prowadzi do (zminimalizowanej) biblioteki javascript o wielkości 4 kB . Nie sądzę, żeby wklejenie go w odpowiedź było dobrym pomysłem.
ivarni
Masz rację wklejanie, nie byłoby lepiej. Właśnie dostałem ten komentarz do losowej recenzji - i skomentowałem, zanim go nie polubiłem. Dla mnie stackoverflow jest lepszy, gdy podaje się wyjaśnienia niż gotowe rozwiązania (którym jest link). Nie chcę również zachęcać ludzi do publikowania lub pobierania kodu czarnej skrzynki.
starmole
14

Bardzo elegancko:

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

Kredyt trafia do (uszkodzony link) https://gist.github.com/0i0/1519811

lior hakim
źródło
To jedyny, który obsługuje nawiasy klamrowe, {{0}}a także takie rzeczy {0}{1}.format("{1}", "{0}"). Powinien być na samym szczycie!
Juan
11

Jeśli chcesz obsłużyć separator tysięcy, naprawdę powinieneś użyć metody toLocaleString () z klasy JavaScript Number, ponieważ sformatuje ona ciąg dla regionu użytkownika.

Klasa daty JavaScript może formatować zlokalizowane daty i godziny.

17 z 26
źródło
1
Właściwie jest to ustawione przez użytkownika jako ustawienie w aplikacji (nie maszyna jest włączona), ale przyjrzę się, dzięki
Chris S
dodaj kilka przykładów, aby każdy mógł to szybko zrozumieć.
Bhushan Kawadkar
9

Projekt PHPJS napisał implementacje JavaScript dla wielu funkcji PHP. Ponieważ sprintf()funkcja PHP jest zasadniczo taka sama jak w języku C printf(), ich implementacja JavaScript powinna zaspokoić twoje potrzeby.

Spudley
źródło
9

Używam tego:

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

    return newStr;
}

Następnie nazywam to:

"<h1>%s</h1><p>%s</p>".format("Header", "Just a test!");
Steven Penny
źródło
9

Mam rozwiązanie bardzo zbliżone do rozwiązania Petera, ale dotyczy ono liczby i przypadku.

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

Może lepiej byłoby zajmować się wszystkimi najgłębszymi sprawami, ale dla moich potrzeb jest to w porządku.

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

PS: Ta funkcja jest bardzo fajna, jeśli używasz tłumaczeń w ramach szablonów, takich jak AngularJS :

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

Gdzie jest en.json

{
    "hello-message": "Hello {name}, welcome.",
    "hello-by-name": "Hello {0}, welcome."
}
Thiago Mata
źródło
część [^}] w wyrażeniu regularnym jest niepotrzebna. Zamiast tego użyj {(. *?)} lub lepiej {([\ s \ S] *?)}, aby dopasować także nowy wiersz.
rawiro
7

Jedna nieco inna wersja, ta, którą preferuję (ta używa tokenów {xxx} zamiast numerowanych argumentów {0}, jest to o wiele bardziej samodokumentujące i lepiej pasuje do lokalizacji):

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

Wariant byłby:

  var formatted = l(this);

która najpierw wywołuje funkcję lokalizacji l ().

Piotr
źródło
6

Dla tych, którzy lubią Node.JS i jego util.formatfunkcję, właśnie wyodrębniłem go do waniliowej formy JavaScript (tylko z funkcjami, które wykorzystuje use.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;
};

Zebrane z: https://github.com/joyent/node/blob/master/lib/util.js

W
źródło
6

Do podstawowego formatowania:

var template = jQuery.validator.format("{0} is not a valid value");
var result = template("abc");
Jewgienij Gerbut
źródło
5

Mam nieco dłuższy formatowania dla JavaScriptu tutaj ...

Możesz przeprowadzić formatowanie na kilka sposobów:

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

Ponadto, jeśli powiesz ObjectBase.prototype.format (na przykład z DateJS ), użyje go.

Przykłady ...

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

Użyłem również aliasu z .asFormat i mam pewne wykrycie na wypadek, gdyby już istniał łańcuch. Format (np. Z MS Ajax Toolkit (nienawidzę tej biblioteki).

Tracker1
źródło
5

Na wypadek, gdyby ktoś potrzebował funkcji zapobiegającej zanieczyszczeniu zasięgu globalnego, oto funkcja, która robi to samo:

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

Możemy użyć prostej, lekkiej biblioteki operacji na łańcuchach znaków String.Format dla maszynopisu.

String.Format ():

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

Format ciągu dla specyfikatorów:

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"

Format ciągu dla obiektów, w tym specyfikatory:

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
źródło
2

Nie widziałem String.formatwariantu:

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
źródło
2

Do użytku z funkcjami powodzenia jQuery.ajax (). Przekaż tylko jeden argument i ciąg znaków zamień na właściwości tego obiektu jako {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;
};

Przykład:

var userInfo = ("Email: {Email} - Phone: {Phone}").format({ Email: "[email protected]", Phone: "123-123-1234" });
Raymond Powell
źródło