Jak mogę interpolować ciąg w JavaScript?

534

Rozważ ten kod:

var age = 3;

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

Czy istnieją inne sposoby wstawiania wartości zmiennej do łańcucha oprócz konkatenacji łańcucha?

Tom Lehman
źródło
7
Możesz sprawdzić
dennismonsewicz
1
Jak wykazały inni, sposób używasz jest najprostszym sposobem, aby go o tym. Chciałbym móc odwoływać się do zmiennych wewnątrz ciągów, ale w javascript musisz użyć konkatenacji (lub innej metody znajdowania / zastępowania). Ludzie tacy jak ty i ja prawdopodobnie są trochę zbyt przywiązani do PHP dla naszego własnego dobra.
Lev
4
Użyj szablonu
Jahan
2
Łączenie nie jest interpolacją, a użytkownik zapytał, jak wykonać interpolację. Myślę, że Lev w końcu udzielił odpowiedzi, tzn. Nie ma możliwości zrobienia tego w natywnym JS. Nie rozumiem, dlaczego to nie jest prawdziwe pytanie.
gitb
4
Gdybym mógł odpowiedzieć, istnieje teraz rozwiązanie dla nowszych tłumaczy (obsługa FF i Chrome już w 2015 roku): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Na przykład. Show GRAPH of : ${fundCode}-${funcCode} dostaje mnie Show GRAPH of : AIP001-_sma (użyj odwrotności wokół ciągu 1 ... nie wydaje się, aby wyświetlał się tutaj)
Marcos

Odpowiedzi:

536

Od wersji ES6 możesz używać literałów szablonów :

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

PS Uwaga użycie backticks: ``.

Damjan Pavlica
źródło
3
Zastanawiam się, dlaczego poszli z tym w tył, czy „$ {age}” nie wystarczyłoby do interpolacji?
Laughing Lemonade,
5
@LaughingLemonade Kompatybilność wsteczna. Jeśli zostanie zaimplementowany w ten sposób, cały istniejący kod, który drukuje ciąg w tym formacie, zawiedzie.
theourourheheye
1
@Jathanathan Zawsze nie lubię zwrotów jako znaczącej postaci. Nie jestem jednak pewien, czy powód jest uniwersalny, czy dotyczy tylko układów klawiatury w moich językach. Backtick jest specjalnym rodzajem postaci, który czeka na napisanie do następnej postaci, wymagając dwukrotnego naciśnięcia, aby napisać (i pisze dwie z nich w tym momencie). Jest tak, ponieważ niektóre postacie wchodzą w interakcje z nimi, na przykład „e”. Jeśli spróbuję napisać z nimi „ello”, dostanę èllo``. Jest również nieco irytująco zlokalizowany (Shift + przewijanie do przodu, co jest kolejną specjalną postacią z wyprzedzeniem), ponieważ wymaga przejścia do pisania, w przeciwieństwie do „.
felix
@ Felix jest to myśl specyficzna dla twojego układu klawiatury; to, co opisujesz, nazywa się „martwymi kluczami”. Są one również obecne w norweskim układzie klawiatury (skąd pochodzę), co jest jednym z powodów, dla których przestawiłem się na układ klawiatury USA dla wszystkich programów. (Istnieje wiele innych znaków - np. [I] - które są również znacznie łatwiejsze na klawiaturze amerykańskiej.)
Eivind Eklund
239

tl; dr

W razie potrzeby użyj literałów ciągowych szablonu ECMAScript 2015.

Wyjaśnienie

Nie ma bezpośredniego sposobu, aby to zrobić, zgodnie ze specyfikacjami ECMAScript 5, ale ECMAScript 6 ma ciągi szablonów , które były również znane jako quasi-literały podczas opracowywania specyfikacji. Użyj ich w ten sposób:

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

Możesz użyć dowolnego prawidłowego wyrażenia JavaScript w {}. Na przykład:

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

Inną ważną rzeczą jest to, że nie musisz się już martwić ciągami wieloliniowymi. Możesz napisać je po prostu jako

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

