¿Cómo puedo hacer la interpolación de cadenas en JavaScript?

536

Considera este código:

var age = 3;

console.log("I'm " + age + " years old!");

¿Hay alguna otra forma de insertar el valor de una variable en una cadena, aparte de la concatenación de cadenas?

Tom Lehman
fuente
77
Usted puede pagar CoffeeScript: coffeescript.org
dennismonsewicz
1
Como otros han indicado, el método que está utilizando es la forma más fácil de hacerlo. Me encantaría poder referirme a las variables dentro de las cadenas, pero en javascript debes usar la concatenación (o algún otro enfoque de buscar / reemplazar). Las personas como usted y yo probablemente estamos demasiado unidas a PHP por nuestro propio bien.
Lev
44
Use la plantilla
Jahan
2
La concatenación no es interpolación y el usuario preguntó cómo hacer la interpolación. Creo que Lev finalmente proporcionó la respuesta, es decir, no hay forma de hacer esto en JS nativo. No entiendo por qué esta no es una pregunta real.
gitb
44
Si se me permitiera responder, ahora hay una solución que comienza para los nuevos intérpretes (2015 FF y Chrome ya son compatibles): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . P.ej. Show GRAPH of : ${fundCode}-${funcCode} me atrapa Show GRAPH of : AIP001-_sma (use las líneas de retroceso alrededor de la cadena 1 ... parece que no se puede mostrar aquí)
Marcos

Respuestas:

537

Desde ES6, puede usar literales de plantilla :

const age = 3
console.log(`I'm ${age} years old!`)

PS Tenga en cuenta el uso de acentos abiertos: ``.

Damjan Pavlica
fuente
3
Me pregunto por qué se volcaron con esto, ¿no sería "$ {age}" suficiente para hacer la interpolación?
Riendo limonada
55
@LaughingLemonade Compatibilidad con versiones anteriores. Si se implementó de esa manera, todo el código existente que imprime una cadena en ese formato fallará.
thefourtheye
1
@ Jonathan Siempre me disgustan los backticks como un personaje significativo. Pero no estoy seguro de si la razón es universal o solo se aplica a los diseños de teclado de mis idiomas. Backtick es un tipo especial de carácter que espera que se escriba hasta el siguiente carácter, que requiere que se presione dos veces para escribir (y escribe dos de ellos en este momento). Esto se debe a que algunos personajes interactúan con ellos, como "e". Si trato de escribir "ello" con ellos, obtengo èllo``. También está algo molesto (shift + forwardtick, que es otro personaje especial de búsqueda anticipada), ya que requiere shift para escribir, a diferencia de '.
Felix
@felix es un pensamiento que es específico para la distribución de su teclado; lo que estás describiendo se llama "llaves muertas". También están presentes en la distribución del teclado noruego (de donde soy yo), lo cual es parte de la razón por la que cambio a una distribución de teclado estadounidense para toda la programación. (Hay una serie de otros caracteres, por ejemplo [y], que también son mucho más fáciles en un teclado de EE. UU.)
Eivind Eklund
239

tl; dr

Utilice los Literales de cadena de plantilla de ECMAScript 2015, si corresponde.

Explicación

No hay una forma directa de hacerlo, según las especificaciones de ECMAScript 5, pero ECMAScript 6 tiene cadenas de plantilla , que también se conocían como cuasi literales durante la redacción de la especificación. Úselos así:

> var n = 42;
undefined
> `foo${n}bar`
'foo42bar'

Puede usar cualquier expresión de JavaScript válida dentro de {}. Por ejemplo:

> `foo${{name: 'Google'}.name}bar`
'fooGooglebar'
> `foo${1 + 3}bar`
'foo4bar'

La otra cosa importante es que ya no tiene que preocuparse por las cadenas de varias líneas. Puedes escribirlos simplemente como

> `foo
...     bar`
'foo\n    bar'

