【STEP.25】
JavaScriptの関数の使い方をマスターしよう!

関数とは

与えられた入力(パラメーター)に基づいてなんらかの処理を行い、その結果を返す仕組みを関数といいます。

JavaScriptはデフォルトでも多くの関数を提供していますが、アプリ開発者が自分で関数を定義することもできます。

これを標準の関数と区別して、ユーザー定義関数といいます。

関数を定義する【function命令/Functionコンストラクター/関数リテラル】

ユーザー定義関数を作成するには、大きく以下の方法があります。

  1. function命令
  2. Functionコンストラクター
  3. 関数リテラル
  4. アロー関数ES2015

アロー関数を除く、それぞれのアプローチで、ユーザー定義関数getSquareAreaを定義したのが以下のコードです。

アロー関数に関しては、次章で解説しています。

なお、getSquareArea関数は、四角形の面積を求めることを想定したユーザ定義関数です。

// function命令
function getSquareArea (width, height) {
             関数名       仮引数
  return width * height;
     関数本体(戻り値)
}
	
// Functionコンストラクター
let getSquareArea = new Function(
        関数名
  'width', 'height', 'return width * height;');
       仮引数            関数本体(戻り値)

// 関数リテラル
let getSquareArea =
        関数名
  function (width, height) { return width * height; };
               仮引数           関数本体(戻り値)	
	
console.log(getSquareArea(2,3));
// 結果:6

関数名は、識別子の条件を満たさなければなりません。

「識別子の条件を満たす」とは、次に掲げる4つの要件を全て満たしていることをいいます。

  1. 1文字目はアルファベット、アンダースコア(_)、ドル記号($)のいずれか
  2. 2文字目以降は、1文字目で利用できる文字か、数字のいずれか
  3. 大文字/小文字は区別
  4. JavaScriptで意味を持つ予約語でないこと

(4)のJavaScriptの予約語とは、次に掲げるものをいいます。

【JavaScriptの予約語(はStrictモードの場合だけ)】
breakcasecatchconstcontinuedebugger
defaultdeletedoelseenumexport
extendsfalsefinallyforfunctionif
implementsimportininstanceofinterfacelet
newnullpackageprivateprotectedpublic
returnstaticsuperswitchthisthrow
truetrytypeofvarvoidwhile
withyield

また、構文規則ではありませんが、その関数がどのような役割を担うものであるかわかるように、「動詞+名詞」の形式で命令するのが通常です。

仮引数は、ユーザー定義関数の中で参照できる変数です。

ユーザー定義関数を呼び出す際に、呼び出し元から値を引き渡すために利用します。

仮引数が複数ある場合にはカンマ(,)区切りで列挙します。

戻り値は、関数で行った結果です。

return命令を呼び出すことで、呼び出し元に値を返すことができます。

戻り値が不要な場合は、return命令は省略してもかまいません(その場合は、関数の戻り値はundefinedと見なされます)。

定義済みの関数を呼び出すには、「関数名(実引数,...)」とします。

上のgetSquareArea関数の例であれば、「getSquareArea(2,3)」のようにして呼び出します。

実引数は、仮引数に引き渡す値です。

実引数と仮引数をまとめて引数と総称します。

MEMO
関数リテラル
関数リテラルの構文は、function命令によく似ていますが、次の点で異なります。

  • function命令は、関数getSquareAreaを直接定義
  • 関数リテラルは、名前のない関数を定義したうえで、変数getSquareAreaに格納
関数リテラルは、宣言したその時点では名前を持たないことから、匿名関数(無名関数)と呼ぶこともあります。

関数を定義する【アロー関数】

アロー関数の構文は次の通りです。

アロー関数

(args,...) => {...statements...}
args       引数
statements 関数本体 

functionというキーワードがなくなり、代わりに引数と関数本体を「=>」(Arrow/矢印の意味)でつないでいることから、アロー関数と呼ばれます。

次のgetSquareArea関数は、四角形の面積を求めることを想定したユーザー定義関数で、アロー関数を用いて表現されています。

