12. ECMAScript 6 における呼び出し可能エンティティ
目次
本書をサポートしてください:購入(PDF、EPUB、MOBI)または寄付
(広告、ブロックしないでください。)

12. ECMAScript 6 における呼び出し可能エンティティ

この章では、ES6 で呼び出し可能なエンティティ(関数呼び出し、メソッド呼び出しなどを介して)を適切に使用する方法について説明します。



12.1 概要

ES5 では、単一の構成要素である(従来の)関数が 3 つの役割を果たしていました。

ES6 では、より専門化が進んでいます。3 つの役割は、現在次のように処理されます。関数定義とクラス定義に関する限り、定義は宣言または式のいずれかです。

特にコールバックの場合、アロー関数は便利です。これは、周囲のスコープの this をシャドウしないためです。

長いコールバックやスタンドアロン関数には、従来の関数でもかまいません。一部の API では、this を暗黙的なパラメーターとして使用します。その場合、従来の関数を使用するしかありません。

注意:

これらのエンティティは動作が異なりますが(後述)、すべて関数です。例えば

> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'

12.2 ES6 での呼び出し方法

一部の呼び出しはどこでも実行できますが、他の呼び出しは特定の場所に制限されます。

12.2.1 どこでも実行できる呼び出し

ES6 では、次の 3 種類の呼び出しをどこでも実行できます。

12.2.2 super を介した呼び出しは特定の場所に限定される

super キーワードを介して 2 種類の呼び出しを行うことができます。これらの使用は特定の場所に限定されます。

12.2.3 メソッドではない関数とメソッド

メソッドではない関数とメソッドの違いは、ECMAScript 6 でより顕著になっています。両方に対して特別なエンティティがあり、それらだけができることがあります。

12.3 呼び出し可能エンティティの使用に関する推奨事項

このセクションでは、呼び出し可能エンティティを使用するためのヒントを示します。どのエンティティを使用するのが最適か、など。

12.3.1 コールバックとしてアロー関数を優先する

コールバックとして、アロー関数には従来の関数よりも 2 つの利点があります。

12.3.1.1 問題:暗黙的なパラメーターとしての this

残念ながら、一部の JavaScript API では、コールバックの暗黙的な引数として this を使用するため、アロー関数を使用できません。例:B 行の this は、A 行の関数の暗黙的な引数です。

beforeEach(function () { // (A)
    this.addMatchers({ // (B)
        toBeInRange: function (start, end) {  
            ···
        }  
    });  
});  

このパターンは明示性が低く、アロー関数の使用を妨げます。

12.3.1.2 解決策 1:API を変更する

これは簡単に修正できますが、API を変更する必要があります。

beforeEach(api => {
    api.addMatchers({
        toBeInRange(start, end) {
            ···
        }
    });
});

API を暗黙的なパラメーター this から明示的なパラメーター api に変えました。私はこの種の明示性が好きです。

12.3.1.3 解決策 2:何らかの他の方法で this の値にアクセスする

一部の API では、this の値を取得する別の方法があります。たとえば、次のコードでは this を使用しています。

var $button = $('#myButton');
$button.on('click', function () {
    this.classList.toggle('clicked');
});

ただし、イベントのターゲットには event.target を介してアクセスすることもできます。

var $button = $('#myButton');
$button.on('click', event => {
    event.target.classList.toggle('clicked');
});

12.3.2 スタンドアロン関数として関数宣言を優先する

(コールバックではなく)スタンドアロン関数として、関数宣言を優先します。

function foo(arg1, arg2) {
    ···
}

利点は次のとおりです。

1 つの注意点があります。通常、スタンドアロン関数で this は必要ありません。それを使用する場合は、周囲のスコープ(たとえば、スタンドアロン関数を含むメソッド)の this にアクセスする必要があります。残念ながら、関数宣言ではそれができません。独自の this があり、周囲のスコープの this をシャドウします。したがって、リンターに、関数宣言内の this について警告させたい場合があります。

スタンドアロン関数のもう 1 つのオプションは、アロー関数を変数に割り当てることです。this の問題は、字句であるため回避されます。

const foo = (arg1, arg2) => {
    ···
};

12.3.3 メソッドにはメソッド定義を優先する

