前回書いたエントリに関してid:donayama さん のこのはてブコメントで
forの中のvarは危険な香り
というフィードバックをいただき、さらに@ryugoo_さんからもTwitterでこんな感じで言及してもらいました
@h5y1m141 クロージャを使う必要はないと思いますが、 for 文中の var がマズいですね。あと気になったのは、 createTabElement 関数の宣言が巻き上げを期待した動きになっているところです。
— Ryutaro Miyashita (@ryugoo_) March 25, 2014
最近、CoffeeScriptで書いてて、素のJavaScript書いてなかったのと、ひな形となるアプリをベースに、コピペしながらコード書いてたから・・と書く単なる言い訳でしかないですが、いづれにしても書き方としてマズい所があったので、制御文のifを取り上げる前に、変数について今回説明することにします。
目次
- グローバル変数とローカル変数
- グローバル変数とローカル変数のイメージ
- JavaScriptの変数のスコープ
- そもそもブロックスコープがない
- 最後に
グローバル変数とローカル変数
ひとまずこれまで書いていたサンプルを一旦削除して、app.jsにこんなコードを書いたとします
var globalMessage = 'Global Hello'; alert(globalMessage); // (1) showMessage(globalMessage); // (2) alert(message); // (3) function showMessage(text){ var message = 'Hello'; alert(message); alert(text); alert(globalMessage); }
(1)から(3)の処理はそれぞれ以下のようになります
- alert()の結果として Global Hello が表示される
- 引数にglobalMessageが渡されてshowMessage()が実行される。showMessageの中ではalertが3つあり、最初に関数内で宣言された変数のmessageに代入されてる Hello が表示される。その次に引数textで渡された Global Hello が表示される。最後にグローバル変数として宣言された globalMessageに代入されてるGlobal Helloが表示される。
- 何も表示されない
グローバル変数とローカル変数のイメージ
グローバル変数とローカル変数のイメージとしてはこういう形です
グローバル変数
ローカル変数
ローバル変数は潜在的にどこかで変更される可能性があり、またプログラムの一部はそれに依存してしまう恐れがあるからである。グローバル変数はそれゆえ相互依存を生み出す無限の可能性を持っており、相互依存が高まることは複雑性を増大することにつながる。(ウィキペディアより引用)
という一文があります。上記記載したサンプル程度のコードなら、すぐに把握できるので大きな問題がないですが、それなりの規模のアプリケーションになると、使ってる変数が増えてきて、それがグローバルな変数だったりすると、上記引用したような問題が生じるかと思うので、必要最低限に抑えたほうが良いかと思います。
JavaScriptの変数のスコープ
先ほどまで、ローカル変数とグローバル変数について触れました
この話を踏まえて、先日私が書いたサンプルコードの一部を取り上げながら少し踏み込んで説明します
for(var i = 1; i < 6; i++){ var tab = createTabElement(i + '!!!'); tabGroup.addTab(tab); }
そもそもブロックスコープがない
このforループでのブロック内で、変数tabを宣言してます。これは指摘されるまで全く気づかなかったのですが関連しそうな情報をググっていたら
Java などの言語では、if や for などの {} で囲まれたブロックごとにもブロックスコープがありますが、JavaScript には存在しません。 JavaScript でどうしてもブロックスコープを使いたい場合は、with 命令を使う方法や、無名関数を定義と同時に呼び出すなどの方法で、擬似的にブロックスコープを作ることは可能です。 JavaScript のスコープを理解する より
というのを見つけました。なので、上記コードは
var tab; for(var i = 1; i < 6; i++){ tab = createTabElement(i + '!!!'); tabGroup.addTab(tab); }
というのと同じです。
ついでに@ryugoo_さんからもこんな感じで教えてもらいましたが、forループ内のカウンターとして利用してる変数iもループ外で宣言するのと同じとのこと。
@h5y1m141 いえ、 JS の言語仕様で、 for や if がブロックスコープを提供しないという話です。 http://t.co/pZpq74F6XJ JS のループ中で変数を宣言するのは、ループ外で宣言しているのと同じ事です。カウンタのために用意する i も同様ですね。
— Ryutaro Miyashita (@ryugoo_) March 25, 2014
前回書いたサンプルコードは以下のようにしておくべきでしたね ^^;
// 最初に変数宣言をまとめて行う。複数ある場合には 変数名の後カンマで区切れば続けて記述することが可能 var i,tab,tabGroup = Titanium.UI.createTabGroup(); Titanium.UI.setBackgroundColor('#000'); for(i = 1; i < 6; i++){ //(1) tab = createTabElement(i + '!!!'); // (2) tabGroup.addTab(tab); } tabGroup.open(); function createTabElement(titleNumber){ // 関数内の先頭箇所で変数宣言をまとめて行う。 var win,label,tab; win = Titanium.UI.createWindow({ title:"Tab" + titleNumber, backgroundColor:'#fff' }); label = Titanium.UI.createLabel({ color:'#999', text:'I am Window' + titleNumber, font:{fontSize:20,fontFamily:'Helvetica Neue'}, textAlign:'center', width:'auto' }); win.add(label); tab = Titanium.UI.createTab({ icon:'KS_nav_views.png', title:"Tab" + titleNumber, window:win }); return tab; };
最後に
上記のスコープについて調べてる中で、変数の巻き上げ(ホイスティング)も関連してきそうということで、そこについても文章を書きかけたのですが、非エンジニアな人におくるJavaScriptの基礎というテーマからすると、スコープが広がりすぎてしまうので、そこについて言及するのは止めておきました
もしも興味持たれた方がいましたら、調べてる中で以下3つが個人的に参考になったのでチェックしてみてください