この章では、ES6 で呼び出し可能なエンティティ(関数呼び出し、メソッド呼び出しなどを介して)を適切に使用する方法について説明します。
super
を介した呼び出しは特定の場所に限定されるObject.prototype
および Array.prototype
の省略形name
プロパティname
new
を介して呼び出されたかどうかを判断するにはどうすればよいですか?ES5 では、単一の構成要素である(従来の)関数が 3 つの役割を果たしていました。
ES6 では、より専門化が進んでいます。3 つの役割は、現在次のように処理されます。関数定義とクラス定義に関する限り、定義は宣言または式のいずれかです。
特にコールバックの場合、アロー関数は便利です。これは、周囲のスコープの this
をシャドウしないためです。
長いコールバックやスタンドアロン関数には、従来の関数でもかまいません。一部の API では、this
を暗黙的なパラメーターとして使用します。その場合、従来の関数を使用するしかありません。
注意:
これらのエンティティは動作が異なりますが(後述)、すべて関数です。例えば
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
一部の呼び出しはどこでも実行できますが、他の呼び出しは特定の場所に制限されます。
ES6 では、次の 3 種類の呼び出しをどこでも実行できます。
func(3, 1)
obj.method('abc')
new Constr(8)
super
を介した呼び出しは特定の場所に限定される super
キーワードを介して 2 種類の呼び出しを行うことができます。これらの使用は特定の場所に限定されます。
super.method('abc')
super(8)
constructor()
内でのみ使用可能です。メソッドではない関数とメソッドの違いは、ECMAScript 6 でより顕著になっています。両方に対して特別なエンティティがあり、それらだけができることがあります。
this
を取得します(「字句 this
」)。super
のサポートを提供します。このセクションでは、呼び出し可能エンティティを使用するためのヒントを示します。どのエンティティを使用するのが最適か、など。
コールバックとして、アロー関数には従来の関数よりも 2 つの利点があります。
this
は字句であり、したがって使用がより安全です。this
残念ながら、一部の JavaScript API では、コールバックの暗黙的な引数として this
を使用するため、アロー関数を使用できません。例:B 行の this
は、A 行の関数の暗黙的な引数です。
beforeEach
(
function
()
{
// (A)
this
.
addMatchers
({
// (B)
toBeInRange
:
function
(
start
,
end
)
{
···
}
});
});
このパターンは明示性が低く、アロー関数の使用を妨げます。
これは簡単に修正できますが、API を変更する必要があります。
beforeEach
(
api
=>
{
api
.
addMatchers
({
toBeInRange
(
start
,
end
)
{
···
}
});
});
API を暗黙的なパラメーター this
から明示的なパラメーター api
に変えました。私はこの種の明示性が好きです。
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'
);
});
(コールバックではなく)スタンドアロン関数として、関数宣言を優先します。
function
foo
(
arg1
,
arg2
)
{
···
}
利点は次のとおりです。
function
は利点です。構造を目立たせたいからです。1 つの注意点があります。通常、スタンドアロン関数で this
は必要ありません。それを使用する場合は、周囲のスコープ(たとえば、スタンドアロン関数を含むメソッド)の this
にアクセスする必要があります。残念ながら、関数宣言ではそれができません。独自の this
があり、周囲のスコープの this
をシャドウします。したがって、リンターに、関数宣言内の this
について警告させたい場合があります。
スタンドアロン関数のもう 1 つのオプションは、アロー関数を変数に割り当てることです。this
の問題は、字句であるため回避されます。
const
foo
=
(
arg1
,
arg2
)
=>
{
···
};
メソッド定義は、super
を使用するメソッドを作成する唯一の方法です。これらはオブジェクトリテラルとクラス(メソッドを定義する唯一の方法)では当然の選択肢ですが、既存のオブジェクトにメソッドを追加する場合はどうでしょうか?例:
MyClass
.
prototype
.
foo
=
function
(
arg1
,
arg2
)
{
···
};
以下は、ES6 で同じことを行う簡単な方法です(注意:Object.assign()
は super
を使用したメソッドを適切に移動しません)。
Object
.
assign
(
MyClass
.
prototype
,
{
foo
(
arg1
,
arg2
)
{
···
}
});
詳細と注意点については、Object.assign()
に関するセクションを参照してください。
通常、関数値プロパティはメソッド定義を介して作成する必要があります。ただし、アロー関数の方が適切な場合もあります。次の 2 つのサブセクションでは、いつ何を使用するかについて説明します。前者の方がメソッドを持つオブジェクトに適しており、後者の方がコールバックを持つオブジェクトに適しています。
プロパティが本当にメソッドである場合は、メソッド定義を介して関数値プロパティを作成します。これは、プロパティ値がオブジェクト(次の例の 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
は、プロパティ start
、pull
、および cancel
が実際のメソッドであるオブジェクトです。したがって、これらのメソッドは this
を使用してオブジェクトローカルの状態(* 行)にアクセスし、相互に呼び出すことができます(** 行)。
プロパティ値がコールバックである場合は、アロー関数を介して関数値プロパティを作成します。このようなコールバックは、オブジェクトに格納されている(例の obj
)のではなく、周囲のスコープ(次の例の surroundingMethod()
)と密接に関連する傾向があります。
アロー関数の this
は、周囲のスコープ(字句 this
)の this
です。アロー関数は優れたコールバックです。これは、コールバック(実際の、メソッドではない関数)で通常必要な動作であるためです。コールバックには、周囲のスコープの this
をシャドウする独自の this
を持たないようにする必要があります。
プロパティ start
、pull
、および 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()
があります。後者のアロー関数がオブジェクトリテラルで囲まれているからといって、それが変わるわけではありません。
このセクションでは、ES6 で 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
ライブラリ(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
);
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
(
''
);
})();
示されているように括弧で囲む必要があることに注意してください(括弧は、関数呼び出し全体ではなく、アロー関数の周りにあります)。詳細については、アロー関数の章で説明されています。
ES5では、コンストラクター関数がオブジェクトのファクトリを作成する主流の方法でした(ただし、他にも多くの手法があり、一部はよりエレガントであると言えるでしょう)。ES6では、クラスがコンストラクター関数を実装する主流の方法です。いくつかのフレームワークは、カスタム継承APIの代替としてそれらをサポートしています。
このセクションでは、各ES6の呼び出し可能エンティティを詳細に説明する前に、チートシートから始めます。
エンティティによって生成された値の特性
関数宣言/関数式 | アロー | クラス | メソッド | |
---|---|---|---|---|
関数呼び出し可能 | ✔ | ✔ | × | ✔ |
コンストラクター呼び出し可能 | ✔ | × | ✔ | × |
プロトタイプ | F.p |
F.p |
SC | F.p |
プロパティprototype |
✔ | × | ✔ | × |
エンティティ全体の特性
関数宣言 | 関数式 | アロー | クラス | メソッド | |
---|---|---|---|---|---|
巻き上げ | ✔ | × | |||
window プロパティを作成(1) |
✔ | × | |||
内部名(2) | × | ✔ | ✔ | × |
エンティティの本体の特性
関数宣言 | 関数式 | アロー | クラス(3) | メソッド | |
---|---|---|---|---|---|
this |
✔ | ✔ | lex | ✔ | ✔ |
new.target |
✔ | ✔ | lex | ✔ | ✔ |
super.prop |
× | × | lex | ✔ | ✔ |
super() |
× | × | × | ✔ | × |
凡例–テーブルセル
F.p
:Function.prototype
Function.prototype
。詳細については、クラスの章で説明されています。凡例–脚注
ジェネレーター関数とメソッドはどうですか? これらは、2つの例外を除いて、ジェネレーターではない対応するものと同様に機能します。
(GeneratorFunction).prototype
を持っています((GeneratorFunction)
は内部オブジェクトです。「イテレーションAPI内の継承(ジェネレーターを含む)」セクションの図を参照)。this
のルール 関数呼び出し | メソッド呼び出し | new |
|
---|---|---|---|
従来の関数(厳格) | undefined |
レシーバー | インスタンス |
従来の関数(非厳格) | window |
レシーバー | インスタンス |
ジェネレーター関数(厳格) | undefined |
レシーバー | TypeError |
ジェネレーター関数(非厳格) | window |
レシーバー | TypeError |
メソッド(厳格) | undefined |
レシーバー | TypeError |
メソッド(非厳格) | window |
レシーバー | TypeError |
ジェネレーターメソッド(厳格) | undefined |
レシーバー | TypeError |
ジェネレーターメソッド(非厳格) | window |
レシーバー | TypeError |
アロー関数(厳格&非厳格) | レキシカル | レキシカル | TypeError |
クラス(暗黙的に厳格) | TypeError |
TypeError |
SCプロトコル |
凡例–テーブルセル
this
を介して新しいインスタンスを受け取ります。派生クラスはスーパークラスからインスタンスを取得します。詳細については、クラスの章で説明されています。これらは、ES5で知られている関数です。それらを作成する方法は2つあります。
const
foo
=
function
(
x
)
{
···
};
function
foo
(
x
)
{
···
}
this
のルール
this
は、厳格モードの関数ではundefined
であり、非厳格モードではグローバルオブジェクトです。this
は、メソッド呼び出しのレシーバー(またはcall
/apply
の最初の引数)です。this
は、新しく作成されたインスタンスです。ジェネレーター関数については、ジェネレーターの章で説明されています。構文は従来の関数に似ていますが、アスタリスクが追加されています。
const
foo
=
function
*
(
x
)
{
···
};
function
*
foo
(
x
)
{
···
}
this
のルールは次のとおりです。this
はジェネレーターオブジェクトを参照しないことに注意してください。
this
は、従来の関数と同じように処理されます。このような呼び出しの結果はジェネレーターオブジェクトです。TypeError
がスローされます。メソッド定義は、オブジェクトリテラル内に記述できます。
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]]
を持つ関数を生成します(詳細については、クラスの章で説明されています)。
ルール
super
を使用できます。TypeError
をスローします。クラス定義内では、名前がconstructor
のメソッドは、この章で後述するように特別です。
ジェネレーターメソッドについては、ジェネレーターの章で説明されています。構文はメソッド定義に似ていますが、アスタリスクが追加されています。
const
obj
=
{
*
generatorMethod
(
···
)
{
···
},
};
class
MyClass
{
*
generatorMethod
(
···
)
{
···
}
}
ルール
this
とsuper
を使用できます。アロー関数については、独自の章で説明されています。
const
squares
=
[
1
,
2
,
3
].
map
(
x
=>
x
*
x
);
次の変数は、アロー関数内でレキシカルです(周囲のスコープから取得されます)。
arguments
super
this
new.target
ルール
this
など。this
は引き続きレキシカルであり、メソッド呼び出しのレシーバーを参照しません。TypeError
を生成します。クラスについては、独自の章で説明されています。
// 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
ルール
this
はそれを参照します。派生クラスはスーパークラスからインスタンスを受け取るため、this
にアクセスする前にsuper()
を呼び出す必要があります。JavaScriptには、メソッドを呼び出す方法が2つあります。
arr.slice(1)
):プロパティslice
は、arr
のプロトタイプチェーンで検索されます。その結果は、this
がarr
に設定された状態で呼び出されます。Array.prototype.slice.call(arr, 1)
):slice
は、this
がarr
(call()
の最初の引数)に設定された状態で直接呼び出されます。このセクションでは、これら2つがどのように機能し、ECMAScript 6でメソッドを直接呼び出すことがほとんどない理由について説明します。始める前に、プロトタイプチェーンの知識を更新しましょう。
JavaScriptの各オブジェクトは、実際には1つ以上のオブジェクトのチェーンであることを思い出してください。最初のオブジェクトは、後続のオブジェクトからプロパティを継承します。たとえば、配列['a', 'b']
のプロトタイプチェーンは次のようになります。
'a'
と'b'
を保持しています。Array.prototype
。Array
コンストラクターによって提供されるプロパティ。Object.prototype
。Object
コンストラクターによって提供されるプロパティ。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.prototype
はtoString()
メソッドの配列固有のバージョンを提供し、Object.prototype.toString()
を上書きします。
> var arr = ['a', 'b'];
> Object.getOwnPropertyNames(Array.prototype)
[ 'toString', 'join', 'pop', ··· ]
> arr.toString()
'a,b'
メソッド呼び出しarr.toString()
を見ると、実際には2つのステップが実行されていることがわかります。
arr
のプロトタイプチェーンで、名前がtoString
である最初のプロパティの値を取得します。this
をメソッド呼び出しの*レシーバー*arr
に設定します。関数のcall()
メソッドを使用することで、この2つのステップを明示的に行うことができます。
> var func = arr.toString; // dispatch
> func.call(arr) // direct call, providing a value for `this`
'a,b'
JavaScriptには、直接メソッド呼び出しを行う2つの方法があります。
Function.prototype.call(thisValue, arg0?, arg1?, ···)
Function.prototype.apply(thisValue, argArray)
メソッドcall
とメソッドapply
は両方とも関数で呼び出されます。これらは、this
の値を指定するという点で、通常の関数呼び出しとは異なります。call
は、個々のパラメータを介してメソッド呼び出しの引数を提供し、apply
は、Arrayを介して引数を提供します。
ディスパッチされたメソッド呼び出しでは、レシーバーは2つの役割を果たします。メソッドを見つけるために使用され、暗黙的なパラメータになります。最初の役割の問題は、メソッドを呼び出すには、オブジェクトのプロトタイプチェーンにメソッドが含まれている必要があるということです。直接メソッド呼び出しでは、メソッドはどこからでも取得できます。これにより、別のオブジェクトからメソッドを借りることができます。たとえば、Object.prototype.toString
を借りて、配列arr
にtoString
の元の、オーバーライドされていない実装を適用できます。
>
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
のすべてのメソッド(すべてのオブジェクトで動作する必要があるため、暗黙的にジェネリックです)が含まれています。
このセクションでは、直接メソッド呼び出しのユースケースについて説明します。毎回、最初にES5でのユースケースを説明し、次にES6での変更点(直接メソッド呼び出しがほとんど必要なくなる場合)について説明します。
一部の関数は複数の値を受け入れますが、パラメータごとに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
...
)は、ほとんどの場合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で複雑な回避策によってのみ実現できます。
JavaScriptの一部のオブジェクトは*配列のような*オブジェクトであり、ほとんど配列ですが、配列メソッドはありません。2つの例を見てみましょう。
最初に、関数の特別な変数arguments
は配列のようです。これには、length
と、要素へのインデックス付きアクセスがあります。
>
var
args
=
function
()
{
return
arguments
}(
'a'
,
'b'
);
>
args
.
length
2
>
args
[
0
]
'a'
ただし、arguments
はArray
のインスタンスではなく、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']
一方、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']
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
hasOwnProperty()
の必要性が少ない hasOwnProperty()
は、主にオブジェクトを介してマップを実装するために使用されます。ありがたいことに、ECMAScript 6には組み込みのMap
データ構造があるため、hasOwnProperty()
の必要性が少なくなります。
Object.prototype
とArray.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つのバージョン間に大きな違いはありません。
name
プロパティ 関数のname
プロパティには、関数の名前が含まれています。
> function foo() {}
> foo.name
'foo'
このプロパティは、デバッグ(その値はスタックトレースに表示されます)および一部のメタプログラミングタスク(名前で関数を選択するなど)に役立ちます。
ECMAScript 6より前は、このプロパティはほとんどのエンジンですでにサポートされていました。ES6では、言語標準の一部になり、自動的に頻繁に設定されるようになります。
次のセクションでは、さまざまなプログラミング構造でname
が自動的に設定される方法について説明します。
関数は、変数宣言を介して作成された場合、名前を取得します。
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
今後は、匿名関数式が表示される場合はいつでも、アロー関数が同じように機能すると想定できます。
関数がデフォルト値の場合、変数またはパラメータから名前を取得します。
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
関数宣言と関数式は*関数定義*です。このシナリオは長い間サポートされています。名前付きの関数定義は、名前を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
関数がプロパティの値である場合、そのプロパティから名前を取得します。それがメソッド定義(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'
クラス定義でのメソッドの名前付けは、オブジェクトリテラルに似ています。
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'
ES6では、メソッドのキーをシンボルにすることができます。このようなメソッドのname
プロパティは、依然として文字列です。
''
)になります。
const
key1
=
Symbol
(
'description'
);
const
key2
=
Symbol
();
let
obj
=
{
[
key1
]()
{},
[
key2
]()
{},
};
console
.
log
(
obj
[
key1
].
name
);
// '[description]'
console
.
log
(
obj
[
key2
].
name
);
// ''
クラス定義は関数を作成することを忘れないでください。これらの関数も、プロパティname
が正しく設定されています。
class
Foo
{}
console
.
log
(
Foo
.
name
);
// Foo
const
Bar
=
class
{};
console
.
log
(
Bar
.
name
);
// Bar
次のすべてのステートメントで、name
が'default'
に設定されます。
export
default
function
()
{}
export
default
(
function
()
{});
export
default
class
{}
export
default
(
class
{});
export
default
()
=>
{};
new Function()
は、name
が'anonymous'
である関数を生成します。WebKitのバグは、Webでそれが必要な理由を説明しています。func.bind()
は、name
が'bound '+func.name
である関数を生成します。
function
foo
(
x
)
{
return
x
}
const
bound
=
foo
.
bind
(
undefined
,
123
);
console
.
log
(
bound
.
name
);
// 'bound foo'
関数名は、常に作成時に割り当てられ、後で変更されることはありません。つまり、JavaScriptエンジンは前述のパターンを検出し、正しい名前で始まる関数を作成します。次のコードは、functionFactory()
によって作成された関数の名前が、B行の宣言によってではなく、A行で割り当てられることを示しています。
function
functionFactory
()
{
return
function
()
{};
// (A)
}
const
foo
=
functionFactory
();
// (B)
console
.
log
(
foo
.
name
.
length
);
// 0 (anonymous)
理論的には、各代入において、右辺が関数に評価されるかどうか、また、その関数がまだ名前を持っていないかどうかをチェックすることができます。しかし、それは著しいパフォーマンスの低下を招くでしょう。
関数名はミニフィケーションの対象となり、通常、ミニファイされたコードでは変更されます。やりたいことによっては、文字列(ミニファイされない)を介して関数名を管理する必要がある場合や、ミニファイアにどの名前をミニファイしないかを指示する必要がある場合があります。
これらはプロパティ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
です)。
name
SetFunctionName()
は、プロパティname
を設定します。仕様でその名前を検索して、それがどこで発生するかを確認してください。'get'
と'set'
)Function.prototype.bind()
(プレフィックス'bound'
)name
を持たない無名関数式は、それらのランタイムセマンティクスを見ることで確認できます。SetFunctionName()
を介して設定されます。この操作は、無名関数式に対しては呼び出されません。SetFunctionName()
は呼び出されません)。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`'
);
}
···
}