メソッド定義は、super を使用するメソッドを作成する唯一の方法です。これらはオブジェクトリテラルとクラス(メソッドを定義する唯一の方法)では当然の選択肢ですが、既存のオブジェクトにメソッドを追加する場合はどうでしょうか?例:

MyClass.prototype.foo = function (arg1, arg2) {
    ···
};

以下は、ES6 で同じことを行う簡単な方法です(注意:Object.assign()super を使用したメソッドを適切に移動しません)。

Object.assign(MyClass.prototype, {
    foo(arg1, arg2) {
        ···
    }
});

詳細と注意点については、Object.assign() に関するセクションを参照してください。

12.3.4 メソッドとコールバック

通常、関数値プロパティはメソッド定義を介して作成する必要があります。ただし、アロー関数の方が適切な場合もあります。次の 2 つのサブセクションでは、いつ何を使用するかについて説明します。前者の方がメソッドを持つオブジェクトに適しており、後者の方がコールバックを持つオブジェクトに適しています。

12.3.4.1 プロパティがメソッドであるオブジェクト

プロパティが本当にメソッドである場合は、メソッド定義を介して関数値プロパティを作成します。これは、プロパティ値がオブジェクト(次の例の obj)およびその兄弟メソッドと密接に関連しており、周囲のスコープ(例の surroundingMethod())には関連がない場合に当てはまります。

メソッド定義では、プロパティ値の this は、メソッド呼び出しの レシーバー(例:メソッド呼び出しが obj.m(···) の場合は obj)です。

たとえば、WHATWG Streams API を次のように使用できます。

const surroundingObject = {
    surroundingMethod() {
        const obj = {
            data: 'abc',
            start(controller) {
                ···
                console.log(this.data); // abc (*)
                this.pull(); // (**)
                ···
            },
            pull() {
                ···
            },
            cancel() {
                ···
            },
        };
        const stream = new ReadableStream(obj);
    },
};

obj は、プロパティ startpull、および cancel が実際のメソッドであるオブジェクトです。したがって、これらのメソッドは this を使用してオブジェクトローカルの状態(* 行)にアクセスし、相互に呼び出すことができます(** 行)。

12.3.4.2 プロパティがコールバックであるオブジェクト

プロパティ値がコールバックである場合は、アロー関数を介して関数値プロパティを作成します。このようなコールバックは、オブジェクトに格納されている(例の obj)のではなく、周囲のスコープ(次の例の surroundingMethod())と密接に関連する傾向があります。

アロー関数の this は、周囲のスコープ(字句 this)の this です。アロー関数は優れたコールバックです。これは、コールバック(実際の、メソッドではない関数)で通常必要な動作であるためです。コールバックには、周囲のスコープの this をシャドウする独自の this を持たないようにする必要があります。

プロパティ startpull、および cancel がアロー関数の場合、surroundingMethod()(周囲のスコープ)の this を取得します。

const surroundingObject = {
    surroundingData: 'xyz',
    surroundingMethod() {
        const obj = {
            start: controller => {
                ···
                console.log(this.surroundingData); // xyz (*)
                ···
            },

            pull: () => {
                ···
            },

            cancel: () => {
                ···
            },
        };
        const stream = new ReadableStream(obj);
    },
};
const stream = new ReadableStream();

* 行の出力に驚いた場合は、次のコードを検討してください。

const obj = {
    foo: 123,
    bar() {
        const f = () => console.log(this.foo); // 123
        const o = {
            p: () => console.log(this.foo), // 123
        };
    },
}

メソッド bar() 内では、f の動作はすぐに理解できるはずです。o.p の動作はそれほど明白ではありませんが、f の動作と同じです。両方のアロー関数には、同じ周囲の字句スコープである bar() があります。後者のアロー関数がオブジェクトリテラルで囲まれているからといって、それが変わるわけではありません。

12.3.5 ES6 では IIFE を避ける

このセクションでは、ES6 で IIFE を避けるためのヒントを示します。

12.3.5.1 IIFEをブロックで置き換える

ES5では、変数をローカルに保ちたい場合、IIFEを使用する必要がありました。

(function () {  // open IIFE
    var tmp = ···;
    ···
}());  // close IIFE