Nota: Usé io.js v2.4.0 para evaluar todas las cadenas de plantillas que se muestran arriba. También puede usar el último Chrome para probar los ejemplos que se muestran arriba.

Nota: Las especificaciones ES6 ahora están finalizadas , pero aún no se han implementado en todos los principales navegadores.
De acuerdo con las páginas de Mozilla Developer Network , esto se implementará para soporte básico a partir de las siguientes versiones: Firefox 34, Chrome 41, Internet Explorer 12. Si eres usuario de Opera, Safari o Internet Explorer y tienes curiosidad sobre esto ahora , este banco de pruebas se puede usar para jugar hasta que todos obtengan apoyo para esto.

thefourtheye
fuente
3
Es utilizable si usa babeljs , por lo que puede introducirlo en su base de código y luego abandonar el paso de transpilación una vez que los navegadores que necesita admitir lo implementen.
ivarni
¿Hay alguna manera de convertir una cadena estándar en una cadena de plantilla literal? Por ejemplo, si uno tiene un archivo json que contiene una tabla de traducción que necesita valores interpolados en ellos para su visualización? Creo que la primera solución probablemente funcione bien para esta situación, pero en general me gusta la nueva sintaxis de plantilla de cadena.
nbering
Como este sigue siendo uno de los primeros resultados de búsqueda en la interpolación de cadenas js, sería genial si pudiera actualizarlo para reflejar la disponibilidad general ahora. "No hay una forma directa de hacerlo" probablemente ya no sea cierto para la mayoría de los navegadores.
Felix Dombek
2
para la pobre vista en la audiencia, el "personaje es diferente de". Si tiene problemas para que esto funcione, asegúrese de usar la cita grave, (también conocida como cita inclinada, cita posterior) en.wikipedia.org/wiki/Grave_accent . Está en la parte superior izquierda de tu teclado :)
Quincy
@Quincy Abajo a la izquierda de un teclado Mac, también conocido como back-tick :)
RB.
192

Remedial JavaScript de Douglas Crockford incluye una String.prototype.supplantfunción. Es breve, familiar y fácil de usar:

String.prototype.supplant = function (o) {
    return this.replace(/{([^{}]*)}/g,
        function (a, b) {
            var r = o[b];
            return typeof r === 'string' || typeof r === 'number' ? r : a;
        }
    );
};

// Usage:
alert("I'm {age} years old!".supplant({ age: 29 }));
alert("The {a} says {n}, {n}, {n}!".supplant({ a: 'cow', n: 'moo' }));

Si no desea cambiar el prototipo de String, siempre puede adaptarlo para que sea independiente, o colocarlo en otro espacio de nombres, o lo que sea.

Chris Nielsen
fuente
103
Nota: Esto se ejecutará diez veces más lento que simplemente concatenando.
cllpse
36
Y tome aproximadamente tres veces más pulsaciones de teclas y bytes.
ma11hew28
8
@roosteronacid - ¿Puedes dar alguna perspectiva sobre esa disminución de velocidad? ¿Como, de 0.01s a 0.1s (importante) o de 0.000000001s a 0.00000001s (irrelevante)?
Chris
16
@george: Una prueba rápida en mi máquina dio 7312 ns para "¡La vaca dice muu, muu, muu!" usando el método de Crockford vs 111 ns para una función precompilada que extrae las variables del objeto pasado y las concatena con las partes constantes de la cadena de plantilla. Este fue Chrome 21.
George
13
O úsalo como:"The {0} says {1}, {1}, {1}!".supplant(['cow', 'moo'])
Robert Massa
54

Advertencia: evite cualquier sistema de plantillas que no le permita escapar de sus propios delimitadores. Por ejemplo, no habría forma de generar lo siguiente utilizando el supplant()método mencionado aquí.

"Tengo 3 años gracias a mi variable {edad}".

La interpolación simple puede funcionar para pequeños scripts autocontenidos, pero a menudo viene con este defecto de diseño que limitará cualquier uso serio. Sinceramente, prefiero las plantillas DOM, como:

