简体   繁体   English

模板字符串中的数字格式(Javascript - ES6)

[英]Number formatting in template strings (Javascript - ES6)

I was wondering if it is possible to format numbers in Javascript template strings, for example something like:我想知道是否可以在 Javascript 模板字符串中格式化数字,例如:

var n = 5.1234;
console.log(`This is a number: $.2d{n}`);
// -> 5.12

Or possibly或者可能

var n = 5.1234;
console.log(`This is a number: ${n.toString('.2d')}`);
// -> 5.12

That syntax obviously doesn't work, it is just an illustration of the type of thing I'm looking for.该语法显然不起作用,它只是我正在寻找的事物类型的说明。

I am aware of tools like sprintf from underscore.string , but this seems like something that JS should be able to do out the box, especially given the power of template strings.我知道underscore.string sprintf之类的工具,但这似乎是 JS 应该能够开箱即用的东西,尤其是考虑到模板字符串的强大功能。

EDIT编辑

As stated above, I am already aware of 3rd party tools (eg sprintf) and customised functions to do this.如上所述,我已经知道 3rd 方工具(例如 sprintf)和定制功能可以做到这一点。 Similar questions (eg JavaScript equivalent to printf/String.Format ) don't mention template strings at all, probably because they were asked before the ES6 template strings were around.类似的问题(例如JavaScript 等价于 printf/String.Format )根本没有提到模板字符串,可能是因为它们在 ES6 模板字符串出现之前就被问到了。 My question is specific to ES6, and is independent of implementation.我的问题特定于 ES6,与实现无关。 I am quite happy to accept an answer of "No, this is not possible" if that is case, but what would be great is either info about a new ES6 feature that provides this, or some insight into whether such a feature is on its way.如果是这种情况,我很高兴接受“不,这是不可能的”的答案,但最好是有关提供此功能的新 ES6 功能的信息,或了解此类功能是否在其上道路。

No, ES6 does not introduce any new number formatting functions, you will have to live with the existing .toExponential(fractionDigits) , .toFixed(fractionDigits) , .toPrecision(precision) , .toString([radix]) and toLocaleString(…) (which has been updated to optionally support the ECMA-402 Standard , though). 不,ES6没有引入任何新的数字格式化函数,你将不得不使用现有的.toExponential(fractionDigits) .toFixed(fractionDigits) .toPrecision(precision) toLocaleString(…) .toString([radix])toLocaleString(…) (已更新为可选,支持ECMA-402标准 )。
Template strings have nothing to do with number formatting, they just desugar to a function call (if tagged) or string concatenation (default). 模板字符串与数字格式无关,它们只是去函数调用(如果标记)或字符串连接(默认)。

If those Number methods are not sufficient for you, you will have to roll your own. 如果这些Number方法不适合你,你将不得不自己动手。 You can of course write your formatting function as a template string tag if you wish to do so. 如果您愿意,您当然可以将格式化函数编写为模板字符串标记。

You should be able to use the toFixed() method of a number: 您应该能够使用数字的toFixed()方法:

var num = 5.1234;
var n = num.toFixed(2); 

If you want to use ES6 tag functions here's how such a tag function would look, 如果你想在这里使用ES6标签功能这样的标签功能,

function d2(pieces) {
    var result = pieces[0];
    var substitutions = [].slice.call(arguments, 1);

    for (var i = 0; i < substitutions.length; ++i) {
        var n = substitutions[i];

        if (Number(n) == n) {
            result += Number(substitutions[i]).toFixed(2);
        } else {
            result += substitutions[i];
        }

        result += pieces[i + 1];
    }

    return result;
}

which can then be applied to a template string thusly, 然后可以将其应用于模板字符串,

d2`${some_float} (you can interpolate as many floats as you want) of ${some_string}`;

that will format the float and leave the string alone. 这将格式化浮动并留下字符串。

Here's a fully ES6 version of Filip Allberg's solution above, using ES6 "rest" params. 这是一个完全ES6版本的Filip Allberg上面的解决方案,使用ES6“rest”参数。 The only thing missing is being able to vary the precision; 唯一缺少的是能够改变精度; that could be done by making a factory function. 这可以通过制作工厂功能来完成。 Left as an exercise for the reader. 留给读者练习。

 function d2(strs, ...args) { var result = strs[0]; for (var i = 0; i < args.length; ++i) { var n = args[i]; if (Number(n) == n) { result += Number(args[i]).toFixed(2); } else { result += args[i]; } result += strs[i+1]; } return result; } f=1.2345678; s="a string"; console.log(d2`template: ${f} ${f*100} and ${s} (literal:${9.0001})`); 

While formatting using template string interpolation is not available as a built-in, you can get an equivalent behavior with Intl.NumberFormat : 虽然使用模板字符串插值的格式不可用作内置函数,但您可以使用Intl.NumberFormat获得等效行为:

const format = (num, fraction = 2) => new Intl.NumberFormat([], {
  minimumFractionDigits: fraction,
  maximumFractionDigits: fraction,
}).format(num);

format(5.1234); // -> '5.12'

