JavaScript

(初心者向け) JavaScript の関数 (ES6対応)

概要

けっこう盛りだくさんの内容になってしまいましたが、初心者としては「書ける」ことよりも「読める」ことを重点にするとよいと思います。

JavaScript の関数は、function キーワードで定義します。function キーワードを使った関数の定義は2通りあります。function の代わりに => を使って関数を定義することもできます。

次のサンプルで myFunc1, myFunc2, myFunc3 は同じ機能を持つ関数です。

function myFunc1(x, y) {
  return x * y;
}

const myFunc2 = function(x, y) {
  return x * y;
}

const myFunc3 = (x, y) => {
  return x * y;
}

(参考) 関数は Function オブジェクトなので、次のようにしても作成できます。

const myFunc4 = new Function(["x", "y"], "return x * y;");

関数の Strict モード

古いコードを流用する場合、Strict モードを適用すると、エラーになったり、動作が変わったりと都合がよくないことが起こります。そのような場合は、コード全体は非 Strict モードにして、新しく追加した機能を関数にまとめ、その関数を Strict モードにすれば、不都合が起こらず安全なコードを提供できる可能性が高くなります。

関数レベルで Strict モードにするには、関数の最初の行に 'use strict'; という行を追加します。これにより、内部関数も含めて関数レベルで Strict モードになります。

/* 関数の Strict モード */

// 最初の状態を表示
try {
   NaN = null;
   console.log('Non Strict Mode.');
}
catch (e) {
   console.log('Strict Mode.');
}

// 非 Strict モード関数
function NonStrictFunc() {
    try {
        NaN = null;
        console.log('Non Strict Mode.');
    }
    catch (e) {
        console.log('Strict Mode.');
    }
}

// Strict モード関数
function StrictFunc() {
  'use strict';
  try {
    NaN = null;
    console.log('Non Strict Mode.');
  }
  catch (e) {
    console.log('Strict Mode.');
  }
}

// 非 Strict モード関数をコール
NonStrictFunc();

// Strict モード関数をコール
StrictFunc();

実行例
Non Strict Mode.
Non Strict Mode.
Strict Mode.

変数の巻き上げ (Hoisting)

関数内で変数を宣言するとき、コードの先頭でなく、途中 (使用する直前など) で変数を宣言してもエラーにはなりません。このようなケースでは、変数は関数の先頭で宣言されたものとみなされます。(実行前に内部的に変数宣言が先頭へ移動される)

次のサンプルで巻き上げに直接関連している部分は var lo2; のところです。この行は関数のコードの中間で宣言されていますが、巻き上げにより先頭部分で宣言されているものとみなされます。このサンプルでは関数を Strict モードにしていますが、非 Strict モードなら var lo2; がなくても動作します。逆に、この宣言がないとエラーになります。

/* 変数の巻き上げ Hoisting */

var g = 'GLOBALな変数';

function testHoist() {
    'use strict';
    lo2 = "関数内の変数 (2)";
    console.log(g); // グローバルな変数 g は有効
    console.log(lo1); // ローカルな変数 lo1 は無効 (undefined)
    var lo1 = "関数内の変数 (1)";
    console.log(lo1); // lo1: ここでは有効になる。
    var lo2;  // この宣言は巻き上げられる。(関数の先頭で宣言されているものとみなされる)
    console.log(lo2); // lo2: 有効になっている。
}

testHoist();

実行例
GLOBALな変数
undefined
関数内の変数 (1)
関数内の変数 (2)

次のサンプルでは g というグローバル変数と関数内でローカル変数 g もを定義している。ローカル変数 g の宣言は関数の中間で行っているが、宣言と代入を行っている。この宣言部分は関数の先頭へ巻き上げられる。よって、最初の console.log(g) は undefined と表示される。

'use strict';

var g = 0;

function test() {
    console.log(g);  // undefined
    var g = 100;   // var g が巻き上げられる。
    console.log(g);  // 100
}

test();

実行例
undefined
100

Arrow 関数

無名関数 (匿名関数) を作る場合も従来の書き方だと function キーワードが必要でしたが、=> を使うことにより、より簡潔に書けるようになりました。書き方ですが、C# などと似ているので、他の言語を知っている人はわかりやすいかもです。

一般的な書き方は下のようにします。

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression

パラメータが1個の場合はカッコはなくても構いません。

(singleParam) => { statements }
singleParam => { statements }
singleParam => expression

オブジェクトのリテラルを返す場合は、次のような簡潔な書き方ができます。

params => ({foo: bar})

Rest パラメータと Default パラメータもサポートされています。

(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements }

Default パラメータの Destructuring assignment (分割代入) もサポートされています。(実際に実装されている環境はまだまだ少ないようなので、使わないほうがよさそうです)

例:
([x, y] = [1, 2]) => { ... }

サンプル (特別なケースのサンプルは後述)

'use strict';

// Arrow 関数の基本形
const basicFunc1 = (x, y) => {
  let v = 2 * x + 1;
  return v;
}

// 式だけの場合は {} を省略できる。
const basicFunc2 = (x, y) => 2 * x + 1;

console.log(basicFunc1(3, 4));
console.log(basicFunc2(3, 4));

// パラメータ1つの場合、より省略が可能。
const singleFunc1 = (x) => { return x * x; };
const singleFunc2 = x => { return x * x; };  // パラメータを囲む () は不要。
const singleFunc3 = x => x * x;

console.log(singleFunc1(5));
console.log(singleFunc2(5));
console.log(singleFunc3(5));