Uwaga: Użyłem io.js v2.4.0 do oceny wszystkich ciągów szablonów pokazanych powyżej. Możesz także użyć najnowszego Chrome do przetestowania powyższych przykładów.

Uwaga: Specyfikacje ES6 są teraz sfinalizowane , ale nie zostały jeszcze zaimplementowane przez wszystkie główne przeglądarki.
Zgodnie ze stronami Mozilla Developer Network zostanie to zaimplementowane w celu zapewnienia podstawowej obsługi począwszy od następujących wersji: Firefox 34, Chrome 41, Internet Explorer 12. Jeśli jesteś użytkownikiem Opery, Safari lub Internet Explorera i jesteś ciekawy tego teraz , to łóżko testowe może być używane do zabawy, dopóki wszyscy nie otrzymają wsparcia.

thefourtheye
źródło
3
Jest to użyteczne, jeśli używasz babeljs , więc możesz wprowadzić go do swojej bazy kodu, a następnie upuścić krok transpilacji, gdy tylko przeglądarki, które mają obsługiwać, go implementują.
ivarni
Czy istnieje sposób na konwersję standardowego ciągu znaków na dosłowny ciąg szablonu? Na przykład, jeśli ktoś miał plik json zawierający tabelę tłumaczeń, która potrzebowała wartości interpolowanych w celu wyświetlania? Myślę, że pierwsze rozwiązanie prawdopodobnie działa dobrze w tej sytuacji, ale ogólnie podoba mi się nowa składnia szablonu łańcucha.
nbering
Ponieważ jest to nadal jeden z pierwszych wyników wyszukiwania interpolacji ciągów js, dobrze byłoby zaktualizować go, aby odzwierciedlał ogólną dostępność teraz. „Nie ma bezpośredniego sposobu, aby to zrobić” prawdopodobnie nie jest już prawdą w przypadku większości przeglądarek.
Felix Dombek
2
z powodu słabego wzroku na widowni „postać jest inna niż”. Jeśli masz problem z uruchomieniem tego, upewnij się, że używasz poważnego cytatu (aka przechylonego, wstecznego) en.wikipedia.org/wiki/Grave_accent . Jest w lewym górnym rogu na klawiaturze :)
Quincy
@Quincy U dołu po lewej stronie klawiatury komputera Mac - znanej również jako back-tick :)
RB.
192

Skorygowany JavaScript Douglasa Crockforda zawiera String.prototype.supplantfunkcję. Jest krótki, znajomy i łatwy w użyciu:

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

Jeśli nie chcesz zmieniać prototypu String, zawsze możesz go dostosować, aby był samodzielny, lub umieścić go w innej przestrzeni nazw, czy cokolwiek innego.

Chris Nielsen
źródło
102
Uwaga: To będzie działać dziesięć razy wolniej niż tylko łączenie.
cllpse
36
I weź około trzy razy więcej naciśnięć klawiszy i bajtów.
ma11hew28,
8
@roosteronacid - Czy możesz dać jakieś spojrzenie na to zmniejszenie prędkości? Na przykład od 0,01 s do 0,1 s (ważne) lub od 0,000000001 s do 0,00000001 s (nieistotne)?
Chris
16
@george: Szybki test na mojej maszynie dał 7312 ns za „Krowa mówi: muczę, muczę, muczę!” używając metody Crockforda vs 111 ns dla wstępnie skompilowanej funkcji, która wyciąga zmienne z przekazywanego obiektu i łączy je ze stałymi częściami łańcucha szablonu. To był Chrome 21.
George
13
Lub użyj go w następujący sposób:"The {0} says {1}, {1}, {1}!".supplant(['cow', 'moo'])
Robert Massa
54

Uwaga: unikaj jakiegokolwiek systemu szablonów, który nie pozwala ci uciec przed własnymi ogranicznikami. Na przykład nie byłoby sposobu na wyprowadzenie następujących przy użyciu supplant()metody wymienionej tutaj.

„Mam 3 lata dzięki mojej zmiennej {age}”.

Prosta interpolacja może działać w przypadku małych samodzielnych skryptów, ale często ma tę wadę projektową, która ogranicza każde poważne użycie. Szczerze wolę szablony DOM, takie jak:

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

I użyj manipulacji jQuery: $('#age').text(3)

Alternatywnie, jeśli jesteś po prostu zmęczony łączeniem łańcuchów, zawsze istnieje alternatywna składnia:

var age = 3;
var str = ["I'm only", age, "years old"].join(" ");
greg.kindel
źródło
4
Uwaga dodatkowa: Array.join()jest wolniejsza niż bezpośrednia ( +stylowa) konkatenacja, ponieważ silniki przeglądarki (w tym V8, która zawiera węzeł i prawie wszystko, co obsługuje JS dzisiaj) znacznie go zoptymalizowały i istnieje duża różnica na korzyść bezpośredniej konkatenacji
pilau
1
Metoda zastępcza pozwala wygenerować wspomniany ciąg: {token} jest zamieniany tylko wtedy, gdy obiekt danych zawiera element o nazwie token- w ten sposób, jeśli nie podasz obiektu danych, który ma ageelement, będzie dobrze.
Chris Fewtrell
1
Chris, nie rozumiem, jak to rozwiązanie. Mógłbym łatwo zaktualizować przykład, aby używał zarówno zmiennej wiekowej, jak i ciągu {age}. Czy naprawdę chcesz się martwić o to, jak możesz nazwać swoje zmienne na podstawie tekstu szablonu? - Również od tego postu stałem się wielkim fanem bibliotek do wiązania danych. Niektóre, takie jak RactiveJS, uratują cię od DOM obciążonego zmiennymi zakresami. I w przeciwieństwie do Wąsów, aktualizuje tylko tę część strony.
greg.kindel
3
Twoja główna odpowiedź wydaje się zakładać, że ten javascript działa w środowisku przeglądarki, nawet jeśli pytanie nie jest otagowane HTML.
kanon
2
Twoje „słowo ostrzeżenia” w odniesieniu do supplantjest nieuzasadnione: "I am 3 years old thanks to my {age} variable.".supplant({}));zwraca dokładnie podany ciąg. Gdyby ageje podano, można by nadal drukować {i }używać{{age}}
le_m,
23

Wypróbuj bibliotekę sprintf (pełna implementacja sprintf JavaScript typu open source). Na przykład:

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

vsprintf pobiera tablicę argumentów i zwraca sformatowany ciąg.

NawaMan
źródło
21

Używam tego wzorca w wielu językach, gdy jeszcze nie wiem, jak to zrobić poprawnie i chcę szybko znaleźć jakiś pomysł:

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

Chociaż nie jest szczególnie wydajny, uważam go za czytelny. Zawsze działa i jest zawsze dostępny:

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

ZAWSZE

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

Możesz to zrobić z łatwością za pomocą ES6 template stringi przenieść na ES5 za pomocą dowolnego dostępnego transpilaru, takiego jak babel.

const age = 3;

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

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

Mohammad Arif
źródło
6

Spróbuj kiwi , lekki moduł JavaScript do interpolacji łańcuchów.

Możesz to zrobić

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

lub

Kiwi.compose("I'm %{age} years old!", {"age" : age});
zs2020
źródło
6

Jeśli chcesz interpolować console.logdane wyjściowe, to po prostu

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

Tutaj %snazywa się to „specyfikatorem formatu”. console.logma wbudowaną obsługę tego rodzaju interpolacji.


źródło
1
To jedyna poprawna odpowiedź. Pytanie dotyczy interpolacji, a nie ciągów szablonów.
sospedra
5

Oto rozwiązanie, które wymaga podania obiektu o wartościach. Jeśli nie podasz obiektu jako parametru, domyślnie użyje zmiennych globalnych. Ale lepiej trzymaj się tego parametru, jest znacznie czystszy.

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>

