Шаблонные литералы ES6 против составных строк

86

У меня есть следующий код для Ecma-Script-6 template literals

let person = {name: 'John Smith'};   
let tpl = `My name is ${person.name}.`;    
let MyVar="My name is "+ person.name+".";

console.log("template literal= "+tpl);  
console.log("my variable = "+MyVar);

Результат выглядит следующим образом:

template literal= My name is John Smith.
my variable = My name is John Smith.

это скрипка. Я попытался найти точную разницу, но не смог ее найти. Мой вопрос: в чем разница между этими двумя утверждениями,

  let tpl = `My name is ${person.name}.`;    

И

  let MyVar = "My name is "+ person.name+".";

Я уже могу связать строку MyVarс person.nameздесь, так каков будет сценарий использования литерала шаблона?

Наим Шейх
источник
8
Это обычная функция для других языков, пора! Выглядит чище и многострочный.
elclanrs
5
Не уверен, что вы подразумеваете под "различием", как tpl === MyVar? Единственная разница - это синтаксис, с которым они были созданы. Обратите внимание, что шаблоны, в отличие от конкатенации строк, также предоставляют функции тегов, которые можно использовать для таких вещей, как автоматическое экранирование.
Берги
Вы в основном спрашиваете, в чем разница между интерполяцией строк и конкатенацией строк.
Damjan Pavlica
Разницу в скорости не стоит учитывать - если наносекунды являются для вас узким местом, тогда вам нужно решить другие более серьезные проблемы, например, другой сервер и т. Д. Я предпочитаю concat, поскольку субъективно обратные кавычки слишком похожи на апострофы, которые используются повсюду.
Джеймс

Ответы:

108

Если вы используете шаблонные литералы только с заполнителями (например `Hello ${person.name}`), как в примере с вопросом, то результат будет таким же, как при простом конкатенации строк. Субъективно он выглядит лучше и его легче читать, особенно для многострочных строк или строк, содержащих и то, 'и другое, и "поскольку вам больше не нужно экранировать эти символы.

Читаемость - отличная функция, но самое интересное в шаблонах - это тегированные литералы шаблонов :

let person = {name: 'John Smith'}; 
let tag = (strArr, name) => strArr[0] + name.toUpperCase() + strArr[1];  
tag `My name is ${person.name}!` // Output: My name is JOHN SMITH!

In the third line of this example, a function named tag is called. The content of the template string is split into multiple variables, that you can access in the arguments of the tag function: literal sections (in this example the value of strArr[0] is My name is and the value of strArr[1] is !) and substitutions (John Smith). The template literal will be evaluated to whatever the tag function returns.

The ECMAScript wiki lists some possible use cases, like automatically escaping or encoding input, or localization. You could create a tag function named msg that looks up the literal parts like My name is and substitutes them with translations into the current locale's language, for example into German:

console.log(msg`My name is ${person.name}.`) // Output: Mein Name ist John Smith.

The value returned by the tag function doesn't even have to be a string. You could create a tag function named $ which evaluates the string and uses it as a query selector to return a collection of DOM nodes, like in this example:

