今まで局所変数は var で宣言し、その有効範囲 (スコープ) は関数単位でした。ES2015 では let / const で局所変数を宣言すると、その有効範囲をブロック { ... } に限定することができます。let は変数で、const は定数を宣言します。
let var1, var2, ..., varnN; let var1 = value1, var2 = value2, ..., varN = valueN;
簡単な実行例を示しましょう。
> { ... var a = 10; ... let b = 20; ... console.log(a); ... console.log(b); ... } 10 20 undefined > console.log(a); 10 undefined > console.log(b); ReferenceError: b is not defined > { let a = 10; { let a = 20; console.log(a); } console.log(a) } 20 10 undefined > { let a = 10, b = 20; { let a = 30; console.log(a); console.log(b); } console.log(a) } 30 20 10 undefined
変数 a は var で宣言されているので、ブロックを終了したあとでも存在します。これに対し、変数 b は let で宣言されているので、有効範囲はブロックの中だけです。ブロックを終了したあと、変数 b は存在しなくなる (破棄される) ので、console.log(b) はエラーになります。
ブロックは入れ子にすることができます。この場合、外側のブロックの変数 a と、内側のブロックの変数 a は別のものになります。内側のブロックで外側のブロックと同じ名前の変数を宣言すると、外側のブロックの変数は「隠蔽 (shadowing)」されるので、アクセスすることはできなくなります。隠蔽されてなければ、内側のブロックから外側のブロックの変数にアクセスすることができます。
これはC/C++ のスコープや関数型言語の let 文と同じ動作ですが、他のプログラミング言語、たとえば Java や C# では、ブロックが入れ子の場合、内側のブロックで外側のブロックと同名の変数を宣言することはできません。ご注意くださいませ。
ES2015 では、匿名関数を => で表記することができます。これを「アロー関数 (Arrow Functions)」といいます。
(引数, ...) => { 処理; ... }
( ... ) の中に仮引数を指定します。引数が一つしかない場合、丸カッコを省略することができます。関数本体の処理は { ... } の中に記述します。値を返す場合は return 文を使います。また、処理 (式) がひとつしかない場合、波カッコと return 文を省略することができます。
簡単な実行例を示します。
> var square = x => x * x; undefined > square(10) 100 > var add = (a, b) => a + b; undefined > add(1, 2) 3 > (x => x * x)(3) 9 > ((a, b) => a + b)(10, 20) 30 > var adder = x => y => x + y undefined > var add10 = adder(10) undefined > add10(20) 30 > adder(10)(20) 30
当然ですが、アロー関数はクロージャとしても使用することができます。
JavaScript の場合、関数の中で this を使用することができます。たとえば、object.method() のように関数 (メソッド) を呼び出した場合、this には object がセットされます。関数呼び出しでも this を参照することができますが、その値は大域変数を格納しているオブジェクト (グローバルオブジェクト) になります。
> var x = 100 undefined > function foo() { return this.x; } undefined > foo() 100 > var obj = {x: 123} undefined > obj.f = foo [Function: foo] > obj.f() 123
foo() と呼び出すと、this はグローバルオブジェクトになるので、x の値は 100 になります。obj を生成して、関数 foo() をプロパティ f にセットします。obj.f() を呼び出すと this の値は obj になるので、x は 123 になります。
call() や apply() を使うと、this の値を変更することができます。
> var oops = {x: "oops!"} undefined > foo.call(oops) 'oops!' > foo.call(obj) 123 > foo.call() 100 > foo.call(null) 100 > foo.call(undefined) 100
foo.call(oops) とすると、this の値は oops になるので oops! と表示されます。obj を渡すと 123 になり、引数無し (または null や undefined) だとグローバルな関数呼び出しになります。
new を使うと新しいオブジェクトが関数の this に渡されます。
> function bar() { this.x = 1.2345; } undefined > new bar() bar { x: 1.2345 } > bar() undefined > x 1.2345
new を付けずに bar() を呼び出すと this の値はグローバル変数になるので、変数 x の値を書き換えることになります。
局所関数の場合、匿名関数とアロー関数では this の値が異なるので注意が必要です。匿名関数の場合、this の値はグローバルオブジェクトになるので、外側の関数の this を参照することはできません。逆に、アロー関数は this をレキシカルスコープで管理するので、外側の関数の this を参照することができます。
> function baz() { ... var f1 = function() { console.log(this.x); }; ... var f2 = () => { console.log(this.x); }; ... f1(); ... f2(); ... } undefined > var x = 100 undefined > var obj = {x: 123} undefined > baz.call(obj) 100 123 undefined
匿名関数 f1 は大域変数 x の値 100 を表示しますが、アロー関数は baz() の this の値 (obj) を参照するので、obj のプロパティ x の値 123 を表示します。
グローバルな環境でアロー関数を定義すると、this の値はグローバルオブジェクトになります。この場合、メソッド形式の呼び出しでも、call() や apply() を使っても this の値はグローバルオブジェクトのままです。
> var func = () => { console.log(this.x); } undefined > func() 100 undefined > obj.f = func [Function: func] > obj.f() 100 undefined > func.call(obj) 100 undefined
詳細な説明はリファレンス this | JavaScript MDN をお読みくださいませ。
仮引数の個数よりも多くの値を受け取りたい場合は、仮引数の前に ... を付けます。これを「可変長引数」といい、... を「スプレッド演算子」と呼びます。仮引数に入りきらない値は、配列に格納されて可変長引数に渡されます。これで可変個の引数を受け取る関数を定義することができます。簡単な例を示しましょう。
> function foo(a, ...args) { console.log(a); console.log(args); } undefined > foo(1) 1 [] undefined > foo(1, 2) 1 [ 2 ] undefined > foo(1, 2, 3) 1 [ 2, 3 ] undefined > function foo0(...args) { ... console.log(args); ... } undefined > foo0() [] undefined > foo0(1) [ 1 ] undefined > foo0(1, 2, 3) [ 1, 2, 3 ] undefined
可変長引数は通常の仮引数よりも後ろに定義します。関数 foo() は通常の引数が一つしかありません。foo(1) と呼び出すと、引数 a に 1 がセットされます。実引数はもうないので、仮引数 args には空の配列が渡されます。次に foo(1, 2) と呼び出すと、実引数 2 が配列に格納されて仮引数 args に渡されます。同様に、foo(1, 2, 3) は 2 と 3 が配列に格納されて仮引数 args に渡されます。
関数 foo0() は、0 個以上の引数を受け取る関数、つまり、引数があってもなくてもどちらでも動作します。この場合、仮引数は args だけになります。実引数がない場合、引数 args には空の配列 [ ] が渡されます。もし、複数の引数があれば、それらを配列にまとめて仮引数 args に渡します。
配列に格納されたデータを関数に渡す場合、要素を取り出す処理をいちいちプログラムするのは面倒です。この場合、実引数の前に ... を付けると、配列を展開して仮引数に要素を渡すことができます。
> function foo(a, b, c) { console.log([a,b,c]); } undefined > var ary = [1, 2, 3] undefined > foo(..ary) ... > foo(...ary) [ 1, 2, 3 ] undefined > foo(10, ...[20, 30]) [ 10, 20, 30 ] undefined > [...ary, 4, 5, 6] [ 1, 2, 3, 4, 5, 6 ]
変数 ary には配列 [1, 2, 3] が格納されています。要素を関数 foo() の引数に渡す場合、...ary のように ... を付けて foo() に渡します。すると、配列が展開されて引数 a, b, c に要素 1, 2, 3 が渡されます。配列の前に直接 ... を付けても大丈夫です。また、配列を生成する [ ] の中でも配列を展開することができます。
ES2015 では関数の引数にデフォルトの値を設定することができます。これを「デフォルト引数」といいます。値は = で指定します。簡単な例を示しましょう。
> function foo(a, b = 10, c = 100) { console.log([a,b,c]); } undefined > foo(1) [ 1, 10, 100 ] undefined > foo(1, 2) [ 1, 2, 100 ] undefined > foo(1, 2, 3) [ 1, 2, 3 ] undefined
関数 foo() の引数 a は通常の引数で、引数 b と c がデフォルト値を指定した引数です。デフォルト引数は通常の引数の後ろに定義してください。foo() を呼び出すとき、引数 a には値を渡さないといけませんが、引数 b と c の値は省略することができます。このとき、使用される値がデフォルト値です。
たとえば、foo(1) と呼び出すと [ 1, 10, 100 ] と表示され、引数 b と c の値はデフォルト値が使用されていることがわかります。foo(1, 2) と呼び出すと、引数 b の値はデフォルト値ではなく、実引数 2 が b の値になります。同様に、foo(1, 2, 3) と呼び出すと、仮引数 c の値は実引数 3 になるので [ 1, 2, 3 ] と表示されます。
「分割代入 (Destructuring assignment)」は、配列またはオブジェクトからデータを取り出して別々の変数に代入するための構文です。JavaScript | MDN では分割と訳していますが、Lisp 好きなユーザならば「分配」のほうがしっくりくるかもしれません。
配列の分割代入は代入演算子 = の左辺式に角カッコを使い、その中に変数を指定します。
var [変数1, 変数2, ..., 変数N] = [値1, 値2, ..., 値N]; または var 変数1, 変数2, ..., 変数N; [変数1, 変数2, ..., 変数N] = [値1, 値2, ..., 値N];
配列の分割代入は他のスクリプト言語、たとえば Ruby の多重代入とよく似ています。簡単な例を示しましょう。
> var [a, b] = [100, 200] undefined > a 100 > b 200 > [a, b] = [b, a] [ 200, 100 ] > a 200 > b 100
変数 a と b に 100 と 200 を代入しています。a と b の値を交換することも分割代入を使えば簡単に行うことができます。
> [a, b] = [1, 2, 3] [ 1, 2, 3 ] > a 1 > b 2 > [a, b] = [10] [ 10 ] > a 10 > b undefined
右辺の要素数が代入先よりも多い場合、残りの要素は無視されます。最初の例では、a と b に 1 と 2 が代入されますが、要素 3 は捨てられます。逆に、代入先の変数が多い場合、残った変数には undefined がセットされます。[a, b] = [10] の場合、a には 10 が代入されて、b の値は undefined になります。
分割代入は関数のデフォルト引数のようにデフォルト値を設定することができます。
> [a, b = 123] = [100] [ 100 ] > a 100 > b 123 > [a, b = 123] = [100, 200] [ 100, 200 ] > a 100 > b 200
分割代入したとき、値が undefined になった変数でデフォルト値が設定される場合は、その値が使用されます。
関数の可変長引数と同じく、配列の分割代入でもスプレッド演算子 (...) を使用することができます。
> var [x, y, ...z] = [1, 2, 3, 4, 5] undefined > x 1 > y 2 > z [ 3, 4, 5 ] > var [x, y, ...z] = [1, [2, 3, 4, 5]] undefined > x 1 > y [ 2, 3, 4, 5 ] > z [] > var [x, y, ...z] = [1, ...[2, 3, 4, 5]] undefined > x 1 > y 2 > z [ 3, 4, 5 ]
角カッコは入れ子になってもかまいません。この機能は関数型言語のパターンマッチングや Common Lisp の defmacro にある機能「分配 (destructuring)」とよく似ています。
> var [x, [y, z]] = [1, [2, 3, 4, 5]] undefined > x 1 > y 2 > z 3 > var [x, [y, ...z]] = [1, [2, 3, 4, 5]] undefined > x 1 > y 2 > z [ 3, 4, 5 ]
右辺と左辺で配列の構造が合わないとエラーになります。
> var [x, [y, ...z]] = [1, 2, [3, 4, 5]] TypeError: undefined is not a function
オブジェクトの分割代入は代入演算子 = の左辺式に { ... } を使い、その中に変数を指定します。
var {名前1, 名前2, ..., 名前N} = {名前1: 値1, 名前2: 値2, ..., 名前N: 値N}; または var 名前1, 名前2, ..., 名前N; ({名前1, 名前2, ..., 名前N} = {名前1: 値1, 名前2: 値2, ..., 名前N: 値N});
オブジェクトの分割代入では、オブジェクトのプロパティ名と同じ名前の変数にプロパティの値がセットされます。後者の場合、カッコで囲まないと動作しません。ご注意くださいませ。
簡単な実行例を示します。
> var {a, b} = {a: 10, b: 20} undefined > a 10 > b 20 > ({b, a} = {a: 100, b: 200}) { a: 100, b: 200 } > a 100 > b 200
オブジェクトの分割代入でもデフォルト値を指定することができます。
> ({a, b} = {a: 1000}) { a: 1000 } > a 1000 > b undefined > ({a, b = 2000} = {a: 1000}) { a: 1000 } > a 1000 > b 2000
プロパティ名とは異なる変数に値を代入することもできます。その場合は左辺式でプロパティの値に代入先の変数名を指定します。
> var x, y undefined > ({a: x, b: y} = {a: 1234, b: 5678}) { a: 1234, b: 5678 } > x 1234 > y 5678
それから、オブジェクトの分割代入は入れ子にすることができ、配列の分割代入と混在してもかまいません。詳細は JavaScript | MDN の 分割代入 をお読みくださいませ。
シンボル (Symbol) は ES2015 で追加された新しいデータ型で、プロパティーなどの識別子として使用することができます。シンボルは関数 Symbol() で生成します。
Symbol("名前")
引数の名前は省略することができます。Symbol() はユニークなシンボル型のデータを返します。引数の名前が同じでも異なるシンボルが生成されます。
> Symbol() Symbol() > var foo = Symbol("foo") undefined > foo Symbol(foo) > foo === Symbol("foo") false > Symbol("foo") === Symbol("foo") false > foo === foo true
グローバルな環境でシンボルを共有したい場合は Symbol.for() を使います。
Symbol.for(string)
Symbol.for() は名前が string のシンボルを生成します。既に同じ名前のシンボルが生成されていれば、そのシンボルを返します [*1]。
> var bar = Symbol.for("bar") undefined > bar Symbol(bar) > bar === Symbol.for("bar") true
グローバルな環境にシンボルが登録されているかチェックする関数が Symbol.keyFor() です。
Symbol.keyFor(symbol)
> Symbol.keyFor(foo) undefined > Symbol.keyFor(bar) 'bar'
見つからない場合は undefined を、見つけた場合はシンボルの名前を文字列で返します。
シンボルをオブジェクトのプロパティとして使用するときは、object[symbol] や {[symbol]: 123} のように角カッコを使ってください。object.name は object["name"] と同じなので、この構文でシンボルを使うことはできません。
> var foo = Symbol("foo") undefined > var obj = {} undefined > obj[foo] = 123 123 > obj[foo] 123 > obj {} > obj.foo = 1.2345 1.2345 > obj.foo 1.2345 > obj["foo"] 1.2345 > obj { foo: 1.2345 } > var bar = Symbol("bar") undefined > var obj1 = {[foo]: 10, [bar]: 20} undefined > obj1[foo] 10 > obj1[bar] 20 > for (var x in obj) { console.log(x); console.log(obj[x]); } foo 1.2345 undefined
プロパティがシンボル (Symbol プロパティ) の場合、for...in 構文でそれを取り出すことはできません。Symbol プロパティを求めるには Object.getOwnPropertySymbols() を使います。
> Object.getOwnPropertySymbols(obj1) [ Symbol(foo), Symbol(bar) ]
ES2015 からメソッドを簡単に定義するための構文が導入されました。
旧 { name: function(...) { .... }、... } 新 { name(...) { ... }, ...} { [symbol](...) { ... }, ...}
プロパティを省略して名前付きの関数を定義すると、関数名がプロパティになります。プロパティにシンボル (symbol) を使いたい場合は、角カッコを使って関数名を [symbol] と指定してください。
簡単な実行例を示しましょう。
> var foo = Symbol("foo") undefined > var obj = { foo() { console.log("string foo"); }, ... [foo]() { console.log("symbol foo"); } ... } undefined > obj.foo() string foo undefined > obj[foo]() symbol foo undefined
角カッコはシンボルだけではなく、その中でプロパティ名を計算で求めることもできます。
> var obj = { ... ["foo" + 1]() { console.log("foo" + 1); }, ... ["bar" + 10]() { console.log("bar" + 10); } ... } undefined > obj.foo1() foo1 undefined > obj.bar10() bar10 undefined
getter と setter はメソッドで、プロパティのアクセスと同じ構文で呼び出すことができます。つまり、obj.prop のときは getter が呼び出され、obj.prop = value のときは setter が呼び出されます。
getter と setter の定義は簡単で、名前の前にキーワード get, set を付けるだけです。
{ get name() {...} } { get [expr]() {...} } { set name(value) { ... } } { set [expr](value) { ... } }
簡単な例を示しましょう。
> var obj = { ... get foo() { return this._foo; }, ... set foo(val) { this._foo = val; } ... } undefined > obj.foo = 100 100 > obj.foo 100 > obj { foo: [Getter/Setter], _foo: 100 }
getter を定義する場合、getter の名前と実際のプロパティ名を同じにすると、無限の再帰呼び出しになってしまうので、値を格納するプロパティは _foo としました。
setter と getter を削除する場合は delete 文を使います。delete はオブジェクトから指定したプロパティを削除します。
> delete obj.foo true > obj { _foo: 100 }
「イテレータ (iterator)」はコレクションの要素を順番にアクセスするための機能です。イテレータは「反復子」と訳されることがありますが、最近は訳さずにそのまま使われることが多いようです。JavaScript にはいろいろなコレクションが用意されていますが、for...in 文で取り出すことができるのはプロパティだけです。ES2015 から導入されたイテレータを使うと、コレクションの要素を順番に取り出していくことができます。
JavaScript では、要素を順番に取り出すメソッド next() を持つオブジェクトを「イテレータ」と呼びます。next() の仕様を示します。
iterator.next() => {value: 要素, done: 真偽値}
next() は引数無しで呼び出して、返り値はプロパティ value と done を持つオブジェクトです。通常、value にはコレクションの要素がセットされます。要素がない場合、done は true がセットされ、value は省略されるか undefined がセットされます。
簡単な例を示しましょう。
リスト : 配列のイテレータ function makeIter(ary) { return { idx: 0, next() { if (ary.length == this.idx) { return {value: undefined, done: true}; } else { return {value: ary[this.idx++], done: false}; } } } }
関数 makeIter() は配列 ary のイテレータを返します。処理は簡単で、next() は idx の位置にある要素を返して、idx を +1 するだけです。idx が配列の最後に到達したら、done を true にセットしたオブジェクトを返します。
それでは実行してみましょう。
> var iter = makeIter([1,2,3,4]) undefined > iter.next() { value: 1, done: false } > iter.next() { value: 2, done: false } > iter.next() { value: 3, done: false } > iter.next() { value: 4, done: false } > iter.next() { value: undefined, done: true }
イテレータを自分で作るのはけっこう面倒ですが、ES2015 で導入された「ジェネレータ (Generator)」を使うと、もっと簡単にイテレータを作ることができます。
function* name(仮引数, ...) { 処理; ... }
function の後ろに * を付けると、その関数は「ジェネレータ関数」になります。ジェネレータ関数はイテレータを返しますが、処理はすぐに実行されません。イテレータのメソッド next() を実行すると、ジェネレータ関数の実行が開始されます。そして、ジェネレータ関数の処理中で yield 文があると、そこでジェネレータ関数の実行を中断し、その引数を呼び出し元の next() に返します。つまり、next() を実行するたびにジェネレータ関数の実行が再開され、yield 文で実行を中断して値を next() に返すわけです。
簡単な例を示しましょう。
> function* foogen() { ... console.log("foo start"); ... yield 1; ... console.log("foo 1 end"); ... yield 2; ... console.log("foo 2 end"); ... yield 3; ... console.log("foo 3 end"); ... } undefined > var foo = foogen() undefined > foo.next() foo start { value: 1, done: false } > foo.next() foo 1 end { value: 2, done: false } > foo.next() foo 2 end { value: 3, done: false } > foo.next() foo 3 end { value: undefined, done: true } > foo.next() { value: undefined, done: true }
next() を呼び出すたびに yield 文の引数を順番に取得していることがわかります。たとえば、配列のジェネレータは次のように簡単に定義することができます。
> function* makeGen(ary){ ... for (let i = 0; i < ary.length; i++) yield ary[i]; ... } undefined > var gen = makeGen([1, 2, 3, 4, 5]) undefined > gen.next() { value: 1, done: false } > gen.next() { value: 2, done: false } > gen.next() { value: 3, done: false } > gen.next() { value: 4, done: false } > gen.next() { value: 5, done: false } > gen.next() { value: undefined, done: true }
ジェネレータ関数から他のジェネレータ関数を呼び出すときは yiled* 文を使います。
> function* genbar(ary1, ary2) { ... yield* makeGen(ary1); ... yield* makeGen(ary2); ... } undefined > var bar = genbar([1, 2, 3], [4, 5]); undefined > bar.next() { value: 1, done: false } > bar.next() { value: 2, done: false } > bar.next() { value: 3, done: false } > bar.next() { value: 4, done: false } > bar.next() { value: 5, done: false } > bar.next() { value: undefined, done: true }
ES2015 では匿名のジェネレータ関数も定義することができます。
> var genfunc = function*() { yield 1; yield 2; yield 3; } undefined > var gen = genfunc() undefined > gen.next(); { value: 1, done: false } > gen.next(); { value: 2, done: false } > gen.next(); { value: 3, done: false } > gen.next(); { value: undefined, done: true }
ES2015 では、プロパティ [Symbol.iterator] を持ったオブジェクトを「iterable オブジェクト」といいます。Symbol.iterator はあらかじめグローバルに定義されたシンボルです。マニュアルでは @@iterator と表記されることがあります。@@iterator は関数で、返り値はイテレータです。配列などの基本的なコレクションは iterable オブジェクトです。
> var ary = [1, 2, 3, 4, 5] undefined > var gen = ary[Symbol.iterator]() undefined > gen.next() { value: 1, done: false } > gen.next() { value: 2, done: false } > gen.next() { value: 3, done: false } > gen.next() { value: 4, done: false } > gen.next() { value: 5, done: false } > gen.next() { value: undefined, done: true }
iterable オブジェクトは、構文 for...of を使って要素を順番に取り出すことができます。
for (変数 of iterable) { ... }
> for (let x of ary) console.log(x) 1 2 3 4 5 undefined
オブジェクトのプロパティ [Symbol.iterator] にジェネレータ関数をセットすれば、そのオブジェクトは iterable になります。
> var obj = {} undefined > obj[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; } [Function] > for (let x of obj) console.log(x) 1 2 3 undefined
上記のプログラムは次のように記述することもできます。
リスト ; iterable オブジェクトの定義 var obj = { *[Symbol.iterator]() { yeild 1; yield 2; yield 3; } }
iterable オブジェクトはスプレッド演算子で展開することができます。
> [...obj] [ 1, 2, 3 ] > [...obj, 4, 5, 6] [ 1, 2, 3, 4, 5, 6 ]
「テンプレート文字列 (Template literal)」はバッククォート ( ` ) で囲まれた文字列で、${...} で変数や式を展開することができます。
> var x = "foo" undefined > `hello ${x}!` 'hello foo!' > var a = 123 undefined > var b = 456 undefined > `a + b = ${a + b}` 'a + b = 579'
テンプレート文字列は複数行に渡って文字列を記述することができます。つまり、改行文字がそのまま含まれます。もちろん \n や \t などエスケープシーケンスも使うことができます。
> var s = `hello world ... hello, foo` undefined > console.log(s) hello world hello, foo undefined > s 'hello world\nhello, foo' > var s1 = `hello\tworld` undefined > console.log(s1) hello world undefined
タグ付きテンプレート文字列は、テンプレート文字列を ${...} の結果とそれ以外の文字列に分けて、それをタグ名と同じ名前の関数に渡して実行します。
tag_name `...`
関数の仕様を示します。
tag_name(strings, ...values) `abc${n}def${m}ghi` => strings[0] = "abc" strings[1] = "def" strings[2] = "ghi" values[0] = ${n} の結果 values[1] = ${m} の結果
関数の返り値は文字列以外でもかまいません。簡単な実行例を示します。
> function foo_tag(strings, ...values) { ... console.log(strings); ... console.log(values); ... return "oops!"; ... } undefined > var x = 100 undefined > var y = 200 undefined > foo_tag`x + y = ${x + y}` [ 'x + y = ', '' ] [ 300 ] 'oops' > foo_tag`x + y = ${x + y}, x * y = ${x * y}` [ 'x + y = ', ', x * y = ', '' ] [ 300, 20000 ] 'oops'
引数の strings にはエスケープシーケンスなどの処理をした文字列が格納されますが、入力された生の文字列は、引数 strings のプロパティ raw から求めることができます。
> function bar_tag(strings, ...values) { ... console.log(strings.raw); ... console.log(strings); ... console.log(values); ... return "oops!"; ... } undefined > bar_tag`hello\tworld\n` [ 'hello\\tworld\\n' ] [ 'hello\tworld\n' ] [] 'oops!'
関数 String.raw`...` は ${...} を処理をした結果と生の文字列を連結して返します。
> var x = 123 undefined > var y = 456 undefined > String.raw`hello${x}\t\tworld${y}\n` 'hello123\\t\\tworld456\\n'