JavaScript эквивалентен printf / String.Format

1973

Я ищу хороший JavaScript-эквивалент C / PHP printf()или программистов на C # / Java String.Format()( IFormatProviderдля .NET).

Моим основным требованием является формат разделителя тысяч для чисел на данный момент, но что-то, что обрабатывает множество комбинаций (включая даты), было бы хорошо.

Я понимаю, что библиотека Ajax от Microsoft предоставляет версию String.Format(), но нам не нужны все издержки этой инфраструктуры.

Chris S
источник
2
Помимо всех замечательных ответов ниже, вы можете взглянуть на этот: stackoverflow.com/a/2648463/1712065, который, по мнению IMO, является наиболее эффективным решением этой проблемы.
Анни
1
Я написал дешевый, который использует C-подобный синтаксис printf.
Брэден Бест
var search = [$ scope.dog, "1"]; var url = vsprintf (" earth / Services / dogSearch.svc / FindMe /% s /% s ", поиск); *** Для узла вы можете получить свой модуль с помощью «npm install sprintf-js»
Jenna Leaf
Я также написал простую функцию для достижения этой цели; stackoverflow.com/a/54345052/5927126
AnandShanbhag

Ответы:

1112

Начиная с ES6 вы можете использовать шаблоны строк:

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

Смотрите ответ Ким ниже для деталей.


В противном случае:

Попробуйте sprintf () для JavaScript .


Если вы действительно хотите сделать простой метод форматирования самостоятельно, не делайте замены последовательно, а выполняйте их одновременно.

Поскольку большинство других упомянутых предложений терпят неудачу, когда строка замены предыдущей замены также содержит последовательность формата, подобную этой:

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

Обычно вы ожидаете, что результат будет, {1}{0}но фактический результат есть {1}{1}. Так что сделайте одновременно замену, как в предложении страха .

Gumbo
источник
16
Если требуется только простое преобразование числа в строку, num.toFixed()метода может быть достаточно!
Хелтонбайкер
@MaksymilianMajer, который кажется совершенно другим.
Эван Кэрролл
@EvanCarroll ты прав. На момент написания комментария хранилище sprintf() for JavaScriptне было доступно. underscore.stringимеет больше возможностей, кроме sprintf, который основан на sprintf() for JavaScriptреализации. Кроме того, библиотека - это совершенно другой проект.
Максимилиан Мажер
@MaksymilianMajer правильно, просто сказать, что этот ответ мертв, и ссылка устарела. Это должно быть полностью очищено.
Эван Кэрролл
2
Это не должно быть принято ответа больше. Начиная с ES6 это встроено в язык javascript (как в браузерах, так и в NodeJS). Смотрите ответ @Kim ниже.
Райан Шиллингтон
1391

Опираясь на ранее предложенные решения:

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

выходы

ASP мертв, но ASP.NET жив! ASP {2}


Если вы предпочитаете не изменять Stringпрототип:

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

Дает вам гораздо более знакомые:

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

с тем же результатом:

ASP мертв, но ASP.NET жив! ASP {2}

fearphage
источник
12
|| трюк не работает, если args [число] равно 0. Должен сделать явный if (), чтобы увидеть, если (args [число] === undefined).
fserb
4
в условном выражении else сокращения, если, почему бы просто не сделать "match" вместо "'{' + number + '}'". совпадение должно равняться этой строке.
mikeycgto
4
Если у вас есть несколько строк, добавленных друг к другу (с помощью +оператора -o), обязательно поставьте полную строку в скобки: в ("asd {0}"+"fas {1}").format("first", "second");противном случае функция будет применена только к последней добавленной строке.
Лукас Кнут
3
Это немного и тонко меняет результат. Представь 'foo {0}'.format(fnWithNoReturnValue()). В настоящее время он вернется foo {0}. С вашими изменениями он вернется foo undefined.
Fearphage
2
@avenmore: / \ {(\ d +) \} / g
Хозуки
491

Забавно, потому что Stack Overflow на самом деле имеет свою собственную функцию форматирования для String прототипа formatUnicorn. Попробуй это! Зайдите в консоль и наберите что-то вроде:

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

поджигатель

Вы получаете этот вывод:

Hello, Gabriel, are you feeling OK?

Вы можете использовать объекты, массивы и строки в качестве аргументов! Я получил его код и переработал его для создания новой версии 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;
};