<div> I am <span id="age"></span> years old!</div>

Y use la manipulación de jQuery: $('#age').text(3)

Alternativamente, si simplemente está cansado de la concatenación de cadenas, siempre hay una sintaxis alternativa:

var age = 3;
var str = ["I'm only", age, "years old"].join(" ");
greg.kindel
fuente
44
Nota al margen: Array.join()es más lenta que la +concatenación directa ( estilo), porque los motores de navegador (que incluye V8, que incluye nodo y casi cualquier cosa que ejecute JS hoy) lo han optimizado masivamente y hay una gran diferencia a favor de la concatenación directa
pilau
1
El método de suplantación le permite generar la cadena que menciona: el {token} solo se reemplaza si el objeto de datos contiene un miembro llamado token, por lo tanto, siempre que no proporcione un objeto de datos que tenga un agemiembro, estará bien.
Chris Fewtrell
1
Chris, no veo cómo es una solución. Podría actualizar fácilmente el ejemplo para usar tanto una variable de edad como la cadena {age}. ¿Realmente quiere preocuparse por cómo puede nombrar sus variables en función del texto de copia de la plantilla? --También, desde esta publicación me he convertido en un gran admirador de las bibliotecas de enlace de datos. Algunos, como RactiveJS, lo salvarán de un DOM cargado con tramos variables. Y a diferencia del bigote, solo actualiza esa parte de la página.
greg.kindel
3
Su respuesta principal parece suponer que este javascript se está ejecutando en un entorno de navegador a pesar de que la pregunta no está etiquetada con HTML.
canon
2
Su "palabra de precaución" con respecto a supplantes injustificada: "I am 3 years old thanks to my {age} variable.".supplant({}));devuelve exactamente la cadena dada. Si se ageles diera, aún se podría imprimir {y }usar{{age}}
le_m
23

Pruebe la biblioteca sprintf (una implementación completa de JavaScript sprintf de código abierto). Por ejemplo:

vsprintf('The first 4 letters of the english alphabet are: %s, %s, %s and %s', ['a', 'b', 'c', 'd']);

vsprintf toma una matriz de argumentos y devuelve una cadena con formato.

NawaMan
fuente
22

Utilizo este patrón en muchos idiomas cuando todavía no sé cómo hacerlo correctamente y solo quiero tener una idea rápidamente:

// JavaScript
let stringValue = 'Hello, my name is {name}. You {action} my {relation}.'
    .replace(/{name}/g    ,'Indigo Montoya')
    .replace(/{action}/g  ,'killed')
    .replace(/{relation}/g,'father')
    ;

Si bien no es particularmente eficiente, me parece legible. Siempre funciona y siempre está disponible:

' VBScript
dim template = "Hello, my name is {name}. You {action} my {relation}."
dim stringvalue = template
stringValue = replace(stringvalue, "{name}"    ,"Luke Skywalker")     
stringValue = replace(stringvalue, "{relation}","Father")     
stringValue = replace(stringvalue, "{action}"  ,"are")

SIEMPRE

* COBOL
INSPECT stringvalue REPLACING FIRST '{name}'     BY 'Grendel'
INSPECT stringvalue REPLACING FIRST '{relation}' BY 'Mother'
INSPECT stringvalue REPLACING FIRST '{action}'   BY 'did unspeakable things to'
Jefferey Cave
fuente
12

Puede hacerlo fácilmente usando ES6 template stringy transpilar a ES5 usando cualquier transpilar disponible como babel.

const age = 3;

console.log(`I'm ${age} years old!`);

http://www.es6fiddle.net/im3c3euc/

Mohammad Arif
fuente
6

Pruebe kiwi , un módulo JavaScript ligero para la interpolación de cadenas.

Tu puedes hacer

Kiwi.compose("I'm % years old!", [age]);

o

Kiwi.compose("I'm %{age} years old!", {"age" : age});
zs2020
fuente
6