let getSquareArea = (width, height) => {
  return width * height;
};

アロー関数をシンプルに表現する

アロー関数は特定の条件下でよりシンプルに表現できます。

関数本体が1文しかない場合

関数本体が1文であれば、ブロックを表す「{...}」は省略可能です。

また、文の戻り値がそのまま関数の戻り値と見なされるので、returnも省略できます。

よって、上記のgetSquareArea関数は、以下のように書き替えが可能です。

let getSquareArea = (width, height) => width * height;

引数が1個の場合

引数が1個であれば、引数をくくる丸カッコを省略できます。

たとえば以下は円を求めるgetCircleArea関数の例です。

let getCircleArea = radius => radius * radius * Math.PI;

ただし、引数がない場合には、カッコは省略できません。

以下のように空のカッコを置きます。

let current = () => console.log(new Date());

その他にも、アロー関数ではthisを固定できるというメリットもあります。

ES2015以降を許容できる環境では、関数リテラルよりもアロー関数を優先して利用すべきです。

オブジェクトリテラルが戻り値となる場合の注意点

アロー関数でオブジェクトリテラルを返す場合には、注意が必要です。

たとえば以下のコードは不可です。

let func = () => { name: '横浜流星' };

オブジェクトリテラルを意図した「{...}」が、この文脈では関数ブロックと見なされてしまうからです(「name:」はラベル、「'横浜流星'」は文字列リテラルと見なされます)。

このような問題を避けるには、以下のように戻り値のオブジェクトリテラルを丸カッコでくくる必要があります。

let func = () => ({ name: '横浜流星' });

関数定義における4つの注意点

return命令の直後で改行しない

JavaScriptでは、基本的にセミコロンで文末を認識します。

ただし、セミコロンを省略した場合にも、JavaScriptは適宜、前後の文脈から文の末尾を判断してくれます。

つまり、JavaScriptでは文末にセミコロンをつけることが好ましいが、「必須ではない」のです。

このような寛容さは、基本的にJavaScriptのハードルを下げる要因ともなるのですが、時として、要らぬ混乱をもたらす原因にもなります。

以下の例を見てみましょう。

function square (height, width) {
  return
  height * width;
}

console.log(square(2,3));

これは呼び出し元に四角形の面積、つまり、式「height * width」の結果を戻すことを意図したコードですが、実際に呼び出してみると、意図したような結果は得られません。

実行結果は、undefinedとなるはずです。

これが、寛容なJavaScriptが余計なお節介をした副産物です。

ソースコードの太字部分(ソースコード2~3行目)は、実際にはセミコロンが自動的に補完されて、

return;  セミコロンを補完
height * width;

のように解釈されているのです。

結果、square関数は戻り値として既定のundefinedを返し、後続の式「height * width」は無視されているのです。

意図したいように動作させるには、以下のように、途中の改行を削除する必要があります。

return height * width;

このような、「エラーは発生しないが、意図した動作もしない」というケースは、後々のデバッグを難しくする原因でもあります。

もちろん、この程度の短い式で途中に改行を入れることはないかもしれませんが、戻り値としてより長い式を指定している場合に、無意識に改行を加えてしまわないよう、十分に注意する必要があります。

MEMO
break、continue、throwなども注意
return命令と同じ理由から、以下のような文についても、途中で改行を含めてはいけません。
  • ラベル付きbreak/continue
  • throw
  • ++、--演算子(後置)

以上をまとめると、JavaScriptでは文の途中で改行できますが、無制限に改行すべきではありません。

一般的には、演算子、カンマ、左カッコの直後など、文の継続が明らかな箇所でのみ改行するのが安全です。

関数はデータ型を一種

他のプログラミング言語を学んだことがある方ならば、以下のコードが直感的に「間違いである」と感じるかもしれません。

let getTriangle = function (base, height) {
  return base * height / 2;
};
console.log(getTriangle(5,2));
// 結果:5
getTriangle = 0;
console.log(getTriangle);
// 結果:0