Łukasz Trzesniewski
źródło
3
Nie używaj eval, evalto zło!
chris97ong
5
@ chris97ong Chociaż jest to „prawda”, przynajmniej podaj przyczynę („zło” nie pomaga) lub alternatywne rozwiązanie. Prawie zawsze istnieje sposób na użycie eval, ale czasem nie. Na przykład, jeśli OP chciałby interpolować przy użyciu bieżącego zakresu, bez konieczności przekazywania obiektu odnośnika (jak działa interpolacja Groovy), jestem prawie pewien eval, że będzie to wymagane. Nie uciekaj się tylko do starego „eval is evil”.
Ian
1
nigdy nie używaj eval ani nigdy nie sugeruj, aby go używać
hasen
2
@hasenj dlatego powiedziałem, że „może nie być najlepszym pomysłem” i podałem alternatywną metodę. Niestety evaljest to jedyny sposób na uzyskanie dostępu do zmiennych lokalnych w zakresie . Więc nie odrzucaj tego tylko dlatego, że rani twoje uczucia. BTW, ja też wolę alternatywny sposób, ponieważ jest bezpieczniejszy, ale evalmetoda dokładnie odpowiada na pytanie OP, stąd jest w odpowiedzi.
Lucas Trzesniewski
1
Problem evalpolega na tym, że nie można uzyskać dostępu do zmiennych z innego zakresu, więc jeśli twoje .interpolatewywołanie jest w innej funkcji, a nie globalnej, to nie zadziała.
georg
2

Rozwijając drugą odpowiedź Grega Kindela , możesz napisać funkcję eliminującą część płyty kotłowej:

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

Stosowanie:

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

Mogę ci pokazać przykład:

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

odcienie3002
źródło
1

Od wersji ES6, jeśli chcesz wykonać interpolację ciągów w kluczach obiektowych, otrzymasz, SyntaxError: expected property name, got '${'jeśli zrobisz coś takiego:

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

Zamiast tego należy wykonać następujące czynności:

let obj = { [`${age}`]: 3 }
Yuxuan Chen
źródło
1

Dostarcza więcej dla wersji ES6 posta @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
źródło
0

Używanie składni szablonów kończy się niepowodzeniem w starszych przeglądarkach, co jest ważne, jeśli tworzysz HTML do użytku publicznego. Używanie konkatenacji jest żmudne i trudne do odczytania, szczególnie jeśli masz wiele lub długie wyrażenia, lub jeśli musisz używać nawiasów do obsługi kombinacji elementów liczbowych i łańcuchowych (oba używają operatora +).

PHP rozwija ciągi cytowane zawierające zmienne, a nawet niektóre wyrażenia, używając bardzo zwartej notacji: $a="the color is $color";

W JavaScript można napisać wydajną funkcję do obsługi tego: var a=S('the color is ',color); przy użyciu zmiennej liczby argumentów. Chociaż w tym przykładzie nie ma przewagi nad konkatenacją, gdy wyrażenia stają się dłuższe, ta składnia może być jaśniejsza. Lub można użyć znaku dolara do zasygnalizowania początku wyrażenia za pomocą funkcji JavaScript, jak w PHP.

Z drugiej strony napisanie wydajnej funkcji obejścia w celu zapewnienia podobnego do szablonu rozszerzenia ciągów dla starszych przeglądarek nie byłoby trudne. Ktoś prawdopodobnie już to zrobił.

Wreszcie, wyobrażam sobie, że sprintf (jak w C, C ++ i PHP) mógłby być napisany w JavaScript, chociaż byłby nieco mniej wydajny niż te inne rozwiązania.

David Spector
źródło
0

Niestandardowa elastyczna interpolacja:

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

Chociaż szablony są prawdopodobnie najlepsze w opisywanym przypadku, jeśli masz lub chcesz swoje dane i / lub argumenty w postaci iterowalnej / tablicowej, możesz użyć String.raw.

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

Mając dane jako tablicę, można użyć operatora rozkładania:

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

Nie mogłem znaleźć tego, czego szukałem, a potem znalazłem -

Jeśli używasz Node.js, jest wbudowany utilpakiet z funkcją formatowania, która działa w następujący sposób:

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

Przypadkowo jest to teraz wbudowane w console.logsmaki również w Node.js -

console.log("This really bad error happened: %s", "ReferenceError");
> This really bad error happened: ReferenceError
B Ryba
źródło