Обратите внимание на умных Array.prototype.slice.call(arguments) вызов - это означает, что если вы добавите аргументы, которые являются строками или числами, а не одним объектом в стиле JSON, вы String.Formatпочти точно получите поведение C # .

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

Это потому , что Array«сslice заставит все , это в argumentsв Array, был ли он изначально или нет, и keyбудет индекс (0, 1, 2 ...) каждый элемент массива принуждается в строку (например,„0“, так "\\{0\\}"для вашего первого шаблона регулярных выражений).

Ухоженная.

Gabriel Nahmias
источник
402
Довольно круто ответить на вопрос о stackoverflow с помощью кода из stackoverflow, +1
Sneakyness
5
@JamesManning Регулярное выражение разрешает глобальный флаг ( g), который может заменить один и тот же ключ более одного раза. В приведенном выше примере вы можете использовать {name}несколько раз в одном предложении и заменить их все.
KrekkieD
3
Это кажется ужасно хрупким, если честно. Что происходит, например, если nameесть "blah {adjective} blah"?
Сэм Хоцевар
5
@ruffin «немного гиперболический»? Код, который обманывает интерпретацию пользовательских данных как строк форматирования, представляет собой целую категорию уязвимостей . 98,44% выше среднего .
Сам Hocevar
3
@ samhocevar Я не могу поверить тебе, Маленький Бобби. ;) Если вы запускаете текст, обработанный клиентским JavaScript на вашем сервере баз данных, без каких-либо проверок безопасности, небеса помогают нам всем. ; ^) Послушайте, не должно быть ничего, что любой пользователь может отправить с клиента (например, Почтальон), который проходит через безопасность вашего сервера. И вы должны предположить, что все опасное, что может быть отправлено клиентом, будет . То есть, если вам требуется 100% безопасность клиентского JavaScript-кода, который всегда доступен для редактирования пользователем, и вы думаете, что эта функция может создать угрозу безопасности, вы играете не в ту игру.
Ерф
325

Форматирование чисел в JavaScript

Я попал на страницу с вопросом в надежде найти способ форматирования чисел в JavaScript, не представляя еще одну библиотеку. Вот что я нашел:

Округление чисел с плавающей точкой

sprintf("%.2f", num)Кажется num.toFixed(2), что эквивалент в JavaScript , который форматирует numдо 2 десятичных разрядов, с округлением (но см. Комментарий @ ars265 Math.roundниже).

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

Экспоненциальная форма

Эквивалентом sprintf("%.2e", num)является num.toExponential(2).

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

Шестнадцатеричные и другие базы

Чтобы напечатать числа в базе B, попробуйте num.toString(B). JavaScript поддерживает автоматическое преобразование в и из баз со 2 по 36 (кроме того, некоторые браузеры имеют ограниченную поддержку кодировки base64 ).

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

Справочные страницы

Краткое руководство по форматированию номеров JS

Справочная страница Mozilla для toFixed () (со ссылками на toPrecision (), toExponential (), toLocaleString (), ...)

респдск
источник
23
Не лучше ли заключить числовой литерал в круглые скобки вместо того, чтобы оставить там странный пробел?
rmobis
7
Это, вероятно, выглядело бы лучше, правда. Но моя цель - просто указать на ловушку синтаксической ошибки.
Rescdsk
4
Просто примечание: если вы используете более старый браузер или поддерживает более старые браузеры, некоторые браузеры, реализованные с ошибкой, исправлены, и вместо этого лучше использовать Math.round вместо toFixed.
ars265
7
@Raphael_ и @rescdsk: ..также работает:33333..toExponential(2);
Питер Ярик
Или (33333). Экспо (2)
Джонатан
245

Начиная с ES6 вы можете использовать шаблоны строк :

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

