[EffectiveJavaScript輪読会4]コードをカプセル化するには、文字列ではなくクロージャを使う

GAS

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

前回のおさらい

前回は、「関数をカリー化するには、bindを使う」をお届けしました。

今回は、「コードをカプセル化するには、文字列ではなくクロージャを使う」 をお届けします。

テキスト第3章「関数の扱い」の項目27に対応しています。

※今日の項目は、答えではなく、読み物としてお楽しみください。

今日のアジェンダ

  • evalってなに
  • evalとクロージャ
  • ギブアップ
  • コードをカプセル化するにはクロージャを使う

evalってなに

ほんとうに一瞬だけ、evalについて触れさせてください。すぐ戻ってきます。

evalとは、eval(string) と書いて、stringをJavaScriptのコードとして評価するものです。

暗黙の型変換をしない、というと馴染みが出てくるかもしれませんが、もしかしたら「へ~それってコンパイルじゃん」という方もいるかもしれません。

evalでFizzBuzzを書いてみたので、お時間のある方は実行してみてください。

function myFunction4_27_00() {

  //暗黙の型変換をしない
  console.log(eval('2 + 2')); //4

  //コンパイルした気分
  console.log(eval('function myFunction(){return "my name is eval"};myFunction()')); //my name is eval

  //FizzBuzz
  console.log(eval('function fizzBuzzOneLiner1(){for(let i=1; i<=100; i++){console.log((i%3 ? "":"fizz")+(i%5 ? "":"buzz")||i);}};fizzBuzzOneLiner1();'));

}

今日のお話は、後で再利用するために、コードを文字列にしておくのか、関数にしておくのかという話

function myFunction4_27_01() {

  function repeat(n, action) {
    for (let i = 0; i < n; i++) {
      eval(action);
    }
  }

}

evalとクロージャ

文字列と関数の違い、それはクロージャかどうかです。

一瞬だけクロージャの復習をしましょう。

クロージャとは、スコープチェーンを遡って変数を参照する仕組みを持っている関数のことです。

以下では、関数makeはクロージャです。

function myFunction2_11_04() {

  function makeSandwich() {

    let vegetable = 'レタス';
    function make(topping = 'なし') {
      return vegetable + 'and' + topping;
    }

    return make;
  }

  const sw = makeSandwich(); //戻り値に関数makeを格納している
  console.log(sw); //[Function: make]

  console.log(sw()); //レタスandなし
  // console.log(sw('トマト')); //レタスandトマト

}

テキストには「evalは文字列の中で参照を見つけたら、グローバル領域を見にいく」と書かれていました。

それが、クロージャではないという意味なのか、わたしの理解力では答えがでませんでした。

考えに考えた結果、evalで評価された文字列も、クロージャだと思います。。。。(泣)

function myFunction4_27_02() {

  function repeat(n, action) {
    for (let i = 0; i < n; i++) {
       let SALES = 0;
      eval(action);
    }
  }

  const textFunction = `
  function myFunction(){
    const latestSaleReport = SALES + "億ドル";
    console.log(latestSaleReport)};
    myFunction()`;

  repeat(3, textFunction); //0億ドル 0億ドル 0億ドル

}

ギブアップ

ここでギブアップでした。違うことを考えて気を紛らわします。

グローバル領域(厳密にはローカルですが、ここはグローバル領域と読み替えてください)に変数を置けば、誰でも変数を利用できて便利でしょ。と。

function myFunction4_27_03() {

  let SALES = 100;
  function repeat(n, action) {
    for (let i = 0; i < n; i++) {
      eval(action);
    }
  }

  const textFunction = `
  function myFunction(){
    const latestSaleReport = SALES + "億ドル";
    console.log(latestSaleReport)};
    myFunction()`;

  repeat(3, textFunction);


}

だけど、関数のなかに変数をいれてしまったら、みんなが使えなくなってしまう(100億ドルで固定)でしょ。と。

function myFunction4_27_03() {

  function repeat(n, action) {
    for (let i = 0; i < n; i++) {
      eval(action);
    }
  }

  const textFunction = `
  function myFunction(){
  let SALES = 100;
    const latestSaleReport = SALES + "億ドル";
    console.log(latestSaleReport)};
    myFunction()`;

  repeat(3, textFunction); //100億ドル 100億ドル 100億ドル

}

コードをカプセル化するにはクロージャを使う

だから、ソースコードは文字列として渡さないんだ。

evalなんか使わないで、関数として渡すんだ。

関数はクロージャだから、「適切なスコープチェーンを参照する」ようにしよう。

というお話でした。

function myFunction4_27_04() {

  let SALES = 100;

  function repeat(n, action) {
    for (let i = 0; i < n; i++) {
      console.log(action());
    }
  }

  const getLatestReport = () => {
    const latestSaleReport = SALES + "億ドル";
    return latestSaleReport;
  }

  repeat(3, getLatestReport); //100億ドル 100億ドル 100億ドル

}

まとめ

以上で、「コードをカプセル化するには、文字列ではなくクロージャを使う」をお届けしました。

evalは使うなというのは有名です。項目17のevalは輪読会でも対象外です。

今日は、そのevalを考察しました。

実は、ちょっと楽しかったです。てへ。

次回は、「関数のtoStringメソッドに依存するのは避けよう」 をお届けします。

参考資料

eval MDN

このシリーズの目次

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