JavaScript関数の考察

 
Web Tips.
Booskanium's
Booskanium's Web Tips.

JavaScriptの関数周りには色々あり、これを理解していないとJavaScriptで書かれたプログラムのリバースエンジニアリングが出来ません。
未だにここの落書き人には記号のお化けにしか見えないJavScriptプログラムに出会います。その度に調べて、ここのメモを加筆訂正します。

関数定義には複数の方法が?

JavaScript関数定義は単純であり難解!!

  • 関数種類
    概要
    要約
  • function定義
    function func(){};
    JavaScriptの関数といえばこの書き方がスタンダードでした。過去形なのはES2015以降はモダンな構文が登場しているからです。
    オブジェクト指向的な疑似クラスとして利用できるprototypeがあります。しかしES2015以降なら、prototypeの糖衣構文のクラス定義で記述するのが適切です。
  • 関数リテラル
    var func = function(){};
    無名関数を変数に代入する記述です。オブジェクトリテラルの中で記述できます。
    落書き人はこの用法が好きです。
  • Functionコンストラクタによる定義
    var func = new Function("arg", " ~処理~ )
    この事例の記述にはお目に掛かったことがありません。利用場面を考えたのですが、動的に動作を変えられますね。eval同様に禁断の命令ですね。リバースエンジニアリングで遭遇した時の為に記憶の片隅に置いとく程度で良いと思います。
    newは既存の関数を擬似的クラスとして利用するのにしばしば使われます。
  • アロー関数
    (Arrow function)
    const func = (arg) => { ~処理~ };
    記号のお化けに見えるが慣れれば可読性が高い。
    thisが拘束されるのでcall,apply,bindは適用できない。
    thisやsuperへの結びつけを持たないので、メソッドとして使用することはできない。
    newで擬似的なクラスとして利用できない。
    ジェネレーター関数として使えない。
  • クラス定義
    class クラス名 { constructor(引数) { プロパティ定義 初期処理 } メソッド名 (引数) { メソッドの処理 } }
    動作環境をES2015以降を前提にできるなら、リエントラントな処理にはクラス定義がお薦めです。実態はプロトタイプベース構文関数の糖衣構文です。
    少し読みづらかったprototypeベースの関数がスッキリ書けます。
変数スコープを掘り下げる

JavaScriptの難問中の難問です。落書き人の頭の中の未整理状態が解消したらメモります。

非strictとstrictモードのthis

落書き人は非strictモードは使わないと決めましたが、世の中にはまだまだ非strictモードのプログラムがあります。
非strictモードとstrictモードでは、変数のスコープが異なります。知らないと不具合を引き起こしますので知っておく必要があります。
※以下の内容は書きかけでほったらかしです。まだ確認不足です。気が向いたら加筆訂正します。^^;

非strictモードの場合

非strictモードでのスコープを確認

strictモードの場合

strictモードでのスコープを確認
クラス定義はプロトタイプベースの砂糖?

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の事をコンストラクタを言っている様にも読めます。また○○のオブジェクトという表現で、〇〇が抜けている文献も多く、オブジェクト指向言語の文献が睡眠導入剤になってしまう落書き人です。そんな落書き人ですので、未だにリエントラントなサブルーチンという考え方に置き換えて理解しています。