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


1970

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

Моя основна вимога - це тисяча розділового формату для чисел на даний момент, але те, що обробляє безліч комбінацій (включаючи дати), було б добре.

Я усвідомлюю, що бібліотека Ajax від Microsoft надає версію String.Format(), але ми не хочемо, щоб цілий наклад цієї рамки.


2
Окрім усіх чудових відповідей нижче, ви можете поглянути на цей: stackoverflow.com/a/2648463/1712065, який IMO, є найбільш ефективним рішенням цієї проблеми.
Енні

1
Я написав дешевий, який використовує синтаксис printf, схожий на C.
Braden Best

var search = [$ range.dog, "1"]; var url = vsprintf (" earth / Services / dogSearch.svc / FindMe /% s /% s ", пошук); *** Щодо вузла, ви можете отримати свій модуль за "npm install sprintf-js"
Jenna Leaf

Я також написав просту функцію, щоб досягти цього; stackoverflow.com/a/54345052/5927126
AnandShanbhag

Відповіді:


1109

З 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}. Тож зробіть замість цього одночасну заміну, як, наприклад, у пропозиціях страху .


16
Якщо потрібне лише просте перетворення числа в рядок, num.toFixed()методу може бути достатньо!
heltonbiker

@MaksymilianMajer, здається, щось масово відрізняється.
Еван Керролл

@EvanCarroll ви праві. На той момент, коли я писав коментар, сховище sprintf() for JavaScriptне було. underscore.stringокрім sprintf, який базується на sprintf() for JavaScriptвпровадженні, має більше можливостей . Крім цього, бібліотека - це зовсім інший проект.
Максиміліан Маєр

@MaksymilianMajer правильно, просто сказавши, що ця відповідь мертва, і посилання затихла. Його потрібно повністю очистити.
Еван Керролл

2
На цю відповідь більше не слід приймати. Станом на ES6 це вбудовано в мову javascript (як у браузерах, так і NodeJS). Дивіться відповідь @Kim нижче.
Райан Шиллінгтон

1390

Спираючись на запропоновані раніше рішення:

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


12
|| трюк не працює, якщо args [число] дорівнює 0. Слід зробити явний if (), щоб побачити, якщо (args [number] === undefined).
fserb

4
в іншому твердженні скорочення, якщо б, чому б просто не "відповідати" замість "'{' + номер + '}'". відповідність повинна дорівнювати цій рядку.
mikeycgto

4
Якщо у вас декілька рядків додано один до одного (разом з +-оператором), не забудьте поставити повний рядок у круглі дужки: ("asd {0}"+"fas {1}").format("first", "second");інакше функція буде застосована лише до останнього доданого рядка.
Лукас Кнут

3
Це трохи і тонко змінює результат. Уявіть собі 'foo {0}'.format(fnWithNoReturnValue()). Наразі це повернеться foo {0}. Зі своїми змінами воно повернеться foo undefined.
страхфаг

2
@avenmore: / \ {(\ d +) \} / g
Хозукі

491

Це смішно, оскільки переповнення стека насправді має власну функцію форматування для Stringназваного прототипу formatUnicorn. Спробуй це! Зайдіть у консоль і введіть щось на кшталт:

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

Firebug

Ви отримуєте цей вихід:

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's sliceзмусить все, що argumentsвходить в Array, будь то спочатку чи ні, і keyбуде індекс (0, 1, 2 ...) кожного елемента масиву, примусованого до рядка (наприклад, "0", так "\\{0\\}"для першого шаблону регулярного вирівнювання).

Акуратний.


402
Дуже прикольно відповісти на запитання про stackoverflow з кодом від
stackoverflow

5
@JamesManning Регекс дозволяє встановити глобальний прапор ( g), який може замінити той самий ключ не один раз. У наведеному вище прикладі ви можете використовувати {name}кілька разів в одному реченні і замінити їх усі.
KrekkieD

3
Якщо чесно, це здається жахливим. Що станеться, наприклад, якщо nameє "blah {adjective} blah"?
sam hocevar

5
@ruffin "трохи гіперболічний"? Код, який обдурить інтерпретацію даних користувача як рядки формату, - це ціла категорія вразливих місць . 98,44% перевищує посереднє .
sam hocevar

3
@samhocevar Я не можу повірити, що ти Маленький Боббі подав мені. ;) Якщо ви використовуєте текст, оброблений клієнтом JavaScript на сервері вашої бази даних, без будь-яких перевірок безпеки, небо допоможе нам усім. ; ^) Подивіться, жоден користувач не може надіслати щось від клієнта (наприклад, листоноші), що перешкоджає безпеці вашого сервера. І ви повинні припустити, що все небезпечне, що може бути надіслане від клієнта, буде . Тобто, якщо вам потрібна стовідсоткова безпека від JavaScript-коду на стороні клієнта, який завжди редагується користувачем, і ви думаєте, що ця функція може відкрити ризик безпеки, ви граєте в неправильну гру.
ruffin

325

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

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

Заокруглення чисел з плаваючою комою