console.log(tmp); // ReferenceError

ECMAScript 6では、ブロックとletまたはconst宣言を使用するだけで済みます。

{  // open block
    let tmp = ···;
    ···
}  // close block

console.log(tmp); // ReferenceError
12.3.5.2 IIFEをモジュールで置き換える

ライブラリ(RequireJS、browserify、webpackなど)を介してモジュールを使用しないECMAScript 5のコードでは、リビーリングモジュールパターンが一般的であり、IIFEに基づいています。その利点は、何がパブリックで何がプライベートかを明確に区別することです。

var my_module = (function () {
    // Module-private variable:
    var countInvocations = 0;

    function myFunc(x) {
        countInvocations++;
        ···
    }

    // Exported by module:
    return {
        myFunc: myFunc
    };
}());

このモジュールパターンはグローバル変数を生成し、次のように使用されます。

my_module.myFunc(33);

ECMAScript 6では、モジュールが組み込まれているため、モジュールを採用する障壁が低くなります。

// my_module.js

// Module-private variable:
let countInvocations = 0;

export function myFunc(x) {
    countInvocations++;
    ···
}

このモジュールはグローバル変数を生成せず、次のように使用されます。

import { myFunc } from 'my_module.js';

myFunc(33);
12.3.5.3 即時実行アロー関数

ES6でも即時実行関数が必要なユースケースが1つあります。つまり、単一の式ではなく、一連のステートメントによってのみ結果を生成できる場合があります。これらのステートメントをインライン化したい場合は、関数を即時実行する必要があります。ES6では、即時実行アロー関数を使用すると、数文字を節約できます。

const SENTENCE = 'How are you?';
const REVERSED_SENTENCE = (() => {
    // Iteration over the string gives us code points
    // (better for reversal than characters)
    const arr = [...SENTENCE];
    arr.reverse();
    return arr.join('');
})();

示されているように括弧で囲む必要があることに注意してください(括弧は、関数呼び出し全体ではなく、アロー関数の周りにあります)。詳細については、アロー関数の章で説明されています。

12.3.6 コンストラクターとしてクラスを使用する

ES5では、コンストラクター関数がオブジェクトのファクトリを作成する主流の方法でした(ただし、他にも多くの手法があり、一部はよりエレガントであると言えるでしょう)。ES6では、クラスがコンストラクター関数を実装する主流の方法です。いくつかのフレームワークは、カスタム継承APIの代替としてそれらをサポートしています。

12.4 ES6の呼び出し可能エンティティの詳細

このセクションでは、各ES6の呼び出し可能エンティティを詳細に説明する前に、チートシートから始めます。

12.4.1 チートシート:呼び出し可能エンティティ

12.4.1.1 呼び出し可能エンティティの動作と構造

エンティティによって生成された値の特性

  関数宣言/関数式 アロー クラス メソッド
関数呼び出し可能 ×
コンストラクター呼び出し可能 × ×
プロトタイプ F.p F.p SC F.p
プロパティprototype × ×

エンティティ全体の特性

  関数宣言 関数式 アロー クラス メソッド
巻き上げ     ×  
windowプロパティを作成(1)     ×  
内部名(2) ×   ×

エンティティの本体の特性

  関数宣言 関数式 アロー クラス(3) メソッド
this lex
new.target lex
super.prop × × lex
super() × × × ×

凡例–テーブルセル

凡例–脚注

ジェネレーター関数とメソッドはどうですか? これらは、2つの例外を除いて、ジェネレーターではない対応するものと同様に機能します。

12.4.1.2 thisのルール
  関数呼び出し メソッド呼び出し new
従来の関数(厳格) undefined レシーバー インスタンス
従来の関数(非厳格) window レシーバー インスタンス
ジェネレーター関数(厳格) undefined レシーバー TypeError
ジェネレーター関数(非厳格) window レシーバー TypeError
メソッド(厳格) undefined レシーバー TypeError
メソッド(非厳格) window レシーバー TypeError
ジェネレーターメソッド(厳格) undefined レシーバー TypeError
ジェネレーターメソッド(非厳格) window レシーバー TypeError
アロー関数(厳格&非厳格) レキシカル レキシカル TypeError
クラス(暗黙的に厳格) TypeError TypeError SCプロトコル