// 1つのオブジェクトを返す場合も簡単に書ける。
const objectFunc = (s) => ({large: s.toUpperCase(), small: s.toLowerCase()});
console.log(objectFunc("Tom"));

実行例
7
7
25
25
25
{ large: 'TOM', small: 'tom' }

パラメータの既定値を指定する

パラメータの既定値を指定すると、すべてのパラメータを与えなくても関数を実行できます。

'use strict';
// 2つめのパラメータの既定値を 0 とした関数
function defaultParams(a, b = 0) {
    return a * a + b * b;
}

// Arrow 関数バージョン
const defaultParamsArrow = (a, b = 0) => a * a + b * b;

console.log(defaultParams(2, 3));
// パラメータ b を省略すると b = 0 とみなされる。
console.log(defaultParams(2));

// Arrow 関数でも結果は同じになる。
console.log(defaultParamsArrow(2, 3));
console.log(defaultParamsArrow(2));

実行例
13
4
13
4

可変数のパラメータを使う

可変数のパラメータを持つ関数を定義することもできます。パラメータの並びの最後を ...rest とすると可変数であるとみなされます。関数内では、特殊な変数 arguments に配列としてパラメータが格納されています。Perl をご存知の方なら、Perl のパラメータの渡し方と同様です。

書き方はこれ以外にもいろいろあります。詳しくは Rest parameters を参照

'use strict';

// 1個以上のパラメータを持つ Rest 関数
function restParam(a, ...rest) {
    let sum = 0;
    // パラメータは arguments に入っている。
    for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

// Arrow 関数バージョン (2017-11 時点で動作しなかった)
const restParamArrow = (a, ...rest) => {
    let sum = 0;
    // パラメータは arguments に入っている。
    for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;   
}

// パラメータを3個指定して実行。
console.log(restParam(1, 2, 3));
console.log(restParamArrow(1, 2, 3));

実行例
6
0 (エラー)

引数の参照渡し

JavaScript にはパラメータを参照渡しする構文はありません。しかし、オブジェクトは参照渡しなので、関数の中でオブジェクトのメンバーの値を変更すると、関数から戻ったときにメンバーの値は変更されています。

これを意識して使えば、パラメータの参照渡しと同じことができますが、知らずに使うとバグの原因になります。

'use strict';

// オブジェクトのメンバーを 0 にする関数
function testRefParam(o) {
    o.x = 0;
    o.y = 0;
}

var o = {x:1, y:2};
testRefParam(o);
// o.x, o.y は 0 になる。
console.log(o);

実行例
{ x: 0, y: 0 }

「参照渡し」と用語にこだわる人がいたので、関数呼び出しのメカニズムを少し語ってみました。(CとC#でJavaScriptでないのですがスマソ)。
https://qiita.com/tadnakam/items/a2b47c21e65a0b015b7e

再帰呼び出し

JavaScript でも関数の中で自分自身を呼び出すことが可能です。これを再帰呼び出しと言いますが、メモリを大量に消費するので注意が必要です。

'use strict';

// 再帰呼び出し関数
function P(n) {
    if (n == 0) {
        return 1;
    }
    else {
        // P() を呼び出す。
        return n * P(n - 1);
    }
}

console.log(P(4));

実行例
24

内部関数

関数の中に関数を定義できます。その関数内でしか使わない機能は内部関数にするとよいかもしれません。

function fx() {

    function fxx() {
        console.log('fxx()');
    }

    fxx();
}

fx();  // fxx と表示される。

次のサンプルのようにすると、関数自体を1つのプログラムとして呼び出すことができます。

(function() {
    function fa() {
        return 'fa';
    }

    console.log('function test');
    console.log(fa());  // fa と表示される。
})();

Function のメソッドを使う

関数は Function のインスタンスなので、Function のメソッドを使うことができます。次のサンプルは Function.call() メソッドを使って、無名関数を実行しています。

'use strict';

(function() {
    console.log('called');
}).call(this);

function* とは

function の後に * が付いた関数は「ジェネレータ関数」と呼ばれます。普通の関数は return で値を返しますが、ジェネレータ関数は yield で値を返します。

ジェネレータ関数では、yield があると、直ちに関数を終了して呼び出し元に戻ります。そして yield で指定した値がその時の関数値になります。ジェネレータ関数は動的なシーケンスを作るときなどに使用します。

詳しくは function* を参照。

'use strict';

// ジェネレータ関数の定義
function* myGenerator() {
    var i = 0;
    while (i < 10) {
        yield i + 16;
        i++;
    }
}

// ジェネレータ関数の使用
for (let x of myGenerator()) {
    console.log(x);
}

実行例
16
17
18
19
20
21
22
23
24
25

async function とは

JavaScript では非同期動作をコールバックで実現しますが、複雑な処理だとコールバックのネストが深くなってコードの可読性が非常に悪くなります。

この問題を解決するために導入されたのが、async function です。詳しくは async function を参照。

次のコードは、developer.mozilla.org のサイトに出ているサンプルです。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  var a = resolveAfter2Seconds(20);
  var b = resolveAfter2Seconds(30);
  return x + await a + await b;
}

add1(10).then(v => {
  console.log(v);  // prints 60 after 2 seconds.
});

async function add2(x) {
  var a = await resolveAfter2Seconds(20);
  var b = await resolveAfter2Seconds(30);
  return x + a + b;
}

add2(10).then(v => {
  console.log(v);  // prints 60 after 4 seconds.
});

実行結果は次のようになります。
60
60

終わり