Si desea interpolar en la console.logsalida, entonces solo

console.log("Eruption 1: %s", eruption1);
                         ^^

Aquí, %ses lo que se llama un "especificador de formato". console.logtiene este tipo de soporte de interpolación incorporado.


fuente
1
Esta es la única respuesta correcta. La pregunta es sobre la interpolación, no las cadenas de plantilla.
sospedra
5

Aquí hay una solución que requiere que proporciones un objeto con los valores. Si no proporciona un objeto como parámetro, utilizará variables globales de manera predeterminada. Pero es mejor seguir usando el parámetro, es mucho más limpio.

String.prototype.interpolate = function(props) {
    return this.replace(/\{(\w+)\}/g, function(match, expr) {
        return (props || window)[expr];
    });
};

// Test:

// Using the parameter (advised approach)
document.getElementById("resultA").innerText = "Eruption 1: {eruption1}".interpolate({ eruption1: 112 });

// Using the global scope
var eruption2 = 116;
document.getElementById("resultB").innerText = "Eruption 2: {eruption2}".interpolate();
<div id="resultA"></div><div id="resultB"></div>

Lucas Trzesniewski
fuente
3
¡No lo uses eval, evales malo!
chris97ong
55
@ chris97ong Si bien eso es "verdadero", al menos da una razón ("el mal" no ayuda) o una solución alternativa. Casi siempre hay una forma de evitar el uso eval, pero a veces no. Por ejemplo, si el OP quería una forma de interpolar usando el alcance actual, sin tener que pasar un objeto de búsqueda (como funciona la interpolación Groovy), estoy bastante seguro de evalque sería necesario. No recurras al viejo "eval is evil".
Ian
1
nunca use eval ni sugiera que se use
hasen
2
@hasenj es por eso que dije que "puede que no sea la mejor idea" y proporcioné un método alternativo. Pero desafortunadamente, evales la única forma de acceder a las variables locales en su alcance . Así que no lo rechaces solo porque lastima tus sentimientos. Por cierto, también prefiero la forma alternativa porque es más segura, pero el evalmétodo es el que responde con precisión la pregunta de OP, por lo tanto, está en la respuesta.
Lucas Trzesniewski
1
El problema evales que no puede acceder a los vars desde otro ámbito, por lo que si su .interpolatellamada está dentro de otra función, y no es global, no funcionará.
georg
2

Ampliando la segunda respuesta de Greg Kindel , puede escribir una función para eliminar algunas de las repeticiones:

var fmt = {
    join: function() {
        return Array.prototype.slice.call(arguments).join(' ');
    },
    log: function() {
        console.log(this.join(...arguments));
    }
}

Uso:

var age = 7;
var years = 5;
var sentence = fmt.join('I am now', age, 'years old!');
fmt.log('In', years, 'years I will be', age + years, 'years old!');
James Ko
fuente
1

Te puedo mostrar con un ejemplo:

function fullName(first, last) {
  let fullName = first + " " + last;
  return fullName;
}

function fullNameStringInterpolation(first, last) {
  let fullName = `${first} ${last}`;
  return fullName;
}

console.log('Old School: ' + fullName('Carlos', 'Gutierrez'));

console.log('New School: ' + fullNameStringInterpolation('Carlos', 'Gutierrez'));

sombras3002
fuente
1

Desde ES6, si desea hacer una interpolación de cadenas en claves de objeto, obtendrá un SyntaxError: expected property name, got '${'si hace algo como:

let age = 3
let obj = { `${age}`: 3 }

En su lugar, debe hacer lo siguiente:

let obj = { [`${age}`]: 3 }
Yuxuan Chen
fuente
1

Suplante más para la versión ES6 de la publicación de @Chris Nielsen.

String.prototype.supplant = function (o) {
  return this.replace(/\${([^\${}]*)}/g,
    (a, b) => {
      var r = o[b];
      return typeof r === 'string' || typeof r === 'number' ? r : a;
    }
  );
};