「関数と同名の変数が定義されたことに問題があるならば、(getTriangle = 0;)はエラーとなるはず」「関数をあたかも変数のように呼び出していることが問題ならば、(console.log(getTriangle);)で問題となるはず」と思うでしょう。

しかし、これはJavaScriptでは正しいコードです。

JavaScriptでは「関数はデータ型の一種」なのです。

ですから、getTriangle関数を定義することは、実は「getTriangleという変数に関数型のリテラルを格納する」ことと同意です。

したがって、「getTriangle = 0;」で変数getTriangleにあらためて数値型の値をセットしても間違いではありませんし、当然、数値型に書き換えられた変数を参照している「console.log(getTriangle);」のコードも正しいわけです。

関数のこの性質を利用して、以下のようなコードを記述することもできます。

let getTriangle = function(base, height) {
  return base * height / 2;
};
console.log(getTriangle);
function(base, height) {
  return base * height / 2;
}

ここでは、getTriangleを変数として参照しているので、getTriangleに格納された関数定義が、そのまま文字列として出力されているわけです(厳密にはFunctionオブジェクトのtoStringメソッドが呼び出されて文字列表現に変換されたものが出力されています)。

これが、関数を呼び出す際に引数がなくても丸カッコを省略できない理由でもあります。

丸カッコは「関数を実行する」という意味も持っているのです。

function命令は静的な構造を宣言する

「静的な構造」とは、コードを解析するタイミングで関数を登録するということです。

console.log(getSquareArea(2,3));
// 結果:6

function getSquareArea(width, height) {		
  return width * height;
}

console.log(getSquareArea(2,3));」の時点では getSquareArea関数は定義されていないにも関わらず、正しく実行できています。

コードを解析した(実行前の)タイミングで、getSquareArea関数がコード内の構造の一部として認識されているからです。

対して、上記のコードをFunctionコンストラクター、関数リテラルで置き換えた場合は、getSquareArea関数は認識されず、今度は実行時エラーとなります。

Functionコンストラクター/関数リテラルは実行時(代入時)に初めて評価されるからです。

原則としてFunctionコンストラクターは使わない

Functionコンストラクターは、引数と関数の本文を文字列として指定します。

文字列で指定できるということは、条件やユーザー入力に応じて、動的に関数の内容を変更できることを意味しますが、そうすべきではありません。

まず、実行時にコードの解析が介在するため、実行速度の低下を招きます。

そして、より深刻なことに、外部からの入力に応じて関数を作成した場合には、外部から任意のコードを実行できてしまう可能性があるためです(セキュリティ的なリスク)。

原則として、関数はアロー関数、function命令、関数リテラルのいずれかで定義すべきです。

関数リテラル/Functionコンストラクターのスコープの違い

関数リテラルとFunctionコンストラクターとでは、関数配下のスコープが異なります。

以下のサンプルを確認します。

let scope = 'グローバル';
		
function hoge () {
  let scope = 'ローカル';
		
  let literalFnc = function () { return scope; };
  console.log(literalFnc());
  // 結果:ローカル
		
  let conFnc = new Function('return scope;');
  console.log(conFnc());
  // 結果:グローバル
}

hoge();

関数リテラルで定義されたliteralFncFunctionコンストラクターで定義されたconFncいずれも、hoge関数内の配下で定義されているので、一見すると、いずれの変数 scopeも関数内の変数「let scope = 'ローカル';」を参照するように思います。

しかし、結果の通り、そのようにはなりません。

Functionコンストラクター配下は、常に関数外の変数を参照します。

一方、関数リテラルは文脈に応じて、その時々のスコープに応じた変数を参照します。

このようなわかりにくさも、Functionコンストラクターを利用すべきではない理由の一つです。

引数の既定値を設定する

引数の既定値を宣言するには、「仮引数 = 既定値」の形式で仮引数を宣言します。

たとえば以下は、四角形の面積を求めることを想定したgetSquareArea関数の引数widthheightに、それぞれ既定値として「1」を割り当てる例です。

function getSquareArea (width = 1, height = 1) {
  return width * height;
}
	
console.log(getSquareArea(2));
// 結果:2

既定値を利用する場合の注意点

既定値を利用する場合には、以下の点に注意する必要があります。

他の引数を参照するならば、自分より前の引数だけ

既定値には、リテラルだけではなく他の引数、関数/式を渡すこともできます。

function getSquareArea (width, height = width) {
  return width * height;
}
	
console.log(getSquareArea(2,3));
// 結果:6
console.log(getSquareArea(2));
// 結果:4

ただし、他の引数を既定値とする場合、参照できるのは、自分より前に定義されたものだけです。

たとえば、以下のようなコードは不可です。

function getSquareArea (width = height, height = 1) {
  return width * height;
}
	
console.log(getSquareArea());
// 結果:Cannot access 'height' before initialization

既定値が適用されるのは、引数が渡されなかった場合

たとえば空文字列、nullのように、意味的に空を表すような値でも、それが明示的に渡された場合には、次のソースコードのように既定値は適用されません。

function getSquareArea (width = 1, height = 1) {
  return width * height;
}
	
console.log(getSquareArea(10, null));
// 結果:0

上記の場合は、「10 × null」で結果は「0」になります。

ただし、undefinedだけは例外で、引数が渡されなかったものと見なされます。

function getSquareArea (width = 1, height = 1) {
  return width * height;
}
	
console.log(getSquareArea(10, undefined));
// 結果:10

上記の場合は、「10 × 1」で結果は「10」となります。

既定値のない引数を、既定値のある引数の後ろに置かない

たとえば以下のようなコードはエラーではありませんが推奨されません。

function getSquareArea (width = 1, height) {...}

というのも、このようなgetSquareArea関数に対して、引数heightだけを指定したつもりで、以下のようなコードを書いてもうまく動作しないからです。

console.log(getSquareArea(10));
// 結果:NaN

10は引数widthに渡され、引数heightは既定値を持たないのでundefinedです。

結果、「10 × undefined」で「NaN」となります。

つまり、このような関数で、引数heightだけに値を渡すことはできません(実質、引数widthは必須です)。

このようなコードは誤解を招きやすいので、原則として避けるべきです。

必須の引数をチェックする

JavaScriptでは、必須の引数という考え方がありません。

既定値のない引数に値が渡されなくても、undefined(未定義)となるだけです。

しかし、既定値構文を利用することで、仮想的に必須の引数を表現できます。

// 無条件に例外をスローするrequired関数
function required (msg) {
  throw new Error(msg);
}
	
function getSquareArea (
  width = required('幅が指定されていません。'),
  height = required('高さが指定されていません。')
){
  return width * height;
}
	
console.log(getSquareArea(2));
// 結果:高さが指定されていません。

例外を投げるだけのrequired関数を用意しておき、これを「必須引数の既定値として」指定しています。

これによって、引数が指定されなかった場合に、required関数が実行(=例外がスロー)されます。

名前付き引数を受け取る

名前付き引数とは、ハッシュ(オブジェクト)形式で関数に引き渡せる引数のことです。

以下に、「値だけを渡す引数」と「名前付き引数」とを並べて比較してみます。

showPanel関数は、指定されたパネルを表示することを想定したユーザー定義関数です。

// 値だけを渡す引数
showPanel('content.html', 200, 300, true, true, false);

// 名前付き引数
showPanel({
  path: 'content.html', // パネルとして表示する内容
  height: 200, // 高さ
  width: 300, // 幅
  resizable: true, // リサイズ可能か
  draggable: true, // ドラッグ可能か
  modeless: false // モードレスパネルか
});

名前付き引数には、以下のようなメリットがあります。

  • 引数の意味が分かりやすい(特に型の同じ値が並んでいる場合に区別しやすい)
  • 引数の順序を自由に変更できる
  • すべての引数が任意となる

名前付き引数の効果は、一部の引数を省略した場合に如実に表れます。