Note that regardless of your implementation of choice, you might get bitten by rounding errors: 请注意,无论您选择哪种实现,都可能会因舍入错误而受到攻击:

(9.999).toFixed(2) // -> '10.00'

new Intl.NumberFormat([], {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2, // <- implicit rounding!
}).format(9.999) // -> '10.00'

based on ES6 Tagged Templates (credit to https://stackoverflow.com/a/51680250/711085 ), this will emulate typical template string syntax in other languages (this is loosely based on python f-strings; I avoid calling it f in case of name overlaps):基于ES6标记模板(信贷https://stackoverflow.com/a/51680250/711085 ),这将效仿其他语言的典型范本字符串语法(这大致是基于Python的F-字符串;我避免调用它f在名称重叠的情况):

Demo:演示:

> F`${(Math.sqrt(2))**2}{.0f}`  // normally 2.0000000000000004
"2"

> F`${1/3}{%} ~ ${1/3}{.2%} ~ ${1/3}{d} ~ ${1/3}{.2f} ~ ${1/3}"
"33% ~ 33.33% ~ 0 ~ 0.33 ~ 0.3333333333333333"

> F`${[1/3,1/3]}{.2f} ~ ${{a:1/3, b:1/3}}{.2f} ~ ${"someStr"}`
"[0.33,0.33] ~ {\"a\":\"0.33\",\"b\":\"0.33\"} ~ someStr

Fairly simple code using :相当简单的代码使用:

var FORMATTER = function(obj,fmt) {
    /* implements things using (Number).toFixed:
       ${1/3}{.2f} -> 0.33
       ${1/3}{.0f} -> 1
       ${1/3}{%} -> 33%
       ${1/3}{.3%} -> 33.333%
       ${1/3}{d} -> 0
       ${{a:1/3,b:1/3}}{.2f} -> {"a":0.33, "b":0.33}
       ${{a:1/3,b:1/3}}{*:'.2f',b:'%'} -> {"a":0.33, "b":'33%'}  //TODO not implemented
       ${[1/3,1/3]}{.2f} -> [0.33, 0.33]
       ${someObj} -> if the object/class defines a method [Symbol.FTemplate](){...}, 
                     it will be evaluated; alternatively if a method [Symbol.FTemplateKey](key){...} 
                     that can be evaluated to a fmt string; alternatively in the future 
                     once decorators exist, metadata may be appended to object properties to derive 
                     formats //TODO not implemented
    */
    try {
        let fracDigits=0,percent;
        if (fmt===undefined) {
            if (typeof obj === 'string')
                return obj;
            else
                return JSON.stringify(obj);
        } else if (obj instanceof Array)
            return '['+obj.map(x=> FORMATTER(x,fmt))+']'
        else if (typeof obj==='object' && obj!==null /*&&!Array.isArray(obj)*/)
            return JSON.stringify(Object.fromEntries(Object.entries(obj).map(([k,v])=> [k,FORMATTER(v,fmt)])));
        else if (matches = fmt.match(/^\.(\d+)f$/))
            [_,fracDigits] = matches;
        else if (matches = fmt.match(/^(?:\.(\d+))?(%)$/))
            [_,fracDigits,percent] = matches;
        else if (matches = fmt.match(/^d$/))
            fracDigits = 0;
        else
            throw 'format not recognized';
        
        if (obj===null)
            return 'null';
        if (obj===undefined) {
            // one might extend the above syntax to 
            // allow for example for .3f? -> "undefined"|"0.123"
            return 'undefined';
        }
        
        if (percent)
            obj *= 100;
        
        fracDigits = parseFloat(fracDigits);
        return obj.toFixed(fracDigits) + (percent? '%':'');
    } catch(err) {
        throw `error executing F\`$\{${someObj}\}{${fmt}}\` specification: ${err}`
    }
}

function F(strs, ...args) {
    /* usage: F`Demo: 1+1.5 = ${1+1.5}{.2f}` 
          --> "Demo: 1+1.5 = 2.50" 
    */
    let R = strs[0];
    args.forEach((arg,i)=> {
        let [_,fmt,str] = strs[i+1].match(/(?:\{(.*)(?<!\\)\})?(.*)/);
        R += FORMATTER(arg,fmt) + str;
    });
    return R;
}

sidenote: The core of the code is as follows.旁注:代码的核心如下。 The heavy lifting is done by the formatter.繁重的工作由格式化程序完成。 The negative lookbehind is somewhat optional, and to let one escape actual curly braces.否定的lookbehind 在某种程度上是可选的,可以让一个人逃避实际的花括号。

    let R = strs[0];
    args.forEach((arg,i)=> {
        let [_,fmt,str] = strs[i+1].match(/(?:\{(.*)(?<!\\)\})?(.*)/);
        R += FORMATTER(arg,fmt) + str;
    });

You can use es6 tag functions. 您可以使用es6标记函数。 I don't know ready for use of that. 我不知道准备好使用它。

It might look like this: 它可能看起来像这样:

num`This is a number: $.2d{n}`

Learn more: 学到更多:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM