発端
let hoge = (x) => x + 1
がbabelで
var hoge = function hoge(x) {x + 1;}
的にコンパイルされるので、無名関数じゃないじゃん!なんで!と駄々こねて同僚の業務時間奪ってしまった。
という話をされて、「なるほど、まあ確かに。」と思ったので調べた。
結論
-
arrow-function
のそのものの機能 / 仕様ではない。 -
function.name
のための変換によって変換がされてるっぽい- function.name の詳細はこちらの記事が有益。
おためし
準備としてこんなコードを用意
let a = () => {} // arrow function
let b = function(){} // 普通のfunction(無名)
let c = function d(){} // 名前付きfunction
babel-preset-es2015でcompile
わりといつもやるようなやり方。
% $(npm bin)/babel foo.js --presets es2015
"use strict";
var a = function a() {};
var b = function b() {};
var c = function d() {};
確かに。無名関数に名前がつけられた。
transform-es2015-arrow-functionだけ使う
babelは各機能毎に使えるので、arrow-function
だけ利用してみると、ちょっと面白い事になった
% $(npm bin)/babel foo.js --plugins transform-es2015-arrow-functions
let a = function () {}; // まだ無名!
let b = function () {};
let c = function d() {};
関数に変換されるだけで名前はつけられてない状態になった。
transform-es2015-function-nameだけ使う
で、問題のfunction.nameの話。
% $(npm bin)/babel foo.js --plugins transform-es2015-function-name
let a = () => {};
let b = function b() {}; // 名前がついた!
let c = function d() {};
arrow functionは当然変換されないのだが、bに注目してみると、function b()
と名前がついているのが確認出来る。この2つのtransformによってarrow functionに名前が付くようになっていることがわかる。
このふたつをどっちも使う
やるまでもないけど、一応
% $(npm bin)/babel foo.js --plugins transform-es2015-function-name,transform-es2015-arrow-functions
let a = function a() {};
let b = function b() {};
let c = function d() {};
function.name
とは?
function.nameプロパティは、ざっくり言えば、関数の名前を取得する機能になる。
// Function Statements
function fn () {}
console.log(fn.name) // "fn"
// variables
const a = function(){}
console.log(a.name) // "a"
const b = () => {}
console.log(b.name) // "b"
ここで、各環境でのサポート具合がわかる Compatibility table を参考にすると、それなりにこの機能をサポートしてないブラウザが多い。
サポートしてないブラウザの場合は、core-jsがpolyfillしてくれている。
そこで更にcore-jsが何をやっているのかを見ると、だいたいこんな感じで処理している。
(core-jsのソースが読みづらい場合はStack Overflowの類似記事らへんを眺めると良いかもしれない)
- functionをstringに変換して、関数の内容を文字列として取得する(
'' + that
)1 -
/^\s*function ([^ (]*)/
という正規表現で、名前の部分を取得する - 取得できた値を、Function.prototype.nameで返せるようにしている。
transform-es2015-function-name
がlet a = function(){}
をlet a = function a(){}
にしてくれることによって、core-jsがここまで処理を出来る、といった具合になる。
余談:じゃあfunction.name
って何に使えるのか?
主観だが、基本的にはデバッグ向けぐらいじゃないかなーと思っている。
本格的に使わなければならない状況に陥ったとすると、無理のある実装をしている可能性を疑うだろう。
ただし、例えばデバッグの場合だったら、こんな場合とかで使えるだろう(あんまりいいコードの例が思いつかなかった)
function hogeFunctionGenerator(someFlag, defaultCallback){
if(someFlag){
return function someFlagFn(){}
}
return defaultCallback
}
// どの関数が結局コールされるのか知りたい場合
console.log(hogeFunctionGenerator(true, function (){}).name) // "someFlagFn"
console.log(hogeFunctionGenerator(false, function (){}).name) // ""
console.log(hogeFunctionGenerator(false, function cb(){}).name) // "cb"
余談のおまけ:bableにおいてclassのconstructorではどうなるか?
function.nameを利用して、class
がどのクラスなのかというのを判定することをc.consturctor.name
を呼び出すことでが出来るということがこちらにて紹介されていた。
ただし、この場合で深めの使い方をすると、babelではなかなかうまくいかなそうだ。
class Hoge {}
function createClassZ() {
// Z classを返す
return class Z{
constructor(){
}
}
}
function createAnonymousClass() {
// 匿名classを返す
return class {
constructor(){
}
}
}
let ZX = createClassZ()
let zx = new ZX()
let X = createAnonymousClass()
let x = new X()
console.log("zx", zx.constructor.name)
console.log("x", x.constructor.name)
Chrome(V8)では
zx Z
x undefined
となってくれるところが、babelではclassをpolyfillしている都合でこんな感じになってしまう。
zx Z
x _class
このような使い方だとヤバそうなので注意したい。
類似の問題はissueとして上がっていた。
https://phabricator.babeljs.io/T7014
参考資料など
- http://www.ecma-international.org/ecma-262/6.0/#sec-setfunctionname
- http://kangax.github.io/compat-table/es6/#test-function_name_property
- https://bugzilla.mozilla.org/show_bug.cgi?id=883377
- http://www.2ality.com/2015/09/function-names-es6.html
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
- http://stackoverflow.com/questions/2648293/javascript-get-function-name
-
自分の記憶が確かなら、functionをstring変換すると文字列になるというのは、特に明確な仕様ではなく、「だいたいのブラウザがそう実装している」という類のものだったはず。 ↩