// 値だけを渡す引数
showPanel('content.html', 200, 300, true, true, false);
            後ろの引数を指定する場合、下線部分を省略できない
// 名前付き引数
showPanel({
  path: 'content.html',
  modeless: false 必要な引数だけを指定できる
});

反面、引数そのものの数が少なく、それらのほとんどすべてが必須である場合には、名前を明記しなければならないので、コードが冗長となります。

任意の引数が大量に用意されているウィジェット系のライブラリでよく利用される記法です。

具体的な実装例を見てみます。

名前付き引数を実装するには、分割代入を利用します。

// 名前付き引数
function showPanel({
  path: 'content.html', // パネルとして表示する内容
  height: 200, // 高さ
  width: 300, // 幅
  resizable: true, // リサイズ可能か
  draggable: true, // ドラッグ可能か
  modeless: false // モードレスパネルか
}){
  console.log('path:' + path);
  console.log('height:' + height);
  console.log('width:' + width);
  console.log('resizable:' + resizable);
  console.log('draggable:' + draggable);
  console.log('modeless:' + modeless);
  ...具体的なパネル表示のコード...
};

仮引数を「{プロパティ名:既定値,...}」としているのが、分割代入の構文です。

これによって、渡されたオブジェクトの内容を個々の引数に分解しています。

関数の配下では、pathheightwidth…といったプロパティが、個別の変数として参照できる点に注目です。

引数のオブジェクトから特定のプロパティだけを取り出す

分割代入を利用することで、引数に渡したオブジェクトから特定のプロパティだけを取り出すことも可能です。

// 引数経由で渡されたオブジェクトからnameプロパティだけを取得
function print ({ name }) {
  console.log(name);
}

let actor = {
  name: '横浜流星',
  age: 23,
  blood: 'O型'
}

print(actor);
// 結果:横浜流星

print関数で受け取っているのは、あくまでactorオブジェクトですが、関数側は、そのnameプロパティだけを分割代入によって取り出しています(function print({name}){...}/ソースコード2~4行目)。

このようなテクニックを利用すれば、複数のプロパティが必要になった場合も、呼び出し側は個々のプロパティを意識しなくて済みます。

オブジェクトを丸ごと渡せばいいからです。

また、あとで関数が参照するプロパティが変化した場合にも、呼び出し側のコードには影響が及びません。

可変長引数の関数を定義する

可変長引数の関数とは、引数の個数があらかじめ決まっていない(=個数が変動する)関数のことです。

たとえばMath.maxminメソッドなども可変長引数の関数の一種です。

console.log(Math.max(85, 625, 227, 1204));
// 結果:1204(引数4個)
	
console.log(Math.min(402, 35, 2, 169, 9));
// 結果:2(引数5個)

不特定多数の引数を受け取って、その中から最大値/最小値を取り出しています。

可変長引数の関数とは、「宣言時に引数の数を確定できない関数」とも言えます。

可変長引数の関数(基本的な例)

まずは、簡単な例から見てみます。

product関数は、与えられた任意の個数の数値から、その総積を求めることを想定したユーザー定義関数です。

function product (...nums) {
  let result = 1;
  // 可変長引数の内容を順に掛け合わせ
  for (let num of nums) {
    result *= num
  }
  return result;
}
			
console.log(product(3, 4, 5));
// 結果:60

可変長引数を表すには、仮引数の前に「...」(ピリオド3個)を付与するだけです(ソースコード1行目)。

これによって、渡された任意個数の引数を配列としてまとめて取得できます。

あとは、配列から値を取り出していくだけです。

この例では、for…of命令で引数リストnumsからすべての引数を取り出し、すべて掛け合わせたうえで、その結果を戻り値として返しています。

固定引数/可変長引数が混在した例

可変長引数と固定引数(=仮引数が明示された引数)とが混在した関数も可能です。

たとえば以下は、引数formatに含まれるプレイスホルダー「{0}」、「{1}」、「{2}」…を、第2引数以降で置き換えるsprintf関数の例です。

