JavaScriptの関数には色々あり、これを理解していないとJavaScriptで書かれたプログラムのリバースエンジニアリングが出来ません。
未だにここの落書き人には記号のお化けにしか見えないJavScriptプログラムに出会います。その度に調べて、ここのメモを加筆訂正してします。
関数定義には複数の書式が?
JavaScript関数定義はカオスです!!
-
関数種類概要要約
-
function定義function func(){};JavaScriptの関数といえばこの書き方がベーシックでした。
オブジェクト指向的な疑似クラスとして利用できるprototypeがあります。しかしES2015以降なら、prototypeの糖衣構文のクラス定義で記述するのが適切です。 -
関数リテラルvar func = function(){};
var feature = {
func1: function(){},
func2: function(){}
}
無名関数を変数に代入する記述です。オブジェクトリテラルの中で記述できます。
落書き人は機能グループ毎に纏められるこの用法が好きです。
-
ジェネレータ関数
function* Generator(i) { yield i; yield i + 10; } const genObj = Generator(10); console.log(genObj.next().value); // expected output: 10 console.log(genObj.next().value); // expected output: 20
function*の様にfunctionにアスタリスクが付いたものがジェネレータ関数で、初期値からジェネレートするオブジェクトを返し、以後なnextメソッドでジェネレートされます。 -
Functionコンストラクタによる定義var func = new Function("arg", " ~処理~ )この事例の記述にはお目に掛かったことがありません。利用場面を考えたのですが、動的に動作を変えられますね。eval同様に禁断の命令ですね。リバースエンジニアリングで遭遇した時の為に記憶の片隅に置いとく程度で良いと思います。
newは既存の関数を擬似的クラスとして利用するのにしばしば使われます。 -
アロー関数
(Arrow function)const func = (arg) => { ~処理~ };記号のお化けに見えますが、慣れれば可読性が高い。
thisが拘束されるのでcall,apply,bindは適用する必要がない。これコーディングが楽で下記の事を差し引いたとしても重宝します。
thisやsuperへの結びつけを持たないので、メソッドとして使用することはできない。
newで擬似的なクラスとして利用できない。
ジェネレーター関数として使えない。
という事で局所的な関数としてよく利用します。
-
クラス定義
class クラス名 { constructor(引数) { プロパティ定義 初期処理 } メソッド名 (引数) { メソッドの処理 } }
動作環境をES2015以降を前提にできるなら、リエントラントな処理にはクラス定義がお薦めです。実態はプロトタイプベース構文関数の糖衣構文です。
少し読みづらかったprototypeベースの関数がスッキリ書けます。
それと大きな利点はthisのスコープが単純になることから、奇っ怪なthisのスコープに悩まされることが軽減することです。
落書き人は使いまわしが堪能な部品的なモジュールの記述で好んで使います。
関数理解に役立った参考文献で、この記事を理解できるまで熟読しておく事で、JavaScriptの関数を理解することに役立ちます。
変数スコープを掘り下げる
JavaScriptの難問中の難問です。落書き人の頭の中の未整理状態が解消したらメモります。
クラス定義はプロトタイプベースの砂糖?
JavaScriptのクラス定義は、JavaScriptのプロトタイプベース関数を解りやすい表現した糖衣構文(syntactic sugar)*1です。
参考文献:【JavaScript入門】コンストラクタの使い方
■ECMAScript 2015(ES6)より前は関数を擬似的なクラスとして利用
//関数による擬似的なクラス
function Person(name, age) {
//プロパティ
this.name = name;
this.age = age;
//メソッド
this.setName = function(name) {
this.name = name;
}
this.getName = function() {
return this.name;
}
}
var person1 = new Person('太郎', 22);
var person2 = new Person('次郎', 31);
console.log( person1.name );
console.log( person1.age );
console.log( person2.name );
console.log( person2.age );
■プロトタイプベースでメモリ使用効率改善
上記の例では実行コード(メソッド部分)もnewでコピーされてしまい、メモリー利用効率が悪い。
そこでメソッド部分を共有してメモリー使用量を減らすprototypeを利用。
まさにリエントラント*2な関数になります。
var Person = function(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
■ECMAScript 2015(ES6)以降はクラス定義で
プロトタイプベース関数の糖衣構文(syntactic sugar)*1です。この砂糖を味わうとプロトタイプの記述には戻れなくなるほどスッキリします。
シンプルに表現したクラス定義は下記のとおりです。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
get result() {
this.checkAge();
}
checkAge() {
if(this.age < 20) {
console.log(this.name + 'は未成年です');
} else {
console.log(this.name + 'は成人です');
}
}
}
var person = new Person('太郎', 22);
person.result;
-
*1 糖衣構文(syntactic sugar)
複雑でわかりにくい書き方を、シンプルでわかりやすい書き方にした構文。
例えば
はPromise.resolve(42);
の糖衣構文です。new Promise((resolve) => { resolve(42); });
クラス定義の説明から外れますが、前者のPromise.resolveはthenableなオブジェクトをpromiseオブジェクトに変換するという機能がありますので、厳密には別物です。
-
*2 リエントラント
オブジェクト指向言語が普及する前は、リエントラントなサブルーチンとか表現していました。
このサブルーチンを作成するにはメモリーの動的取得など難易度が高いとされていました。
落書き人にとっては、抽象的な説明が多いオブジェクトプログラミングの解説が、最初は珍糞漢糞でした。^^;
落書き人の旧式電脳に解るようにオブジェクト指向を言い換えれば
・リエントラントサブルーチンのライブラリ→クラス定義
・オーバーロード(初期化を含む) → コンストラクタ(new)
・オーバーロードされた実態→インスタンス オブジェクト
実は、落書き人にはコンストラクタという用語がいまいちピントときていません。文献によってはクラス定義の事をコンストラクタと云っているようにも読める、newの事をコンストラクタを言っている様にも読めます。また○○のオブジェクトという表現で、〇〇が抜けている文献も多く、オブジェクト指向言語の文献が睡眠導入剤になってしまう落書き人です。そんな落書き人ですので、未だにリエントラントなサブルーチンという考え方に置き換えて理解する旧式電脳です。