凡例–テーブルセル

12.4.2 従来の関数

これらは、ES5で知られている関数です。それらを作成する方法は2つあります。

thisのルール

12.4.3 ジェネレーター関数

ジェネレーター関数については、ジェネレーターの章で説明されています。構文は従来の関数に似ていますが、アスタリスクが追加されています。

thisのルールは次のとおりです。thisはジェネレーターオブジェクトを参照しないことに注意してください。

12.4.4 メソッド定義

メソッド定義は、オブジェクトリテラル内に記述できます。

const obj = {
    add(x, y) {
        return x + y;
    }, // comma is required
    sub(x, y) {
        return x - y;
    }, // comma is optional
};

また、クラス定義内にも記述できます。

class AddSub {
    add(x, y) {
        return x + y;
    } // no comma
    sub(x, y) {
        return x - y;
    } // no comma
}

ご覧のとおり、オブジェクトリテラルのメソッド定義はコンマで区切る必要がありますが、クラス定義ではメソッド定義間に区切り文字はありません。前者は、特にゲッターとセッターに関して、構文の一貫性を保つために必要です。

メソッド定義は、superを使用してスーパープロパティを参照できる唯一の場所です。superを使用するメソッド定義のみが、その機能に必要な内部プロパティ[[HomeObject]]を持つ関数を生成します(詳細については、クラスの章で説明されています)。

ルール

クラス定義内では、名前がconstructorのメソッドは、この章で後述するように特別です。

12.4.5 ジェネレーターメソッド定義

ジェネレーターメソッドについては、ジェネレーターの章で説明されています。構文はメソッド定義に似ていますが、アスタリスクが追加されています。

const obj = {
    * generatorMethod(···) {
        ···
    },
};
class MyClass {
    * generatorMethod(···) {
        ···
    }
}

ルール

12.4.6 アロー関数

アロー関数については、独自の章で説明されています。

const squares = [1,2,3].map(x => x * x);

次の変数は、アロー関数内でレキシカルです(周囲のスコープから取得されます)。

ルール

12.4.7 クラス

クラスについては、独自の章で説明されています。

// Base class: no `extends`
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

// This class is derived from `Point`
class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y);
        this.color = color;
    }
    toString() {
        return super.toString() + ' in ' + this.color;
    }
}

メソッドconstructorは、クラスの「実体」になるため、特別です。つまり、クラスはコンストラクター関数と非常に似ています。

> Point.prototype.constructor === Point
true

ルール

12.5 ES5とES6でのディスパッチされたメソッド呼び出しと直接メソッド呼び出し

JavaScriptには、メソッドを呼び出す方法が2つあります。

このセクションでは、これら2つがどのように機能し、ECMAScript 6でメソッドを直接呼び出すことがほとんどない理由について説明します。始める前に、プロトタイプチェーンの知識を更新しましょう。

12.5.1 背景: プロトタイプチェーン

JavaScriptの各オブジェクトは、実際には1つ以上のオブジェクトのチェーンであることを思い出してください。最初のオブジェクトは、後続のオブジェクトからプロパティを継承します。たとえば、配列['a', 'b']のプロトタイプチェーンは次のようになります。

  1. インスタンス。要素'a''b'を保持しています。
  2. Array.prototypeArrayコンストラクターによって提供されるプロパティ。
  3. Object.prototypeObjectコンストラクターによって提供されるプロパティ。
  4. null (チェーンの終端。実際にはメンバーではありません)。

チェーンは、Object.getPrototypeOf()で調べることができます。

> var arr = ['a', 'b'];
> var p = Object.getPrototypeOf;

> p(arr) === Array.prototype
true
> p(p(arr)) === Object.prototype
true
> p(p(p(arr)))
null

「早い」オブジェクトのプロパティは、「遅い」オブジェクトのプロパティを上書きします。たとえば、Array.prototypetoString()メソッドの配列固有のバージョンを提供し、Object.prototype.toString()を上書きします。

> var arr = ['a', 'b'];
> Object.getOwnPropertyNames(Array.prototype)
[ 'toString', 'join', 'pop', ··· ]
> arr.toString()
'a,b'

12.5.2 ディスパッチされたメソッド呼び出し

メソッド呼び出しarr.toString()を見ると、実際には2つのステップが実行されていることがわかります。

  1. ディスパッチ: arrのプロトタイプチェーンで、名前がtoStringである最初のプロパティの値を取得します。
  2. 呼び出し: 値を呼び出し、暗黙的なパラメータthisをメソッド呼び出しの*レシーバー*arrに設定します。

関数のcall()メソッドを使用することで、この2つのステップを明示的に行うことができます。

> var func = arr.toString; // dispatch
> func.call(arr) // direct call, providing a value for `this`
'a,b'

12.5.3 直接メソッド呼び出し

JavaScriptには、直接メソッド呼び出しを行う2つの方法があります。

メソッドcallとメソッドapplyは両方とも関数で呼び出されます。これらは、thisの値を指定するという点で、通常の関数呼び出しとは異なります。callは、個々のパラメータを介してメソッド呼び出しの引数を提供し、applyは、Arrayを介して引数を提供します。

ディスパッチされたメソッド呼び出しでは、レシーバーは2つの役割を果たします。メソッドを見つけるために使用され、暗黙的なパラメータになります。最初の役割の問題は、メソッドを呼び出すには、オブジェクトのプロトタイプチェーンにメソッドが含まれている必要があるということです。直接メソッド呼び出しでは、メソッドはどこからでも取得できます。これにより、別のオブジェクトからメソッドを借りることができます。たとえば、Object.prototype.toStringを借りて、配列arrtoStringの元の、オーバーライドされていない実装を適用できます。

> const arr = ['a','b','c'];
> Object.prototype.toString.call(arr)
'[object Array]'

配列版のtoString()は異なる結果を生成します。

> arr.toString() // dispatched
'a,b,c'
> Array.prototype.toString.call(arr); // direct
'a,b,c'

さまざまなオブジェクト(「それらの」コンストラクターのインスタンスだけでなく)で動作するメソッドは、*ジェネリック*と呼ばれます。*Speaking JavaScript*には、ジェネリックなメソッドのリストがあります。このリストには、ほとんどの配列メソッドと、Object.prototypeのすべてのメソッド(すべてのオブジェクトで動作する必要があるため、暗黙的にジェネリックです)が含まれています。

12.5.4 直接メソッド呼び出しのユースケース

このセクションでは、直接メソッド呼び出しのユースケースについて説明します。毎回、最初にES5でのユースケースを説明し、次にES6での変更点(直接メソッド呼び出しがほとんど必要なくなる場合)について説明します。

12.5.4.1 ES5: Arrayを介してメソッドにパラメータを渡す

一部の関数は複数の値を受け入れますが、パラメータごとに1つの値のみを受け入れます。Arrayを介して値を渡したい場合はどうすればよいでしょうか。

たとえば、push()を使用すると、複数の値を配列に破壊的に追加できます。

> var arr = ['a', 'b'];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]

ただし、配列全体を破壊的に追加することはできません。apply()を使用することで、その制限を回避できます。

