どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「newに依存しないコンストラクタの作り方」をお届けしました。
今回は、「メソッドをプロトタイプに格納しよう」「プライベートデータの格納にはクロージャを使おう」 をまとめてお届けします。
テキスト第4章「オブジェクトとプロトタイプ」の項目34、35に対応しています。
今日のアジェンダ
- プロトタイプを使わないメモリ無駄遣いクラス
- プライベートデータの格納にはクロージャを使おう
プロトタイプを使わないメモリ無駄遣いクラス
旧方式のクラスの書き方の復習です。(ここから読み始めた方用)
function myFunction5_34_01() {
/**
* コンストラクタ
* @param {string} name 名前
* @param {string} passwordHash ハッシュ
*/
function User(name, passwordHash) {
this.name = name;
this.passwordHash = passwordHash;
}
/** パスワードをチェックするメソッド */
User.prototype.checkPassword = function (password) {
return hash(password) === this.passwordHash;
}
const u = new User('Tom', 'abcde');
console.log(u); //{ name: 'Tom', passwordHash: 'abcde' }
console.log(u.checkPassword('abcde')); //true
}
/**
* パスワードを返す関数
* @param {string} password
* @return {string} password
*/
function hash(password) {
return password;
}
このクラスですが、プロトタイプを使わない書き方もできます。
プロトタイプ(メソッドをクラスに参照しにいく仕組み)ではないので、インスタンスに、それぞれメソッドがコピーされます。
function myFunction5_34_02() {
/**
* コンストラクタ
* @param {string} name 名前
* @param {string} passwordHash ハッシュ
*/
function User(name, passwordHash) {
this.name = name;
this.passwordHash = passwordHash;
this.checkPassword = function (password) {
return hash(password) === this.passwordHash;
};
}
const u1 = new User('Tom', 'abcde');
const u2 = new User('Bob', 'fghij');
const u3 = new User('Ivy', 'klmno');
console.log(u1); //{ name: 'Tom', passwordHash: 'abcde', checkPassword: [Function] }
console.log(u2); //{ name: 'Bob', passwordHash: 'fghij', checkPassword: [Function] }
console.log(u3); //{ name: 'Ivy', passwordHash: 'klmno', checkPassword: [Function] }
}
/**
* パスワードを返す関数
* @param {string} password
* @return {string} password
*/
function hash(password) {
return password;
}
インスタンスにメソッドが直接格納されているほうが、ルックアップも早く、最適化しやすいのでは?という疑問もあるかもしれません。
しかし、現在のJavaScriptエンジンは、プロトタイプのルックアップを強力に最適化するそうです。
また、インスタンスメソッドが、プロトタイプメソッドより、多くのメモリを消費することは、確実だそうです。※裏取り作業はしません。
プライベートデータの格納にはクロージャを使おう
項目11で、クロージャを解説しました。クロージャとは関数です。
関数内で、関数スコープ外の変数を参照する自由変数が書かれているばあい、スコープチェーンを辿って外側のスコープにある変数に結合し、参照を閉じるのでクロージャと呼ばれます。
自由変数である変数vegetableは、関数内のローカル変数ではなく、関数の仮引数にも設定できます。
function myFunction2_11_05() {
function makeSandwich(vegetable) {
return function (topping) {
return vegetable + 'and' + topping;
};
}
const lettuceAnd = makeSandwich('レタス');
console.log(lettuceAnd('トマト')); //レタスandトマト
}
つまり、プロトタイプではなく、インスタンスにメソッドをコピーするタイプでもクラスを書けるということです。
この方法であれば、Userのインスタンスにはプロパティを含まなくなるので、外側のコードから、Userインスタンスの名前やパスワードを直接アクセスする手段はなくなります。
インスタンスにメソッドをコピーするのでメモリの無駄遣いに思えますが、セキュリティは強化されるでしょう。
function myFunction5_35_01() {
/**
* コンストラクタ
* @param {string} name 名前
* @param {string} passwordHash ハッシュ
*/
function User(name, passwordHash) {
this.toString = function () {
return `[User ${name}]`;
};
this.checkPassword = function (password) {
return hash(password) === passwordHash;
};
}
const u = new User('Tom', 'abcde');
console.log(u); //{ toString: [Function], checkPassword: [Function] }
console.log(u.toString()); //[User Tom]
console.log(u.checkPassword('abcde')); //true
}
/**
* パスワードを返す関数
* @param {string} password
* @return {string} password
*/
function hash(password) {
return password;
}
繰り返しますが、ES6のクラス構文では、気にすることはありません。
クラス内のプロパティは、隠蔽されており、外側から書き換えることができません。(プロパティを書き換えるための、setter/getterメソッドが必要です)
function myFunction5_35_02() {
class User {
/**
* コンストラクタ
* @param {string} name 名前
* @param {string} passwordHash ハッシュ
*/
constructor(name, password) {
this.name = name;
this.password = password;
}
/** 文字列で名前を返すメソッド */
toString() { return `[User ${name}]`; }
/** パスワードをチェックするメソッド */
checkPassword(password) { return hash(password) === passwordHash; }
}
}
このように、関数はいともかんたんに書き換えられます。クラス構文を使っていきましょう。
function myFunction5_35_03() {
class ES6Person {
constructor(name) {
this.name = name;
}
}
let RhinoPerson = function (name) {
this.name = name;
}
function myFunction() {
const ep = new ES6Person('takahashi')
console.log(`私は${ep.name}です`); //私はtakahashiです
//RhinoPerson関数はいともかんたんに書き換えられる
RhinoPerson = function (name) {
this.name = name + '様';
}
const rp = new RhinoPerson('takahashi')
console.log(`私は${rp.name}です`); // 私はtakahashi様です
}
myFunction();
}
まとめ
以上で、「メソッドをプロトタイプに格納しよう」「プライベートデータの格納にはクロージャを使おう」を2本まとめてお届けしました。
次回は、「インスタンスの状態は、インスタンスオブジェクトにだけ保存する」「thisの暗黙的な結合を理解しよう」 を2本まとめてお届けします。