Имейте в виду, что строки шаблона заключаются в обратные кавычки `вместо (одинарных) кавычек.

Для дополнительной информации:

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

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

Примечание. Проверьте сайт mozilla, чтобы найти список поддерживаемых браузеров.

Ким
источник
61
Проблема с шаблонными строками заключается в том, что они, кажется, выполняются немедленно, что делает их использование, скажем, i18n-подобной таблицы строк совершенно бесполезной. Я не могу определить строку на раннем этапе и предоставить параметры для использования позже и / или повторно.
Tustin2121
4
@ Tustin2121 Вы правы в том, что они не созданы для присваивания переменной, что немного поразительно, но достаточно просто работать с тенденциями мгновенного выполнения шаблонных строк, если вы прячете их в функцию. См. Jsfiddle.net/zvcm70pa
inanutshellus
13
@ Tustin2121 нет никакой разницы между использованием строки шаблона или конкатенации строк старого стиля, ее сахаром для одной и той же вещи. Вы должны были бы обернуть генератор строк старого стиля в простую функцию, и то же самое прекрасно работает с шаблонами строк. const compile = (x, y) => `I can call this template string whenever I want.. x=${x}, y=${y}`...compile(30, 20)
cchamberlain
4
это решение не будет работать для строки формата, передаваемой в переменной (например, с сервера)
user993954
1
@inanutshellus Это прекрасно работает, если ваша шаблонная функция определена на том же компьютере, где она выполняется. Насколько я знаю, вы не можете передать функцию как JSON, поэтому хранение шаблонных функций в базе данных не работает должным образом.
Styfle
171

JSXT, Zippo

Этот вариант подходит лучше.

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

С помощью этой опции я могу заменить строки следующим образом:

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

С вашим кодом вторая {0} не будет заменена. ;)

Filipiz
источник
3
gist.github.com/1049426 Я обновил ваш пример с этим подходом. Многочисленные преимущества, в том числе сохранение собственной реализации, если она существует, строковое форматирование и т. Д. Я пытался удалить регулярные выражения, но все же это было необходимо для глобальной замены. : - /
tbranyen
6
К сожалению,
jsxt
109

Я использую эту простую функцию:

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

Это очень похоже на string.format:

"{0} is dead, but {1} is alive!".format("ASP", "ASP.NET")
Zippoxer
источник
1
почему +=? Должно ли этоformatted = this.replace("{" + arg + "}", arguments[arg]);
Гуйлинь
2
Я думаю, что код все еще не верен. Правильный должен быть похож на Филипиз .
Вэньцзян
3
Для справки, for...inне будет работать в каждом браузере, как этого ожидает код. Он будет перебирать все перечисляемые свойства, которые в некоторых браузерах будут включены arguments.length, а в других вообще не будут включать сами аргументы. В любом случае, если Object.prototypeон добавлен, любые дополнения, вероятно, будут включены в группу. Код должен использовать стандартный forцикл, а не for...in.
cHao
3
Это терпит неудачу, если предыдущая замена также содержит строку формата:"{0} is dead, but {1} is alive!".format("{1}", "ASP.NET") === "ASP.NET is dead, but ASP.NET is alive!"
Gumbo
6
Переменная argявляется глобальной. Вы должны сделать это вместо этого:for (var arg in arguments) {
Pauan
68

Для пользователей Node.js есть util.formatфункция, аналогичная printf:

util.format("%s world", "Hello")
George Eracleous
источник
1
Это не поддерживает% x на Node v0.10.26
Макс Крон,
Также не поддерживает модификаторы ширины и выравнивания (например %-20s %5.2f)
FGM
Мне пришлось прокрутить страницу вниз, чтобы увидеть этот полезный ответ.
Донато
53

Я удивлен, что никто не использовал reduce , это родная краткая и мощная функция 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"

Как это работает:

Reduce применяет функцию к аккумулятору и каждому элементу в массиве (слева направо), чтобы уменьшить его до одного значения.

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
источник
4
Вот версия, которая использует этот подход для создания упрощенной printfфункции: jsfiddle.net/11szrbx9
Дем Пилафян,
1
А вот еще один, использующий ES6, в одной строке: (...a) => {return a.reduce((p: string, c: any) => p.replace(/%s/, c));
dtasev
Нет необходимости String.prototype.formatв ES6: ((a,b,c)=>`${a}, ${b} and ${c}`)(...['me', 'myself', 'I'])(обратите внимание, что это немного избыточно, чтобы лучше соответствовать вашему примеру)
Tino
Вам нужно будет реализовать функции замены для каждого из printfспецификаторов типов и включить логику для префиксов заполнения. Похоже, что перебор строки формата в разумной манере является второстепенной задачей, imho. Оптимальное решение, если вам нужны только замены строк.
коллапсар
51

Вот минимальная реализация sprintf в JavaScript: он выполняет только «% s» и «% d», но я оставил место для его расширения. Это бесполезно для ОП, но другие люди, которые натыкаются на эту ветку из Google, могут извлечь из этого пользу.

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

Пример:

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

В отличие от аналогичных решений в предыдущих ответах, этот выполняет все замены за один раз , поэтому он не заменит части ранее замененных значений.

Люк Мадханга
источник
31

Программисты JavaScript могут использовать String.prototype.sprintf по адресу https://github.com/ildar-shaimordanov/jsxt/blob/master/js/String.js . Ниже приведен пример:

var d = new Date();
var dateStr = '%02d:%02d:%02d'.sprintf(
    d.getHours(), 
    d.getMinutes(), 
    d.getSeconds());
оборота jsxt
источник
@JasonMorgan, я поделился рабочей ссылкой на GitHub. Смотрите исправленный ответ.
jsxt
24

Добавление к zippoxer ответу, я использую эту функцию:

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.

У меня также есть не прототипная версия, которую я использую чаще для ее 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

ES 2015 обновление

Все классные новинки ES 2015 делают это намного проще:

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

Я подумал, что, поскольку это, как и старые, на самом деле не анализирует буквы, оно может просто использовать один токен %%. Преимущество этого состоит в том, что он очевиден и не затрудняет использование одного %. Однако, если вам нужно %%по какой-то причине, вам нужно заменить его на себя:

format("I love percentage signs! %%", "%%");
// "I love percentage signs! %%"
Braden Best
источник
3
Этот ответ отлично подходит для быстрого копирования и вставки в существующую функцию. Не требуется никаких загрузок и т. Д.
Ник
@ Ник да, это идея :)
Брэден Бест
21

+1 Zippo за исключением того, что тело функции должно быть таким, как показано ниже, или иначе оно добавляет текущую строку на каждую итерацию:

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};
user437231
источник
1
Это не сработало на Firefox. Отладчик показывает arg как неопределенный.
Сяо 啸
Это не заменяет второй символ, 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); результатом становится The ASP is dead. Don't code {0}. Code PHP that is open source!. Еще одна вещь for(arg in arguments)не работает в IE. я заменил на for (arg = 0; arg <arguments.length; arg++)
Самарджит Саманта
2
Для справки, for...inне будет работать в каждом браузере, как этого ожидает код. Он будет перебирать все перечисляемые свойства, которые в некоторых браузерах будут включены arguments.length, а в других вообще не будут включать сами аргументы. В любом случае, если Object.prototypeон добавлен, любые дополнения, вероятно, будут включены в группу. Код должен использовать стандартный forцикл, а не for...in.
cHao
Вы должны предложить редактирование ответа вместо дубликата ответа. Это продублировать этот ответ
RousseauAlexandre
19

Я хочу поделиться своим решением для «проблемы». Я не изобрел колесо заново, но пытаюсь найти решение, основанное на том, что JavaScript уже делает. Преимущество в том, что вы получаете все неявные преобразования бесплатно. Установка свойства prototype $ для String дает очень красивый и компактный синтаксис (см. Примеры ниже). Это, возможно, не самый эффективный способ, но в большинстве случаев, когда дело доходит до выхода, его не нужно супероптимизировать.

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

Вот несколько примеров:

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
источник
к сожалению, по крайней мере # и + не реализованы для поплавков. вот ссылка на функцию в c: tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Даниил
15

Я добавлю свои собственные открытия, которые я нашел, так как я спросил:

К сожалению, кажется, что sprintf не обрабатывает тысячи разделителей форматирования, как формат строки .NET.

Chris S
источник
14

Я использую небольшую библиотеку под названием String.format для JavaScript, которая поддерживает большинство возможностей форматирования строк (включая формат чисел и дат) и использует синтаксис .NET. Сам скрипт меньше 4 кБ, поэтому он не создает больших накладных расходов.

Свен Н
источник
Я посмотрел на эту библиотеку, и она выглядит действительно здорово. Я разозлился, когда увидел, что загрузка была EXE. Какого черта это? Не скачивал.
jessegavin
Часто загружаемый архив, который является EXE, является не чем иным, как «самораспаковывающимся ZIP». Выполните его, и он сам распакуется. Это очень удобно, НО, потому что это выглядит как вредоносное ПО, формат больше не используется в Интернете.
Чак Колларс
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными в случае изменения связанной страницы.
starmole
@starmole - ссылка на (минимизированную) библиотеку JavaScript 4 КБ . Я не верю, что вставлять это в ответ - хорошая идея.
ivarni
Вы правильно вставляете это не будет лучше. Я только что получил этот комментарий для случайного просмотра - и прокомментировал, прежде чем его не любить. Для меня stackoverflow лучше при предоставлении объяснений, а не готовых решений (ссылка на которые есть). Я также не хочу поощрять людей размещать или загружать код черного ящика.
Стармол
14

Очень элегантно:

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

Кредит идет в (неработающей ссылке) https://gist.github.com/0i0/1519811

lior hakim
источник
Это единственный, который обрабатывает escape-скобки, {{0}}а также такие вещи, как {0}{1}.format("{1}", "{0}"). Должно быть на самой вершине!
Хуан
11

Если вы хотите обработать разделитель тысяч, вам действительно следует использовать toLocaleString () из класса JavaScript Number, так как он отформатирует строку для региона пользователя.

Класс JavaScript Date может форматировать локализованные даты и время.

17 из 26
источник
1
На самом деле это набор, заданный пользователем как настройка в приложении (а не на машине, на которой они включены), но я посмотрю, спасибо
Chris S
Добавьте несколько примеров, чтобы каждый мог быстро это понять.
Бхушан Кавадкар
9

Проект PHPJS написал реализации JavaScript для многих функций PHP. Поскольку sprintf()функции PHP в основном совпадают с функциями C printf(), их реализация на JavaScript должна удовлетворить ваши потребности.

Spudley
источник
9

Я использую это:

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

    return newStr;
}

Тогда я называю это:

"<h1>%s</h1><p>%s</p>".format("Header", "Just a test!");
Стивен Пенни
источник
9

У меня есть решение, очень близкое к решению Питера, но оно касается числа и случая объекта.

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

Возможно, может быть даже лучше иметь дело со всеми случаями глубины, но для моих нужд это просто отлично.

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

PS: эта функция очень полезна, если вы используете переводы в шаблонах, таких как AngularJS :

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

Где en.json что-то вроде

{
    "hello-message": "Hello {name}, welcome.",
    "hello-by-name": "Hello {0}, welcome."
}
Thiago Mata
источник
часть [^}] в регулярном выражении не нужна ... используйте вместо нее {(. *?)} или лучше {([\ s \ S] *?)}, чтобы соответствовать символу новой строки.
Rawiro
7

Одна версия, немного отличающаяся от той, которую я предпочитаю (в ней используются токены {xxx}, а не аргументы с нумерацией {0}, это намного больше самодокументируется и лучше подходит для локализации):

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

Вариация будет:

  var formatted = l(this);

который сначала вызывает функцию локализации l ().

Питер
источник
6

Для тех, кто любит Node.JS и его util.formatвозможности, я только что извлек его в свою ванильную форму JavaScript (только с функциями, которые использует 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;
};

Собран из: https://github.com/joyent/node/blob/master/lib/util.js

В
источник
6

Для базового форматирования:

var template = jQuery.validator.format("{0} is not a valid value");
var result = template("abc");
Евгений Гербут
источник
5

У меня есть немного больше форматера для JavaScript здесь ...

Вы можете сделать форматирование несколькими способами:

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

Кроме того, если вы говорите ObjectBase.prototype.format (например, с DateJS ), он будет использовать это.

Примеры...

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

Я также связался с .asFormat и у меня есть определенное место на случай, если уже есть string.format (например, с MS Ajax Toolkit (я ненавижу эту библиотеку).

Tracker1
источник
5

На всякий случай, если кому-то нужна функция для предотвращения загрязнения глобальной области, вот функция, которая делает то же самое:

  function _format (str, arr) {
    return str.replace(/{(\d+)}/g, function (match, number) {
      return typeof arr[number] != 'undefined' ? arr[number] : match;
    });
  };
Афшин Мехрабани
источник
3

Мы можем использовать простую облегченную библиотеку строковых операций String.Format для Typescript.

String.Format ():

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

Формат строки для спецификаторов:

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"

Формат строки для объектов, включая спецификаторы:

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
Муртаза Хуссейн
источник
2

Я не видел String.formatвариант:

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
источник
2

Для использования с функциями успеха jQuery.ajax (). Передайте только один аргумент и строку замените свойствами этого объекта как {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;
};

Пример:

var userInfo = ("Email: {Email} - Phone: {Phone}").format({ Email: "someone@somewhere.com", Phone: "123-123-1234" });
Рэймонд Пауэлл
источник