> var arr = ['a', 'b'];
> Array.prototype.push.apply(arr, ['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]

同様に、Math.max()Math.min()は単一の値に対してのみ機能します。

> Math.max(-1, 7, 2)
7

apply()を使用すると、配列に対して使用できます。

> Math.max.apply(null, [-1, 7, 2])
7
12.5.4.2 ES6: スプレッド演算子(...)は、ほとんどの場合apply()に取って代わる

配列を引数に変えるためだけにapply()を介して直接メソッド呼び出しを行うのは面倒です。そのため、ECMAScript 6には、このためのスプレッド演算子(...)があります。これは、ディスパッチされたメソッド呼び出しでもこの機能を提供します。

> Math.max(...[-1, 7, 2])
7

別の例

> const arr = ['a', 'b'];
> arr.push(...['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]

おまけに、スプレッドはnew演算子でも機能します。

> new Date(...[2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET)

apply()newでは使用できないことに注意してください。上記の偉業は、ECMAScript 5で複雑な回避策によってのみ実現できます。

12.5.4.3 ES5: 配列のようなオブジェクトを配列に変換する

JavaScriptの一部のオブジェクトは*配列のような*オブジェクトであり、ほとんど配列ですが、配列メソッドはありません。2つの例を見てみましょう。

最初に、関数の特別な変数argumentsは配列のようです。これには、lengthと、要素へのインデックス付きアクセスがあります。

> var args = function () { return arguments }('a', 'b');
> args.length
2
> args[0]
'a'

ただし、argumentsArrayのインスタンスではなく、map()メソッドがありません。

> args instanceof Array
false
> args.map
undefined

次に、DOMメソッドdocument.querySelectorAll()NodeListのインスタンスを返します。

> document.querySelectorAll('a[href]') instanceof NodeList
true
> document.querySelectorAll('a[href]').map // no Array methods!
undefined

したがって、多くの複雑な操作では、最初に配列のようなオブジェクトを配列に変換する必要があります。これは、Array.prototype.slice()を使用して実現されます。このメソッドは、レシーバーの要素を新しい配列にコピーします。

> var arr = ['a', 'b'];
> arr.slice()
[ 'a', 'b' ]
> arr.slice() === arr
false

slice()を直接呼び出すと、NodeListを配列に変換できます。

var domLinks = document.querySelectorAll('a[href]');
var links = Array.prototype.slice.call(domLinks);
links.map(function (link) {
    return link.href;
});

また、argumentsを配列に変換できます。

function format(pattern) {
    // params start at arguments[1], skipping `pattern`
    var params = Array.prototype.slice.call(arguments, 1);
    return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
12.5.4.4 ES6: 配列のようなオブジェクトの負担が軽減される

一方、ECMAScript 6には、配列のようなオブジェクトを配列に変換するためのより簡単な方法であるArray.from()があります。

const domLinks = document.querySelectorAll('a[href]');
const links = Array.from(domLinks);
links.map(link => link.href);

一方、ECMAScript 6には*レストパラメータ*(3つのドットで宣言)があるため、配列のようなargumentsは必要ありません。

function format(pattern, ...params) {
    return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
12.5.4.5 ES5: hasOwnProperty()を安全に使用する

obj.hasOwnProperty('prop')は、objに*独自の*(継承されていない)プロパティpropがあるかどうかを通知します。

> var obj = { prop: 123 };

> obj.hasOwnProperty('prop')
true

> 'toString' in obj // inherited
true
> obj.hasOwnProperty('toString') // own
false

ただし、Object.prototype.hasOwnPropertyがオーバーライドされると、ディスパッチを介してhasOwnPropertyを呼び出すと正常に動作しなくなる可能性があります。

> var obj1 = { hasOwnProperty: 123 };
> obj1.hasOwnProperty('toString')
TypeError: Property 'hasOwnProperty' is not a function

Object.prototypeがオブジェクトのプロトタイプチェーンに含まれていない場合、ディスパッチを介してhasOwnPropertyを使用できない場合もあります。

> var obj2 = Object.create(null);
> obj2.hasOwnProperty('toString')
TypeError: Object has no method 'hasOwnProperty'

どちらの場合も、解決策はhasOwnPropertyを直接呼び出すことです。

> var obj1 = { hasOwnProperty: 123 };
> Object.prototype.hasOwnProperty.call(obj1, 'hasOwnProperty')
true

> var obj2 = Object.create(null);
> Object.prototype.hasOwnProperty.call(obj2, 'toString')
false
12.5.4.6 ES6: hasOwnProperty()の必要性が少ない

hasOwnProperty()は、主にオブジェクトを介してマップを実装するために使用されます。ありがたいことに、ECMAScript 6には組み込みのMapデータ構造があるため、hasOwnProperty()の必要性が少なくなります。

12.5.5 Object.prototypeArray.prototypeの省略形

空のオブジェクトリテラル(プロトタイプがObject.prototypeである)を介して、Object.prototypeのメソッドにアクセスできます。たとえば、次の2つの直接メソッド呼び出しは同等です。

Object.prototype.hasOwnProperty.call(obj, 'propKey')
{}.hasOwnProperty.call(obj, 'propKey')

同じトリックは、Array.prototypeにも適用できます。

Array.prototype.slice.call(arguments)
[].slice.call(arguments)

このパターンは非常に人気があります。長いバージョンほど明確に作成者の意図を反映していませんが、はるかに冗長ではありません。速度に関しては、2つのバージョン間に大きな違いはありません。

12.6 関数のnameプロパティ

関数のnameプロパティには、関数の名前が含まれています。

> function foo() {}
> foo.name
'foo'

このプロパティは、デバッグ(その値はスタックトレースに表示されます)および一部のメタプログラミングタスク(名前で関数を選択するなど)に役立ちます。

ECMAScript 6より前は、このプロパティはほとんどのエンジンですでにサポートされていました。ES6では、言語標準の一部になり、自動的に頻繁に設定されるようになります。

12.6.1 関数の名前を提供する構造

次のセクションでは、さまざまなプログラミング構造でnameが自動的に設定される方法について説明します。

12.6.1.1 変数宣言と代入

関数は、変数宣言を介して作成された場合、名前を取得します。

let func1 = function () {};
console.log(func1.name); // func1

const func2 = function () {};
console.log(func2.name); // func2

var func3 = function () {};
console.log(func3.name); // func3

しかし、通常の代入でも、nameは適切に設定されます。

let func4;
func4 = function () {};
console.log(func4.name); // func4

var func5;
func5 = function () {};
console.log(func5.name); // func5

名前に関して、アロー関数は匿名関数式のようなものです。

const func = () => {};
console.log(func.name); // func

今後は、匿名関数式が表示される場合はいつでも、アロー関数が同じように機能すると想定できます。

12.6.1.2 デフォルト値

関数がデフォルト値の場合、変数またはパラメータから名前を取得します。

let [func1 = function () {}] = [];
console.log(func1.name); // func1

let { f2: func2 = function () {} } = {};
console.log(func2.name); // func2

function g(func3 = function () {}) {
    return func3.name;
}
console.log(g()); // func3
12.6.1.3 名前付き関数定義

関数宣言と関数式は*関数定義*です。このシナリオは長い間サポートされています。名前付きの関数定義は、名前をnameプロパティに渡します。

たとえば、関数宣言。

function foo() {}
console.log(foo.name); // foo

名前付き関数式の名前もnameプロパティを設定します。

const bar = function baz() {};
console.log(bar.name); // baz

最初に来るため、関数式の名前bazは他の名前(たとえば、変数宣言を介して提供される名前bar)よりも優先されます。

ただし、ES5と同様に、関数式の名前は関数式内の変数にすぎません。

const bar = function baz() {
    console.log(baz.name); // baz
};
bar();
console.log(baz); // ReferenceError
12.6.1.4 オブジェクトリテラルのメソッド

関数がプロパティの値である場合、そのプロパティから名前を取得します。それがメソッド定義(A行)、従来のプロパティ定義(B行)、計算されたプロパティキーを持つプロパティ定義(C行)、またはプロパティ値の省略形(D行)のいずれで行われるかは関係ありません。

function func() {}
let obj = {
    m1() {}, // (A)
    m2: function () {}, // (B)
    ['m' + '3']: function () {}, // (C)
    func, // (D)
};
console.log(obj.m1.name); // m1
console.log(obj.m2.name); // m2
console.log(obj.m3.name); // m3
console.log(obj.func.name); // func

ゲッターの名前には'get'が接頭辞として付けられ、セッターの名前には'set'が接頭辞として付けられます。

let obj = {
    get foo() {},
    set bar(value) {},
};
let getter = Object.getOwnPropertyDescriptor(obj, 'foo').get;
console.log(getter.name); // 'get foo'

let setter = Object.getOwnPropertyDescriptor(obj, 'bar').set;
console.log(setter.name); // 'set bar'
12.6.1.5 クラス定義のメソッド

クラス定義でのメソッドの名前付けは、オブジェクトリテラルに似ています。

class C {
    m1() {}
    ['m' + '2']() {} // computed property key

    static classMethod() {}
}
console.log(C.prototype.m1.name); // m1
console.log(new C().m1.name); // m1

console.log(C.prototype.m2.name); // m2

console.log(C.classMethod.name); // classMethod

ゲッターとセッターには、それぞれ'get''set'の名前の接頭辞が再び付けられます。

class C {
    get foo() {}
    set bar(value) {}
}
let getter = Object.getOwnPropertyDescriptor(C.prototype, 'foo').get;
console.log(getter.name); // 'get foo'

let setter = Object.getOwnPropertyDescriptor(C.prototype, 'bar').set;
console.log(setter.name); // 'set bar'
12.6.1.6 キーがシンボルのメソッド

ES6では、メソッドのキーをシンボルにすることができます。このようなメソッドのnameプロパティは、依然として文字列です。

const key1 = Symbol('description');
const key2 = Symbol();

let obj = {
    [key1]() {},
    [key2]() {},
};
console.log(obj[key1].name); // '[description]'
console.log(obj[key2].name); // ''
12.6.1.7 クラス定義

クラス定義は関数を作成することを忘れないでください。これらの関数も、プロパティnameが正しく設定されています。

class Foo {}
console.log(Foo.name); // Foo

const Bar = class {};
console.log(Bar.name); // Bar
12.6.1.8 デフォルトエクスポート

次のすべてのステートメントで、name'default'に設定されます。

export default function () {}
export default (function () {});

export default class {}
export default (class {});

export default () => {};
12.6.1.9 その他のプログラミング構造

12.6.2 注意点

12.6.2.1 注意点: 関数の名前は常に作成時に代入される

関数名は、常に作成時に割り当てられ、後で変更されることはありません。つまり、JavaScriptエンジンは前述のパターンを検出し、正しい名前で始まる関数を作成します。次のコードは、functionFactory()によって作成された関数の名前が、B行の宣言によってではなく、A行で割り当てられることを示しています。

function functionFactory() {
    return function () {}; // (A)
}
const foo = functionFactory(); // (B)
console.log(foo.name.length); // 0 (anonymous)

理論的には、各代入において、右辺が関数に評価されるかどうか、また、その関数がまだ名前を持っていないかどうかをチェックすることができます。しかし、それは著しいパフォーマンスの低下を招くでしょう。

12.6.2.2 注意点:ミニフィケーション

関数名はミニフィケーションの対象となり、通常、ミニファイされたコードでは変更されます。やりたいことによっては、文字列(ミニファイされない)を介して関数名を管理する必要がある場合や、ミニファイアにどの名前をミニファイしないかを指示する必要がある場合があります。

12.6.3 関数の名前の変更

これらはプロパティnameの属性です。

> let func = function () {}
> Object.getOwnPropertyDescriptor(func, 'name')
{ value: 'func',
  writable: false,
  enumerable: false,
  configurable: true }

プロパティが書き込み可能でないということは、代入によってその値を変更できないことを意味します。

> func.name = 'foo';
> func.name
'func'

しかし、プロパティは構成可能であり、再定義することで変更できることを意味します。

> Object.defineProperty(func, 'name', {value: 'foo', configurable: true});
> func.name
'foo'

プロパティnameが既に存在する場合、記述子プロパティconfigurableを省略できます。これは、欠落している記述子プロパティが、対応する属性が変更されないことを意味するためです。

プロパティnameがまだ存在しない場合、記述子プロパティconfigurableは、nameが構成可能なままになることを保証します(デフォルトの属性値はすべてfalseまたはundefinedです)。

12.6.4 仕様における関数プロパティname

12.7 FAQ:呼び出し可能なエンティティ

12.7.1 関数がnewを介して呼び出されたかどうかを判断するにはどうすればよいですか?

ES6には、クラスに関する章で説明されているサブクラス化のための新しいプロトコルがあります。そのプロトコルの一部は、コンストラクター呼び出しのチェーンの最初の要素を参照するメタプロパティnew.targetです(スーパーメソッド呼び出しのチェーンにおけるthisに似ています)。コンストラクター呼び出しがない場合はundefinedです。これを使用して、関数がnewを介して呼び出す必要があるか、またはそれを介して呼び出す必要がないことを強制できます。これは後者の例です。

function realFunction() {
    if (new.target !== undefined) {
        throw new Error('Can’t be invoked via `new`');
    }
    ···
}

ES5では、これは通常このようにチェックされていました。

function realFunction() {
    "use strict";
    if (this !== undefined) {
        throw new Error('Can’t be invoked via `new`');
    }
    ···
}
次へ:13. アロー関数