どうも。つじけ(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メソッドに依存するのは避けよう」 をお届けします。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会4]固定レシーバを持つメソッドを抽出するにはbindを使う
- [EffectiveJavaScript輪読会4]関数をカリー化するには、bindを使う
- [EffectiveJavaScript輪読会4]コードをカプセル化するには、文字列ではなくクロージャを使う
- [EffectiveJavaScript輪読会4]関数のtoStringメソッドに依存するのは避けよう
- [EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう
- [EffectiveJavaScript輪読会4]prototype、getPrototypeOf、__proto__の違いを理解する
- [EffectiveJavaScript輪読会4]__proto__は決して変更しないこと