0
0

前回の記事では、レキシカル環境とクロージャについてカイージとともに学んでいきました。後編となるこちらでは、クロージャを利用したプライベート変数(みたいなやつ)と、アロー関数とfunctionでのthisの挙動について書いていこうと思います。

⇩⇩前回の記事はこちらです⇩⇩

今回も、以下の登場人物が出現します。

登場人物

スクリーンショット 2024-07-10 21.38.46.png
なんか二人増えてますね。原作ファンブチギレのメンバーたちです。

前回のお話も踏まえながら

1発4000円もするパチンコ"ヌーマ"に挑戦した二人。すると、そこには新たな敵、イチジョーと自分たちを裏切ったイシーダのおっちゃんの娘であるイシーダヒロミがいるではありませんか。

スクリーンショット 2024-07-10 23.24.44.png

そのパチンコ台の仕組みの一部は以下のようになっていましたね。(少し中身が変わっています。)

function Pachinko(money) {
  let Kaiji_money = money;
  return function (amount) {
    function (amount) {
      if (Kaiji_money - amount * 4000 <= 0) {
        Kaiji_money = 0;
        console.log("お金がつきました。補充してください。");
      } else {
        Kaiji_money -= amount * 4000;
        console.log(
          amount * 4000 + "円使用しました。残りのお金は: " + Kaiji_money + ""
        );
      }
    );
  };
}

const UsedBall = Pachinko(1090000); //残金を投入
UsedBall(100); //400000円使用しました。残りのお金は: 690000円

ではこの台でどんどん打っていきましょう

UsedBall(100);//[Log] 400000円使用しました。残りのお金は: 690000円 
UsedBall(100);//[Log] 400000円使用しました。残りのお金は: 290000円
UsedBall(100);//[Log] お金がつきました。補充してください。 

おや、お金がなくなってしまいました、、カイージくんピンチです!

カイージの逆襲

そんな時「かイージくん!!」と聞き覚えのある声が、、
スクリーンショット 2024-07-10 23.27.54.png

なんと!(テーアイのブラックカードを使って)1000万円を持ったイシーダヒロミが駆けつけてくれました!

しかし、この関数、お金自体をkaiji_moneyに加えることができませんね。そこで、戻り値をオブジェクトにして、その中にaddMoneyという関数とuseMoneyの二つ作ってあげればいいんです!

function Pachinko(money) {
  let Kaiji_money = money;
  return {
    usedBall: function (amount) {
      if (Kaiji_money - amount * 4000 <= 0) {
        Kaiji_money = 0;
        console.log("お金がつきました。補充してください。");
      } else {
        Kaiji_money -= amount * 4000;
        console.log(
          amount * 4000 + "円使用しました。残りのお金は: " + Kaiji_money + ""
        );
      }
    },
    addMoney: function (amount) {
      Kaiji_money += amount;
      console.log(
        amount + "円追加されました。現在のお金は: " + Kaiji_money + ""
      );
    },
  };
}

const pachinkoMachine = Pachinko(1090000); //残金を投入

使う時は、以下のようにメソッドとして使います。

pachinkoMachine.usedBall(100); //[Log] 400000円使用しました。残りのお金は: 690000円
pachinkoMachine.usedBall(100); //[Log] 400000円使用しました。残りのお金は: 290000円
pachinkoMachine.usedBall(100); //[Log] お金がつきました。補充してください。

pachinkoMachine.addMoney(10000000);//10000000円追加されました。現在のお金は: 10000000円

お!10000000円追加され、現在のお金は: 10000000円となっているので、うまくできていますね。

しかし、クロージャは関数に保存すると言っていたのに、どうしてaddMoneyusedBallにアクセスできたのでしょうか。

以下のレキシカル環境をみていきましょう。
スクリーンショット 2024-07-13 18.58.43.png

なんじゃこりゃ。と思った方はこちらの前半をご覧ください。

前回でも言ったのですが、レキシカル環境の「外側のレキシカル環境」に関数オブジェクトの「スコープ」を設定するので、クロージャとして保存しているのはPachinkoの方でしたよね。

だから、addMoneykaiji_moneyがもしなかったらPachinkokaiji_moneyを探すんですね。

