chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

JSを勉強しなおすため、O'REILLYのJavaScriptを買った→クロージャは分かった気になったし、JSが好きになった

背景:プログラミングを学ぶ人の支援活動として、JSの学習支援を行うことになった*1
課題:正直に言うとJSがあまり好きではなく、避けてきた。正しく理解できていない。最低限説明できるレベルまで理解する必要あり
アプローチ:別件でIoT教材の再実装が必要なのだが、JSを使って実装することで開発を通じて言語仕様を学ぶ。また、JS解説本を買って一通り読む

詳細:
他人に提供する予定のソフトにおいてはできるだけ正しくコーディングしたいと思い、JSの理解のためにO'REILLYのJavaScriptを買った。最初本屋でこの本を見た時は分厚すぎて、これは買わないなーと思ってその時は買わずに帰ったのだが、JSでプログラミングを始めるといろいろ不明な点が出てきた。このJSには、コーディングスタイルとか、本来どのように実装するべきなのか?まで細かく説明してくれているので買う価値があると判断した。

ちょうど夏休みということもあり、サイの表紙のJS本をパラパラと読みながら勉強中です。筆者の章立ての妙なのか、訳者の腕によるのか、詰まったり飽きることなく、一筆書き的にすんなり読めます(なお、深い記述は飛ばしてます)。この本で初めて、JSでの正しい比較は ===であると知りました。また、最初に出会った言語がBasicやCだった自分にとって、モダンなクロージャも苦手だったのですが、この本のクロージャの説明を読んで自分なりに腑に落ちました。今後クロージャで??となった時のために現時点での自分の理解をまとめておきます。本からかなりそのまま引用しています(引用元:JavaScript第7版 P224 8.6クロージャ)。

クロージャ・・・・関数オブジェクトと関数の変数の名前解決に使われるスコープを組み合わせたもの
(関数が定義された時に有効であったスコープが変数スコープとして管理される(スコープチェーンへの参照))

クロージャのふるまいを理解するため、本のサンプルをベースに試作しました。nodejsで動作確認済み

#!/usr/bin/node

let scope = 'global scope';
global_reporter = () => {return scope;}

///////////////////////////////////////////////////////////////

console.log("-------- closure test No1 ----------");

function check_scope(){
   let scope = 'local scope';
   function f() { return scope; }
   return f();  // returns value of scope
}

console.log(check_scope());      // -> local scple
console.log(global_reporter());  // -> global scppe


///////////////////////////////////////////////////////////////

console.log("---------- closure test No2 ----------");

function check_scope2(){
   let scope = 'local scope';
   function reporter() { return scope; }
   function changer(msg) { scope = msg; }
   return [reporter, changer];
}

const [rep, chg]=check_scope2();
console.log(chg);                   // -> [Function: changer]
console.log(rep);                   // -> [Function: reporter]

console.log(rep());                 // -> local scope
chg('local scope var is changed');  
console.log(rep());                 // -> local scope var is changed
console.log(global_reporter());     // -> global scppe


///////////////////////////////////////////////////////////////

console.log("---------- closure test No3 ----------");

function check_scope3(){
   let scope = 'local scope';
   return [() => { return scope; }, (msg) => { scope = msg; }]
}

const [get, set]=check_scope3();
console.log(get);                     // -> [Function]
console.log(set);                     // -> [Function]

console.log(get());                   // -> local scope
set('val of local scope is modified by set function');
console.log(get());                   // -> val of local scope is modified by set function
console.log(global_reporter());       // -> 'global scppe'


///////////////////////////////////////////////////////////////

console.log("---------- closure test No4 ----------");

function check_scope4(scope){
   return [() => { return scope }, (msg) => { scope = msg}]
}

const [f1,f2]=check_scope4(true);
console.log(f1);                    // -> [Function]
console.log(f2);                    // -> [Function]

console.log(f1());                  // -> true
f2('var of local scope is modified by set function(test4)');
console.log(f1());                  // -> var of local scope is modified by set function(test4)
console.log(global_reporter());     // -> global scppe

実行結果は以下。一応理解した通りの結果にはなっている。今後もっと難しいコードに出くわすかもしれないが、、今の段階では上記コードと実行結果は腑に落ちている。

-------- closure test No1 ----------
local scope
global scope
---------- closure test No2 ----------
[Function: changer]
[Function: reporter]
local scope
local scope var is changed
global scope
---------- closure test No3 ----------
[Function]
[Function]
local scope
val of local scope is modified by set function
global scope
---------- closure test No4 ----------
[Function]
[Function]
true
var of local scope is modified by set function(test4)
global scope

曲解しがちな自分の反省
Pythonに最初に出会った時、インデントでスコープが決まると知ってキワモノ言語と曲解して使うのを避けていたのですが、必要に迫られてPythonを使いだしてその簡潔さ圧倒されました(そしてそれ以来、速度が問われないプログラムは全部Pythonになりました)。

JSに対しても、勉強を始めた当時JSの文法が納得できず*2、また、「JSとはWeb画面でダイアログ出したりDOM操作用の言語」と曲解しており、苦手意識もあって強く避けてきました。ですが、この本を読んで、それは古い時代のJSであり、ES2015(ES6)で仕様が美しくなり、毎年言語仕様が見直されるぐらいにホットな言語であると再認識しました。仕様の曖昧性が気に入らなかったら、strictモードで厳しく叱ってもらうとか、AltJSとしてTypeScriptを選ぶという手段もあるわけで・・・・
この本のお陰で、自分のJSに対する認識が10年以上前から止まったままと認識しました。ちゃんと勉強しなおすと、(実際はTypeScriptを使うかもですが) JSがかなり好きになりました。ただ、、thisのワナとかまだまだ手ごわい所もありそうです。JSの強みを活かしたプログラミングのためには、非同期処理も正しく理解しないとなとは思っています(当分その局面にはならないだろうけど)

■追記
JSの===(厳密な等価性)とは何なのか??
pythonのisはオブジェクトIDの一致を確認する。sliceで切り出した文字列と別の変数の文字列では同じ値でもオブジェクトのIDが異なるのでisによる判定ではFalseとなる。これはまぁ分かる

x = "aaa"
msg = "asfsdfasaaasadfsdfsadf"
msg[8:11]          # -> 'aaa'
x == msg[8:11]     # -> True
x is msg[8:11]     # -> False

id(x)              # -> 123145299899312
id(msg[8:11])      # -> 123145299900016


JSの===は何に基づき比較する??

x = "aaa"
msg = "asfsdfasaaasadfsdfsadf"
msg.slice(8,11)           // -> 'aaa'
msg.slice(8,11) === x     // -> true

MDNのサイトから引用
=== による厳密な等価性
厳密な等価性は、2 つの値が等しいか比較します。比較対象の値はどちらも、比較する前に別の値へ暗黙のうちに変換されることはありません。値が異なる型の場合、それらの値は等しくないとみなします。値が同じ型で数値ではない場合、同じ値であれば等しいとみなします。
まるめると、、===による比較とは、オブジェクトIDやオブジェクトのアドレス等による同一性確認ではなく、型変換しない==であると理解 (PythonのisやLISPのeqではないと)

■参考URL
等価性の比較と同一性 - JavaScript | MDN

Node.js | Socket.IOでリアルタイム双方向通信 - わくわくBank

*1:pythonの学習支援だったらよかったけど、世の中そんなにうまくはいかないだろう

*2:「変数宣言はしてもしなくてもいい」仕様とか、「オブジェクトは {} で表現」といった点でメゲました