JavaScript のもう一つの「関数名」 —— name プロパティ

## 「関数名」と `name` プロパティ
JavaScript において「関数名」あるいは「メソッド名」というと何を指すでしょうか。
関数オブジェクトを参照している変数名やプロパティ名を指すことが多いと思います。
しかし、もう一つ「関数の名前」と言える物として、関数オブジェクトの `name` プロパティがあります。
これは関数オブジェクトの生成時に決定される、文字列のプロパティです。

```JavaScript
function foo() {
	// do something
}

let bar = foo
let baz = foo
let qux = [foo, foo]

console.log( foo.name    )   // 出力 -> "foo"
console.log( bar.name    )   // 出力 -> "foo"
console.log( baz.name    )   // 出力 -> "foo"
console.log( qux[0].name )   // 出力 -> "foo"
```

JavaScript では関数はオブジェクトです。
例えば上のコードのように、同じ関数が値としてさまざまな変数から参照されたり、配列の要素になったりします。
また、関数を参照していた変数に別の物が代入されることもあります。
変数名と関数オブジェクトの関係は儚いのです。
ですからデバッグの際などに、関数を区別するための「関数の名前」として `name` プロパティが使われることがあります。


## `name` プロパティはいかに決定されるか


### `function` 文・`function` 式での指定
JavaScript に古くからある `function` 文と `function` 式では「関数名」を指定できます。
指定した「関数名」は生成される関数オブジェクトの `name` プロパティに使用されます。
（`function` 文の場合はさらに関数オブジェクトを参照する変数名にも使われます）

```JavaScript
// function 文
function foo() {
	// do something
}

console.log( foo.name )   // 出力 -> "foo"


// function 式
let f = function foo() {
	// do something
}

console.log( f.name )   // 出力 -> "foo"
```

`function` の後に指定する「関数名」は識別子です。
コードを書いた時点で決定されています。
任意の式を置いて実行時に計算させる事はできません。
下のように `eval` を使えば別ですが。

```JavaScript
let funcName = "foo"

let f = eval(`(function ${funcName}() {
	// do something
})`)

console.log( f.name )   // 出力 -> "foo"
```

以降、`eval` のことは考えないことにします。


### `Function` コンストラクタ
`Function` コンストラクタで作成される関数オブジェクトの `name` プロパティは `"anonymous"` となります。
これを変更する引数などはありません。

```JavaScript
let f = new Function("// do something")

console.log( f.name )   // 出力 -> "anonymous"
```


### アロー関数式・関数名を省略した `function` 式
ES6(ES2015) で登場したアロー関数式には、関数名を指定する方法がありません。
また `function` 式も、関数名が省略可能です。
では、これらの構文で作成された関数オブジェクトの `name` プロパティは空なのでしょうか？
実はそうとは限りません。

```JavaScript
let f1 = ()=>{ /* do something */ }
let f2 = function(){ /* do something */ }

console.log( f1.name )   // 出力 -> "f1"
console.log( f2.name )   // 出力 -> "f2"
```

上では変数宣言の初期値としてこれらを使用しました。
すると、生成される関数オブジェクトの `name` プロパティには変数名と同じ文字列が自動的に設定されます。

この設定は、関数オブジェクトが生成される際に一度だけ行なわれます。
例えば下のようにしても `f1` 関数の `name` プロパティが変更されることはありません。

```JavaScript
let f1 = function(){ /* do something */ }
let f2 = f1
f1 = null

console.log( f2.name )   // 出力 -> "f1"
```

`name` プロパティは不変です。
もはや、この関数オブジェクトは `f1` という変数から参照されていませんが、`name` プロパティは `"f1"` のままです。

MDN では、このように自動的に設定される `name` プロパティを ["Inferred function names"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name) と呼んで紹介しています。
「推定関数名」といった所でしょうか。
これをもう少し詳しく見てみましょう。


### 「関数名」の推定


#### 変数宣言
```JavaScript
let   f1 = function(){ /* do something */ }
const f2 = function(){ /* do something */ }
var   f3 = function(){ /* do something */ }

console.log( f1.name, f2.name, f3.name )   // 出力 -> "f1 f2 f3"
```

既に紹介したように、変数の宣言時に初期化する場合、変数名から「関数名」が推定されます。
変数名ですから、これもコードを書いた時点で決定されています。


#### 変数への代入
```JavaScript
let f
f = function(){ /* do something */ }

console.log( f.name )   // 出力 -> "f"
```