function sprintf (format, ...args) {
  for (let i = 0; i < args.length; i++) {
    let pattern = new RegExp('\\{' + (i) +'\\}','gi');
    format = format.replace(pattern, args[i]);
  }
  return format;
}

console.log(sprintf('{0}は俳優です。{1}です。','横浜流星','23歳'));
// 結果:横浜流星は俳優です。23歳です。

可変長引数と、通常引数を同居させる場合、注意すべき点は

可変長引数は引数リストの末尾に置くこと

だけです。

さもないと、すべての引数が可変長引数に吸収されてしまい、以降の引数が無意味になってしまうからです(実際、可変長引数を末尾以外に置いた場合にはエラーになります)。

ここでは、forループで可変長引数を取り出し、対応するプレイスホルダ(「{0},{1}...{n}」)と順に置き換えています。

上記の例であれば、引数formatもまとめて可変長引数として扱うこともできます。(構文上は、ありとあらゆる引数を可変長引数にできます。)

しかし、これはコードの可読性という意味で、推奨されません。(「args[0]」よりも、「format」という名前のほうが中身は類推し易いはずです。)

基本は、できるだけ普通の引数で表記し、可変長引数は、個数を特定できない、やむを得ない場合にだけ使うべきです。

可変長引数に配列を渡す

たとえば可変長引数の関数であるMath.minメソッドに、そのまま配列を渡すことはできません。

console.log(Math.min([402, 35, 2, 169, 9]));
// 結果:NaN

このような場合は、配列を渡す際に...演算子(スプレッド演算子)を介することで、個々の値に分解できます。

console.log(Math.min(...[402, 35, 2, 169, 9]));
// 結果:2

可変長引数の関数に渡すべき値が、あらかじめ配列として用意されているような状況では、...演算子を利用することでシンプルに値を受け渡しできます。

...演算子を利用できないES2015より前の環境では、applyメソッドを利用します。

これでMath.minメソッドに第2引数(配列)を引き渡す、という意味になります。

console.log(Math.min.apply(null, [402, 35, 2, 169, 9]));
// 結果:2

関数を引数として渡す【高階関数】

JavaScriptでは、関数もまたデータ型の一種です。

データ型であるとは、数値や文字列と同じく、関数もまた、関数の引数として渡したり、戻り値として返したりといったことが可能ということです。

そして、関数を受け渡すための関数のことを高階関数といいます。

たとえば、以下は指定された処理にかかる時間を測定するbenchmark関数の例です。

function benchmark (proc) {
  let start = new Date(); // 開始時間
  proc();
  let end = new Date(); // 終了時間
  return end.getTime() - start.getTime(); // 計測時間
}
	
// 指定された匿名関数の処理時間を計測
console.log(
  benchmark(function () {
    let x = 15;
    for (let i = 0; i < 10000000; i++) {
      x *= i;
    }
  })
);
// 結果:51(その時々で結果は変化します。)

benchmark関数は、引数としてベンチマークする処理を関数procとして受け取ります。

引数として渡された関数は、普通の関数と同じく、「proc()」のように呼び出せます(必要に応じて引数を渡してもかまいません)。

このように、呼び出し先の関数の中で呼び出される関数のことを、コールバック関数と呼びます。

あとで呼び出される(コールバックされる)べき処理、という意味です。

コールバック関数は差し替え自由なので、高階関数では機能の外枠だけを用意しておいて、詳細な機能は関数を利用する側で決められるのです。

イベント処理、非同期処理のためのライブラリでよく利用される記法です。

関数から複数の値を返す

戻り値を配列/オブジェクトに束ねて返すようにします。

たとえば以下は、与えられた引数から合計値と平均値を求めるgetSumAverage関数の例です。

function getSumAverage (...values) {
  let result = 0;
  // 可変長引数の内容を順に足し込む
  for (let value of values) {
    result += value;
  }
  return [result, result/values.length];
}
	
let [sum, average] = getSumAverage(3, 4, 5, 6);
console.log(sum);
// 結果:18
console.log(average);
// 結果:4.5