sprintf("%.2f", num)Здається, еквівалент JavaScript у num.toFixed(2)форматі numдо двох десяткових знаків з округленням (але дивіться коментар @ 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
Лише бічна примітка, якщо ви використовуєте старіший веб-переглядач або підтримуєте старіші браузери, деякі браузери, реалізовані toFixed неправильно, використання Math.round замість toFixed є кращим рішенням.
ars265

7
@Raphael_ і @rescdsk: ..також працює:33333..toExponential(2);
Петро Ярич

Або (33333) .toExponential (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)
Камберлен

4
це рішення не працюватиме для рядка формату, переданого в змінній (наприклад, із сервера)
user993954

1
@inanutshellus Це добре працює, якщо функція шаблону визначена на тій самій машині, де вона виконується. Наскільки я знаю, ви не можете передавати функцію як JSON, тому зберігання функцій шаблонів у базі даних працює не так добре.
стифл

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} не буде замінено. ;)


3
gist.github.com/1049426 Я оновив ваш приклад таким підходом. Численні переваги, включаючи збереження нативної реалізації, якщо вона існує, стримування тощо. Я спробував видалити регулярні вирази, але приємний вид, необхідний для глобальної заміни. : - /
tbranyen

6
jsxt на жаль, ліцензований GPL
AndiDog

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

1
чому +=?, чи вартоformatted = this.replace("{" + arg + "}", arguments[arg]);
guilin 桂林

2
Я думаю, що код все ще не правильний. Правильний має бути таким, як розміщений Філіпіз .
wenqiang

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

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"

Як це працює:

Зменшення застосовує функцію проти акумулятора і кожного елемента масиву (зліва направо), щоб зменшити його до одного значення.

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


4
Ось версія, яка використовує цей підхід для створення спрощеної printfфункції: jsfiddle.net/11szrbx9
Dem Pilafian

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'])(зауважте, що це трохи зайве, щоб краще вписатись у ваш приклад)
Тіно

Вам доведеться реалізувати функції заміни для кожного printfспецифікатора типу та включати логіку для заміщення префіксів. Ітерація над рядком формату в розумному вигляді, здається, є незначною проблемою тут, імхо. Хоча акуратне рішення, якщо вам потрібні лише заміни рядків.
колапс

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

@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! %%"

3
ця відповідь була чудовою для швидкої копіювання вставлення в існуючу функцію. Не потрібно ніяких завантажень і т. Д.
Нік

@ Нік так, це ідея :)
Бреден Кращий

21

+1 Zippo за винятком того, що тіло функції має бути як нижче, так інакше він додає поточний рядок на кожній ітерації:

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

1
На Firefox це не працювало. Налагоджувач показує аргумент як невизначений.
xiao 啸

Це не замінює другого символу, '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++)
samarjit samanta

2
Для довідки, for...inпрацюватиме не в кожному браузері, як очікує цей код. Він буде перебирати всі перелічені властивості, які в деяких браузерах будуть включати arguments.length, а в інших взагалі навіть не включатимуть самі аргументи. У будь-якому випадку, якщо вони Object.prototypeбудуть додані, будь-які доповнення, ймовірно, будуть включені до групи. Код повинен використовувати стандартний forцикл, а не for...in.
cHao

Вам слід запропонувати редагувати відповідь замість дублювання відповіді. Цей дублікат цієї відповіді
Руссо

19

Я хочу поділитися своїм вирішенням проблеми. Я не винайшов колесо, але намагаюся знайти рішення на основі того, що вже робить JavaScript. Перевага полягає в тому, що ви отримуєте всі неявні перетворення безкоштовно. Встановлення властивості прототипу $ 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'

на жаль, принаймні # і + не реалізовані для плавців. ось посилання на функцію в: tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Даніель,


14

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


Я подивився на цю бібліотеку, і вона виглядає справді чудово. Я був розлючений, коли побачив, що завантаження - EXE. Про що це чорт? Не завантажували.
jessegavin

Часто архів, що завантажується, це EXE - це не що інше, як "саморозпаковується ZIP". Виконай його, і він розпакується. Це досить зручно, але так схоже на зловмисне програмне забезпечення, формат не використовується в Інтернеті все частіше.
Чак Колларс

Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться.
starmole

@starmole посилання на (мінімізовану) бібліотеку JavaScript на 4 кБ . Я не вірю, що вставка цього відповіді є гарною ідеєю.
ivarni

Ви правильно вставте, було б не краще. Я щойно отримав цей коментар для випадкового огляду - і прокоментував його, перш ніж не сподобався. Для мене stackoverflow краще під час надання пояснень, а не готових рішень (на які посилання є). Я також не хочу заохочувати людей публікувати чи завантажувати чорний код.
starmole

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


Це єдиний, який обробляє кронштейни для втечі {{0}}, а також подібні речі {0}{1}.format("{1}", "{0}"). Повинен бути на самому верху!
Хуан

11

Якщо ви хочете обробити роздільник тисяч, вам дійсно слід скористатися toLocaleString () з класу Java Number, оскільки він буде форматувати рядок для регіону користувача.

JavaScript Дата клас може форматувати локалізовану дату і час.


1
Це насправді набір користувачем як налаштування в додатку (а не машина увімкнена), але я погляну, дякую
Chris S

додайте кілька прикладів, щоб кожен міг це швидко зрозуміти.
Bhushan Kawadkar

9

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


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

частина [^}] у регулярному вираженні непотрібна. Натомість використовуйте {(. *?)}, або краще {([\ 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



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 (я ненавиджу цю бібліотеку).


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

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" });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.