変数への代入でも、宣言時の初期化の際と同じように推定されます。


#### プロパティへの代入
```JavaScript
let obj = {}
obj.foo = function(){ /* do something */ }

console.log( obj.foo.name )   // 出力 -> ""
```

左辺から推定され、`"obj.foo"` などになるのが正しいと思うのですが、空文字になります。
現在の処理系では推定が行なわれないようです。
（ただ、Firefox は内部的にはこういった推定を行なっているようで、関数オブジェクトのコンソールへの表示には推定された関数名が使われています）


#### オブジェクト初期化子
```JavaScript
let obj = {
	foo:        function(){ /* do something */ },
	"foo bar":  function(){ /* do something */ },
}

console.log( obj.foo.name        )   // 出力 -> "foo"
console.log( obj["foo bar"].name )   // 出力 -> "foo bar"
```

プロパティ名から推定されます。
プロパティ名は空白なども許される文字列ですから、これは自由度が高いですね。


#### オブジェクト初期化子 —— 計算によるプロパティ名
オブジェクト初期化子を使えば、関数オブジェクトの `name` プロパティを自由な文字列にできることが分かりました。
これを使って、実行時に任意の「関数名」を付けてみましょう。
ES6 からは、オブジェクト初期化子のプロパティ名部分に式を使うことができます。
括弧 `[]` で囲み任意の式を置きます。

```JavaScript
// funcName は 実行時に決定される「関数名」を保持するものとする
let funcName = "foo"

let obj = {
	[funcName]: function(){ /* do something */ }
}
let f = obj[funcName]

console.log( f.name )   // 出力 -> "foo"
```

これで実行時に計算し、関数オブジェクトの `name` プロパティを決定することができます。


#### オブジェクト初期化子  ——  その他の記法
ES6 で導入されたオブジェクト記述子の記法を用いても、下のように関数名の推定が行なわれます。

```JavaScript
let obj = {
	// メソッドの短縮記法
	foo() { /* do something */ },

	// アクセサ
	get bar()  { /* do something */ },
	set bar(v) { /* do something */ },
}
let barDesc = Object.getOwnPropertyDescriptor(obj, "bar")

console.log( obj.foo.name     )   // 出力 -> "foo"
console.log( barDesc.get.name )   // 出力 -> "get bar"
console.log( barDesc.set.name )   // 出力 -> "set bar"
```

これらの記法を計算によるプロパティ名と共に使用すれば、オーソドックスな初期化子で行なったように、実行時の計算により関数オブジェクトの `name` プロパティを決定することができます。

```JavaScript
let methodName = "foo"
let accessorName = "bar"

let obj = {
	// メソッドの短縮記法
	[methodName]() { /* do something */ },

	// アクセサ
	get [accessorName]()  { /* do something */ },
	set [accessorName](v) { /* do something */ },
}

let f    = obj[methodName]
let desc = Object.getOwnPropertyDescriptor(obj, accessorName)

console.log( f.name        )   // 出力 -> "foo"
console.log( desc.get.name )   // 出力 -> "get bar"
console.log( desc.set.name )   // 出力 -> "set bar"
```


## `name` プロパティを後から変更する
さて、関数オブジェクトの生成時に `name` プロパティがいかに決定されるかを見てきましたが、これを後から変更できるでしょうか。

```JavaScript
function foo(){
	// do something
}
foo.name = "bar"

console.log( foo.name )   // 出力 -> "foo"
```

上のように `name` プロパティに代入しても例外が発生したりはしませんが、値は変更されません。
`name` プロパティは書込不可の属性を持つからです。
しかし、ES6 での標準化により `name` プロパティの属性は設定可能になりました。
「設定可能」ならば値（`value` 属性）を設定することもできます。
つまりは間接的にならば書き込めるということです。

```JavaScript
function foo(){
	// do something
}

let desc = Object.getOwnPropertyDescriptor(foo, "name")
desc.value = "bar"
Object.defineProperty(foo, "name", desc)

console.log( foo.name )   // 出力 -> "bar"
```

あるいは、`name` プロパティを書き込み可能（`writable` 属性を `true`）に設定すれば、代入で変更できるようにもなります。

```JavaScript
function foo(){
	// do something
}

let desc = Object.getOwnPropertyDescriptor(foo, "name")
desc.writable = true
Object.defineProperty(foo, "name", desc)

foo.name = "bar"
console.log( foo.name )   // 出力 -> "bar"
```