前回のお話から、戻り値がオブジェクトになっただけでした。

なぜかトネーガワも参戦

今、カイージくんが頑張ってパチンコを回していますね。

するとここでトネーガワが急に話しかけてきました。

スクリーンショット 2024-07-11 1.14.30.png

今回は、PachinkoMachineを、Pachinkoのクロージャとして設定しましたが、以下のようにPachinkoMachine2としてクロージャを新しく作って、それぞれ打ってみるとどのようになるのでしょうか。

const pachinkoMachine = Pachinko(1090000); //109万円投入
const pachinkoMachine2 = Pachinko(1000000); //100万円投入

pachinkoMachine.usedBall(100);//400000円使用しました。残りのお金は: 690000円
pachinkoMachine.addMoney(1000000);//1000000円追加されました。現在のお金は: 1690000円

pachinkoMachine2.usedBall(100);//400000円使用しました。残りのお金は: 600000円
pachinkoMachine2.addMoney(1000000);//1000000円追加されました。現在のお金は: 1600000円

お、なぜかそれぞれ保存されています。Pachinko関数は一つだけなのに、、

ではレキシカル環境を見ていきましょう。
スクリーンショット 2024-07-13 18.56.40.png

なかなかにえげつないですね。もう二度と作りたくはありません。

そうです、これをみたらわかる通り、新しくレキシカル環境を作り、別のクロージャとして保存しているのです!

なので、後からアクセスしたとしてもそれぞれの変数はクロージャごとに保存されているのです!!

(今回はわかりやすいようにPachinko2としていますが、Pachinko(インスタンスは100万)のような表記の方が相応しいと思われます。)

また、何を動作をしていない時でもみれるような関数getMoneyも作っておきましょう。後で役に立ちます。

function Pachinko(money) {
  let Kaiji_money = money;
  return {
    usedBall: function (amount) {
      if (Kaiji_money - amount * 4000 <= 0) {
        Kaiji_money = 0;
        console.log("お金がつきました。補充してください。");
      } else {
        Kaiji_money -= amount * 4000;
        console.log(
          amount * 4000 + "円使用しました。残りのお金は: " + Kaiji_money + ""
        );
      }
    },
    addMoney: function (amount) {
      Kaiji_money += amount;
      console.log(
        amount + "円追加されました。現在のお金は: " + Kaiji_money + ""
      );
    },
    getMoney: function () {
      console.log("現在のお金は: " + Kaiji_money + "");
      return Kaiji_money;
    },
  };
}

const pachinkoMachine = Pachinko(1090000); //残金を投入
pachinkoMachine.getMoney();//現在のお金は: 1090000円

うまく動作していますね。

そして、、なんとカイージらはパチンコに勝ち、それぞれ2億円ずつ持ち帰ることにしました。

Eカード:トネーガワとの最終決戦

2億を手にしたカイージ、そんな時、トネーガワからその2億を賭けないかと話を持ちかけられました。もちろんEカードです。

多分原作を読んだことない方々はここまで来てもう飽きてギブアップしてると思いますが、一応Eカードについて解説しておきます。

皇帝側(インペラーサイド)と奴隷側(スレイブサイド)の2つの立場に分かれてプレイします。各プレイヤーは、それぞれ異なる種類のカードを持っています。

スクリーンショット 2024-07-11 3.15.20.png

カードの選択と配置:各プレイヤーは、自分のデッキから1枚のカードを選び、同時に裏向きで場に出します。

スクリーンショット 2024-07-11 3.18.56.png

両プレイヤーがカードを裏返して公開し、勝敗を決定します。
その時の勝敗は以下のように決まります。
皇帝 vs 市民:皇帝の勝ち  市民 vs 奴隷:市民の勝ち
奴隷 vs 皇帝:奴隷の勝ち  市民 vs 市民:引き分け

スクリーンショット 2024-07-11 3.25.04.png

この勝負が決まるまで繰り返し続けます。

やってみないとわからないので、まずは試してみましょう。

Resultを押して、0.5x(倍率)にすると見やすいです。

See the Pen Untitled by shimaf4979 (@shimaf4979) on CodePen.

これのJavaScriptはどうなっているのでしょうか。

