[EffectiveJavaScript輪読会5]メソッドをプロトタイプに格納しよう

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「newに依存しないコンストラクタの作り方」をお届けしました。

[EffectiveJavaScript輪読会5]newに依存しないコンストラクタの作り方
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のシリーズでは、第3章終わりと、第4章始めの考察をお...

今回は、「メソッドをプロトタイプに格納しよう」「プライベートデータの格納にはクロージャを使おう」 をまとめてお届けします。

テキスト第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本まとめてお届けします。

このシリーズの目次

タイトルとURLをコピーしました