$`a.${className}[href=~'//${domain}/']`
kapex
источник
2
Nice! If you had another template literal there, like ${person.message}, it would be translated along?
Rigotti
1
@Rigotti That's up to the implementation of the msg function. You could certainly also translate the substitution values.
kapex
@Beat The whole ecmascript.org site seems do be down. I think they had plans to abandoned their wiki anyway, so I've updated the links with archived versions.
kapex
I try to run $a.${className}[href=~'//${domain}/'] in chrome console (and set before className='' and domain='' but I don't get the DOM nodes but array of strings :/ (in the other hadn, in jsfiddle we get error in console: jsfiddle.net/d1fkta76 "Uncaught ReferenceError: $ is not defined" - why?
Kamil Kiełczewski
2
@AniketSuryavanshi Here's a comparison of template strings vs. concatenation performance: stackoverflow.com/a/29083467/897024 A few years ago template strings were slower but it looks like they are a bit faster than concatenation now.
kapex
17

ES6 comes up with a new type of string literal, using the ` back-tick as the delimiter. These literals do allow basic string interpolation expressions to be embedded, which are then automatically parsed and evaluated.

let actor = {name: 'RajiniKanth', age: 68};

let oldWayStr = "<p>My name is " + actor.name + ",</p>\n" +
  "<p>I am " + actor.age + " old</p>\n";

let newWayHtmlStr =
 `<p>My name is ${actor.name},</p>
  <p>I am ${actor.age} old</p>`;

console.log(oldWayStr);
console.log(newWayHtmlStr);

As you can see, we used the ..`` around a series of characters, which are interpreted as a string literal, but any expressions of the form ${..} are parsed and evaluated inline immediately.

One really nice benefit of interpolated string literals is they are allowed to split across multiple lines:

var Actor = {"name" : "RajiniKanth"};

var text =
`Now is the time for all good men like ${Actor.name}
to come to the aid of their
country!`;
console.log( text );
// Now is the time for all good men
// to come to the aid of their
// country!

Interpolated Expressions

Any valid expression is allowed to appear inside ${..} in an interpolated string lit‐ eral, including function calls, inline function expression calls, and even other interpo‐ lated string literals!

function upper(s) {
 return s.toUpperCase();
}
var who = "reader"
var text =
`A very ${upper( "warm" )} welcome
to all of you ${upper( `${who}s` )}!`;
console.log( text );
// A very WARM welcome
// to all of you READERS!

Here, the inner ${who}s`` interpolated string literal was a little bit nicer convenience for us when combining the who variable with the "s" string, as opposed to who + "s". Also to keep an note is an interpolated string literal is just lexically scoped where it appears, not dynamically scoped in any way

function foo(str) {
 var name = "foo";
 console.log( str );
}
function bar() {
 var name = "bar";
 foo( `Hello from ${name}!` );
}
var name = "global";
bar(); // "Hello from bar!"

Using the template literal for the HTML is definitely more readable by reducing the annoyance.

The plain old way:

'<div class="' + className + '">' +
  '<p>' + content + '</p>' +
  '<a href="' + link + '">Let\'s go</a>'
'</div>';

With ES6:

`<div class="${className}">
  <p>${content}</p>
  <a href="${link}">Let's go</a>
</div>`
  • Your string can span multiple lines.
  • You don't have to escape quotation characters.
  • You can avoid groupings like: '">'
  • You don't have to use the plus operator.

Tagged Template Literals

We can also tag a template string, when a template string is tagged, the literals and substitutions are passed to function which returns the resulting value.

function myTaggedLiteral(strings) {
  console.log(strings);
}

myTaggedLiteral`test`; //["test"]

function myTaggedLiteral(strings,value,value2) {
  console.log(strings,value, value2);
}
let someText = 'Neat';
myTaggedLiteral`test ${someText} ${2 + 3}`;
// ["test ", " ", ""]
// "Neat"
// 5

We can use the spread operator here to pass multiple values. The first argument — we called it strings — is an array of all the plain strings (the stuff between any interpolated expressions).

we then gather up all subsequent arguments into an array called values using the ... gather/rest operator, though you could of course have left them as individual named parameters following the strings parameter like we did above (value1, value2 etc).

function myTaggedLiteral(strings,...values) {
  console.log(strings);
  console.log(values);    
}

let someText = 'Neat';
myTaggedLiteral`test ${someText} ${2 + 3}`;
// ["test ", " ", ""]
// ["Neat", 5]

The argument(s) gathered into our values array are the results of the already evaluated interpolation expressions found in the string literal. A tagged string literal is like a processing step after the interpolations are evaluated but before the final string value is compiled, allowing you more control over generating the string from the literal. Let's look at an example of creating a re-usable templates.

const Actor = {
  name: "RajiniKanth",
  store: "Landmark"
}

const ActorTemplate = templater`<article>
  <h3>${'name'} is a Actor</h3>
  <p>You can find his movies at ${'store'}.</p>

</article>`;

function templater(strings, ...keys) {
  return function(data) {
  let temp = strings.slice();
  keys.forEach((key, i) => {
  temp[i] = temp[i] + data[key];
  });
  return temp.join('');
  }
};

const myTemplate = ActorTemplate(Actor);
console.log(myTemplate);

Raw Strings

our tag functions receive a first argument we called strings, which is an array. But there’s an additional bit of data included: the raw unprocessed versions of all the strings. You can access those raw string values using the .raw property, like this:

function showraw(strings, ...values) {
 console.log( strings );
 console.log( strings.raw );
}
showraw`Hello\nWorld`;

As you can see, the raw version of the string preserves the escaped \n sequence, while the processed version of the string treats it like an unescaped real new-line. ES6 comes with a built-in function that can be used as a string literal tag: String.raw(..). It simply passes through the raw versions of the strings:

console.log( `Hello\nWorld` );
/* "Hello
World" */

console.log( String.raw`Hello\nWorld` );
// "Hello\nWorld"
Thalaivar
источник
4

It's a lot cleaner and as stated in the comments, is a common features in another languages. The other thing that I found nice was the line breaks, very useful when writing strings.

let person = {name: 'John Smith', age: 24, greeting: 'Cool!' };

let usualHtmlStr = "<p>My name is " + person.name + ",</p>\n" +
                   "<p>I am " + person.age + " old</p>\n" +
                   "<strong>\"" + person.greeting +"\" is what I usually say</strong>";


let newHtmlStr = 
 `<p>My name is ${person.name},</p>
  <p>I am ${person.age} old</p>
  <p>"${person.greeting}" is what I usually say</strong>`;


console.log(usualHtmlStr);
console.log(newHtmlStr);
Rigotti
источник
I dont understand if there is a major difference in having linebreaks in a string and a literal. check this es6fiddle.net/i3vj1ldl. literal only puts a space instead of a line break
Naeem Shaikh
1
Woh, I didn't said it was a major difference. Literals line breaks are just syntax sugar. It's just for the sake of readability.
Rigotti
but still you pointed out a good difference. but before accepting your answer, I will wait for some more time for a better answer which shows any big difference(if there is any! ) :)
Naeem Shaikh
2
@NaeemShaikh I am terribly sorry, but literals line breaks actually do work. Just noticed that ES6Fiddle is just a terrible way to test it. I'll edit my answer.
Rigotti
2

While, my answer does not directly address the question. I thought it may be of some interest to point out one drawback of using template literals in favor of array join.

Lets say I have

let patient1 = {firstName: "John", lastName: "Smith"};
let patient2 = {firstName: "Dwayne", lastName: "Johnson", middleName: "'The Rock'"};

So some patients have a middleName and others do not.

If I wanted a string representing the full name of a patient

let patientName = `${patient1.firstName} ${patient1.middleName} ${patient1.lastName}`;

Then this would become "John undefined Smith"

However if I did

let patientName = [patient1.firstName, patient1.middleName,  patient1.lastName].join(" ");

Then this would become just "John Smith"

EDIT

General_Twyckenham pointed out that a join on " " would result in an extra space between "John" and "Smith".

To get around this you can have a filter before the join to get rid of falsy values: [patient1.firstName, patient1.middleName, patient1.lastName].filter(el => el).join(" ");

Dhruv Prakash
источник
2
Actually, that's not quite correct - the join version will give you John Smith, with an extra space. As you can imagine, this is often undesirable. A fix for this is to use map like so: [patient1.firstName, patient1.middleName, patient1.lastName].map(el => el).join(" ");
General_Twyckenham
@General_Twyckenham aah I see your point. Good catch. Also, it should be filter and not map to get rid of that extra space. I'll edit my answer, thanks.
Dhruv Prakash
Whoops - yeah, filter was the function I meant.
General_Twyckenham
And according to this discussion, string concatenate is faster than array join. stackoverflow.com/questions/7299010/…
Michael Harley