見てみましょう。(今回は、手札の保持とどちらが勝利するかのコードの説明のみで、DOM等には触れません。あらかじめご了承ください。)

createEcardGame.js
function createECardGame() {
 let player1Deck = { emperor: 1, citizen: 4 };
 let player2Deck = { slave: 1, citizen: 4 };

 return {
   drawCard: function (player, card) {
     if (player === 1) {
       if (card === "emperor" && player1Deck.emperor > 0) {
         player1Deck.emperor--;
         return "emperor";
       } else if (card === "citizen" && player1Deck.citizen > 0) {
         player1Deck.citizen--;
         return "citizen";
       }
     } else if (player === 2) {
       if (card === "slave" && player2Deck.slave > 0) {
         player2Deck.slave--;
         return "slave";
       } else if (card === "citizen" && player2Deck.citizen > 0) {
         player2Deck.citizen--;
         return "citizen";
       }
     }
     return null;
   },
   playRound: function (player1Card, player2Card) {
     if (player1Card === "emperor" && player2Card === "slave") {
       return "Player 2 wins";
     } else if (player1Card === "slave" && player2Card === "emperor") {
       return "Player 1 wins";
     } else if (player1Card === "emperor" && player2Card === "citizen") {
       return "Player 1 wins";
     } else if (player1Card === "citizen" && player2Card === "slave") {
       return "Player 2 wins";
     } else if (player1Card === player2Card) {
       return "Draw";
     } else {
       return "error";
     }
   },
   getCardCount: function () {
     return {
       player1: { ...player1Deck },
       player2: { ...player2Deck },
     };
   },
 };
}

このゲームの始め方は以下のような感じです。

// Eカードゲームのインスタンスを作成
const eCardGame = createECardGame();

// カードを引く
const player1Card1 = eCardGame.drawCard(1, "citizen");
const player2Card1 = eCardGame.drawCard(2, "citizen");

// ラウンドをプレイ
const result1 = eCardGame.playRound(player1Card1, player2Card1);
console.log(result1); //Draw

// カードの残り枚数を確認
console.log(eCardGame.getCardCount()); 
//{player1: {emperor: 1, citizen: 3}, player2: {slave: 1, citizen: 3}}

const player1Card2 = eCardGame.drawCard(1, "emperor");
const player2Card2 = eCardGame.drawCard(2, "slave");

const result2 = eCardGame.playRound(player1Card2, player2Card2);
console.log(result2); // 'Player 2 wins'

// カードの残り枚数を確認
console.log(eCardGame.getCardCount());
//{player1: {emperor: 0, citizen: 3}, player2: {slave: 0, citizen: 3}}

図でも書いておきます。

スクリーンショット 2024-07-11 15.06.16.png

今回は前回と違い、引数が二つあることでわかりずらいように見えますが、やっていることは同じですね。(anonymousではないのは処理中だからということで見逃してください。)

スクリーンショット 2024-07-13 19.04.12.png

ここで、トネーガワがズルを仕掛けてきました。

スクリーンショット 2024-07-11 16.58.35.png

なんと卑怯な、これでは負けかねません。

eCardGame.player1Deck.emperor = 0;

すると、以下のような結果になりました。

ypeError: undefined is not an object (evaluating 'eCardGame.player1Deck.emperor = 0')

どうしてでしょうか。

上で、player1Deckplayer2Deckというデータは、createECardGame 関数のスコープ内に閉じ込められています。これらのデッキは関数外から直接アクセスすることはできないのです!

また、getCardCountdrawCardplayRoundという内部関数も同様に関数のスコープ内に閉じ込められており、外部から直接呼び出すことはできません。

しかし、内容を知りたい時もありますよね、そういう時のためにgetCardCountがあるのです。

こうして、データとそれに関連する操作を一つの単位(オブジェクト)にまとめ、外部からの不正なアクセスを防ぐことを、カプセル化といい、その時に使われる外部からの参照を禁止する変数をプライベート変数といいます。

スクリーンショット 2024-07-13 19.03.58.png

クロージャの中にクロージャ(ネストクロージャ)

今回は以上となります。やっと次回、thisの挙動についてかけそうです。

ここまでどうしてレキシカル環境について説明してきたか、その理由がわかると思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0