string = "How now ${color} cow? {${greeting}}, ${greeting}, moo says the ${color} cow.";

string.supplant({color: "brown", greeting: "moo"});
=> "How now brown cow? {moo}, moo, moo says the brown cow."
SoEzPz
fuente
0

El uso de la sintaxis de la plantilla falla en los navegadores más antiguos, importante si está creando HTML para uso público. El uso de la concatenación es tedioso y difícil de leer, especialmente si tiene muchas o largas expresiones, o si debe usar paréntesis para manejar mezclas de elementos de números y cadenas (los cuales usan el operador +).

PHP expande cadenas entre comillas que contienen variables e incluso algunas expresiones usando una notación muy compacta: $a="the color is $color";

En JavaScript, se puede escribir una función eficiente para apoyar esto:, var a=S('the color is ',color);usando un número variable de argumentos. Si bien no hay ninguna ventaja sobre la concatenación en este ejemplo, cuando las expresiones se alargan, esta sintaxis puede ser más clara. O se puede usar el signo de dólar para señalar el inicio de una expresión usando una función de JavaScript, como en PHP.

Por otro lado, escribir una función de solución eficiente para proporcionar una expansión de cadenas similar a una plantilla para navegadores más antiguos no sería difícil. Probablemente alguien ya lo haya hecho.

Finalmente, imagino que sprintf (como en C, C ++ y PHP) podría escribirse en JavaScript, aunque sería un poco menos eficiente que estas otras soluciones.

David Spector
fuente
0

Interpolación flexible personalizada:

var sourceElm = document.querySelector('input')

// interpolation callback
const onInterpolate = s => `<mark>${s}</mark>`

// listen to "input" event
sourceElm.addEventListener('input', parseInput) 

// parse on window load
parseInput() 

// input element parser
function parseInput(){
  var html = interpolate(sourceElm.value, undefined, onInterpolate)
  sourceElm.nextElementSibling.innerHTML = html;
}

// the actual interpolation 
function interpolate(str, interpolator = ["{{", "}}"], cb){
  // split by "start" pattern
  return str.split(interpolator[0]).map((s1, i) => {
    // first item can be safely ignored
	  if( i == 0 ) return s1;
    // for each splited part, split again by "end" pattern 
    const s2 = s1.split(interpolator[1]);

    // is there's no "closing" match to this part, rebuild it
    if( s1 == s2[0]) return interpolator[0] + s2[0]
    // if this split's result as multiple items' array, it means the first item is between the patterns
    if( s2.length > 1 ){
        s2[0] = s2[0] 
          ? cb(s2[0]) // replace the array item with whatever
          : interpolator.join('') // nothing was between the interpolation pattern
    }

    return s2.join('') // merge splited array (part2)
  }).join('') // merge everything 
}
input{ 
  padding:5px; 
  width: 100%; 
  box-sizing: border-box;
  margin-bottom: 20px;
}

*{
  font: 14px Arial;
  padding:5px;
}
<input value="Everything between {{}} is {{processed}}" />
<div></div>

vsync
fuente
0

Si bien las plantillas son probablemente las mejores para el caso que usted describe, si tiene o quiere sus datos y / o argumentos en forma iterativa / matriz, puede usarlos String.raw.

String.raw({
  raw: ["I'm ", " years old!"]
}, 3);

Con los datos como una matriz, se puede usar el operador de propagación:

const args = [3, 'yesterday'];
String.raw({
  raw: ["I'm ", " years old as of ", ""]
}, ...args);
Brett Zamir
fuente
0

No pude encontrar lo que estaba buscando, luego lo encontré:

Si está utilizando Node.js, hay un utilpaquete integrado con una función de formato que funciona así:

util.format("Hello my name is %s", "Brent");
> Hello my name is Brent

Casualmente, esto también está integrado en console.logsabores en Node.js:

console.log("This really bad error happened: %s", "ReferenceError");
> This really bad error happened: ReferenceError
B pescado
fuente