getSumAverage関数からの戻り値は、そのまま配列として受け取ってもかまいませんが、コードの可読性を鑑みれば、分割代入で個々の変数に振り分けるべきです。(「result[0]」よりもsumのような名前のほうが内容を把握しやすいはずです。)

let [sum, average] = getSumAverage(3,4,5,6);」では、合計値/平均値をそれぞれsumaverageに代入していますが、もし片方の値しか使わないのであれば、以下のように表すこともできます。

let [, average] = getSumAverage(3, 4, 5, 6);

この場合は、平均値だけが変数averageに割り当てられ、合計値は切り捨てられます。

thisを固定して関数/メソッドを呼び出す【call/apply】

Function(関数)オブジェクトのcallapplyメソッドは、いずれもその関数を呼び出します。

ただし、呼び出しに際して、関数配下のthisキーワードを指定できるという特徴があります。

call/applyメソッド

func.call(that [,arg1 [,arg2 [,...]]])
func.apply(that [,args])
that          関数の中でthisキーワードが指すもの
arg1,arg2...  関数に渡す引数
args          関数に渡す引数(配列)

callapplyメソッドの違いは、関数funcに渡す引数の指定方法だけです。

前者は可変長引数として渡すのに対して、後者は配列として渡します。

以下に、具体的な例を見てみます。

引数thatに異なるオブジェクトを渡すことで、関数配下のthisの内容(ここでは出力される「this.name」)が変化することを確認して下さい。

var name = 'Global';
let data1 = { name:'data1' };
let data2 = { name:'data2' };
	
function showName () {
  console.log(this.name);
}
	
showName.call(null);
// 結果:Global
showName.call(data1);
// 結果:data1
showName.call(data2);
// 結果:data2

引数thatnullを渡した場合、callメソッドは暗黙的にグローバルオブジェクトが渡されたものと見なします。

var name = 'Global';」でletではなくvarを利用しているのは、varが関数の外でグローバル変数(=グローバルオブジェクトのプロパティ)を生成するのに対して、letはあくまでブロックスコープの変数を生成しているだけだからです。

よって、「var name = 'Global';」をletで置き換えた場合には、「showName.call(null);」は、undefinedを返します。

配列ライクなオブジェクトを配列に変換する

ES2015より前では、配列ライクなオブジェクト(たとえばarguments)をArrayに変換するような用途でも利用できます。

たとえば以下は、与えられた引数(arguments)を「」区切りで連結したものを表示するshowArgs関数の例です。

function showArgs () {
  var args = Array.prototype.slice.call(arguments);	
  return args.join('・');
}

console.log(showArgs('松','竹','梅'));
// 結果:松・竹・梅

var args = Array.prototype.slice.call(arguments);」で「argumentsオブジェクトをthisとして、Arrayオブジェクトのsliceメソッドを呼び出しなさい」という意味になります。

sliceメソッドは、引数を省略すると、元の配列をそのまま返すので、これで、argumentsオブジェクトを配列に変換できるわけです。

確かに、「return args.join('・');」でもArrayオブジェクトのメソッドであるjoinメソッドを呼び出せていることが確認できます。

for...ofで列挙可能な値を生成する【ジェネレーター】

ジェネレーターという仕組みを利用します。

たとえば以下のサンプルは、引数startend-1の範囲の値を返すためのrange関数の例です。

function* range (start, end) {
  for (let i = start; i < end; i++) {
    yield i;
  }
}
// 10~19の範囲の値を取得
for (let num of range(10,20)) {
  console.log(num);
}
// 結果:10、11、12、13、14、15、16、17、18、19

ジェネレーターはほぼ普通の関数と同じ構文で表現できますが、以下の点が異なります。

  • function*で定義(functionの後ろに「*」)
  • 値を返すのはyield命令

yieldは、returnと同じく、呼び出し元に値を返します。

しかし、return命令がそのまま関数を終えるのに対して、yield命令は一時的に停止するだけです。

つまり、再度呼び出すことで、以前の位置から処理を再開できます。

return命令とyield命令の違い

この例であれば、yield時点でカウンター変数iの値を維持するので、10,11,12...と値がカウントアップします。

ジェネレーターの仕組み

ジェネレーターの挙動をより明快にするために、もう一つ、より簡単なジェネレーターとしてmygenを作成します。

mygen関数は、文字列「おはよう」「こんにちは」「おやすみ」を順に返します。

function* mygen () {
  yield 'おはよう';
  yield 'こんにちは';
  yield 'おやすみ';
}

let itr = mygen();
console.log(itr.next());
// 結果:{value: "おはよう", done: false}
console.log(itr.next());
// 結果:{value: "こんにちは", done: false}
console.log(itr.next());
// 結果:{value: "おやすみ", done: false}
console.log(itr.next());
// 結果:{value: undefined, done: true}

ジェネレーター関数の戻り値は、Generatorオブジェクトとなる点に注意が必要です。(yieldからの戻り値を返すわけではありません!!!)

Generatorはイテレーター(iterator)として動作するオブジェクトです。

確かにnextメソッドを呼び出せる点、複数回の呼び出しによって、戻り値の内容が変化していることが確認できます。

ジェネレーターから別のジェネレーターを呼び出す【yield*】

yield*(末尾に「*」)を利用します。

たとえば以下は、mygenジェネレーターからsubgenジェネレーターを呼び出す例です。

function* mygen () {
  yield '春が来た';
  yield* subgen();
  yield '花が咲く';
}

function* subgen () {
  yield '山に来た';
  yield '里に来た';
  yield '野にも来た';
}

for (let str of mygen()) {
  console.log(str);
}
// 結果:春が来た、山に来た、里に来た、野にも来た、花が咲く

yield*で別のジェネレーター(サブジェネレーター)が呼び出された場合には、サブジェネレーターによる列挙が完了したところで、メインジェネレーターの後続のyield命令が実行されます。

テンプレート文字列への変数埋め込み時に処理を挟み込む【タグ付きテンプレート】

タグ付きテンプレートという仕組みを利用することで、テンプレート文字列に値を反映させる前に、値を加工できます。

たとえば以下は、変数を埋め込む際に、「<」「>」などのHTML予約文字を「&lt;」「&gt;」に置き換えてから反映させる例です。(このような変換のことをHTMLエスケープといいます。)

// 文字列をHTMLエスケープ
function htmlEscape (str) {
  if (!str) { return ''; }
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/'/g, '&#39;')
    .replace(/"/g, '&quot;');
}
// 分解されたstrs/varsを交互に連結(varsはエスケープ処理)
function e (strs, ...vars) {
  let result = '';
  for (let i = 0; i < strs.length; i++) {
    result += strs[i] + htmlEscape(vars[i]);
  }
  return result;
}

let name = '<"Pochi" & \'Tama\'>';
console.log(e`はじめまして、${name}!`);
// 結果:はじめまして、&lt;&quot;Pochi&quot; &amp; &#39;Tama&#39;gt;!

タグ付きテンプレートとは、その実、単なる関数呼び出しにすぎません。( e`はじめまして、${name}!` /ソースコード21行目)

タグ付きテンプレート

func`string`
func     関数名
string   文字列

ただし、タグ付きテンプレートによって呼び出される関数(function e(strs, ...vars) {...}/ソースコード12~18行目)は、以下の条件を満たしている必要があります。

  • 引数として「テンプレート文字列を分解したもの(配列)」「埋め込まれた変数(可変長引数)を受け取ること」
  • 戻り値として、最終的に加工された結果文字列を返すこと

上記のソースコードであれば、引数には以下のような値がセットされます。

  • strs: 「"はじめまして、", "!",
  • vars: 「"<\"Pochi\" & 'Tama'>"

あとは、これをforループで交互に出力し、文字列を組み立てなおすだけです。

その際、変数の値はあらかじめ用意しておいたhtmlEscape関数でエスケープ処理します。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です