第1回 はじめてのJavaScript – 言語仕様を読み解く

2013.05.08kickbaseCreatorsMeetUp | JavaScript


本記事はデザインサテライツが主催している勉強会、第3回 Creators MeetUpで発表した資料とその補足となります。
この発表をしようと思った背景として、「JavaScriptはその歴史から長い間誤解をされ続けてきた言語であり、体系的に学ぶ機会が少なかった」と常々思ってたことが挙げられます。

本格的なWEBアプリの需要が高くなり、優れたユーザー体験の傍らに必ずJSの存在がある現在、その仕様に向かい合う時期は今しかないと思っています。
便利なライブラリ紹介などはありませんが、フロントエンドエンジニア求められる言語に対する深い知識を共に学ぼうというのが本連載の主眼となります。
言語仕様については解釈が様々だと思いますので、ご指摘やご助言についてはTwitter ID @kickbaseまでご連絡いただければ幸いです。

▼スライド
第1回 はじめてのJavaScript – 言語仕様を読み解く

JavaScriptの簡単な歴史

  • JSにはブラウザごとに独自の拡張がある
  • ECMAScriptは標準化されたJSの基礎を成す言語
  • 本日現在ECMAScriptは第5版
  • 策定中のECMAScript6は強力な言語仕様になりそう
  • 見送られたECMAScript第4版が今後の鍵に?
  • 本連載記事ではJavaScriptに焦点をあてて学んでいきますが、開発効率や保守性の観点からJSへのトランスコンパイル言語を選択するケースも増えてきているかと思います。最終的に本連載でもそれらの言語を紹介することになるかと思いますが、「ECMAScript第4版」がキーワードになると個人的に思っています。

    値渡しと参照渡し

    var num1 = 1;
    var num2 = num1;
    num1 = 2;
    
    console.log(num1); //2
    console.log(num2); //1 (2行めで値を代入時にコピーしたので、大元のnum1が変更されても関知しない)
    
    var obj1 = {x: 1, str: 'hoge'};
    var obj2 = obj1;
    obj1.x = 2;
    console.log(obj1); //{x: 2, str: 'hoge'}
    console.log(obj2); //{x: 2, str: 'hoge'} (obj2はオブジェクト{x: 2, str: 'hoge'}への参照(リンク)を持っているだけなので、大元が変更されると反映される)
    

    データ型には「基本型」と「参照型」に大きく分類され、変数代入の際、基本型では値はコピーされ(値渡し)、参照型では参照される(参照渡し)
    値の代入は簡単に言うと下記の2パターンに分類されます。

  • 値渡し : 代入時にコピーされ、大元の変更は関知しない
  • 参照渡し : 参照元へのリンクを持っている状態(大元が変更されると自身も変更される)
  • プロパティへのアクセス

    プロパティアクセスの方法はドット演算子とブラケット演算子がありますが、なぜ2つ用意されているのでしょうか。
    特に気をつけなければいけない、「ブラケット演算子のみOKとなる場合(ドット演算子ではNGとなる場合)」について見ていきます。

    ▼プロパティ名にハイフンが含まれている場合(識別子に使用できないプロパティ名の場合)

    var obj1 = {'hoge-moga': 100};
    //console.log(obj1.hoge-moga); //エラー
    console.log(obj1['hoge-moga']); //OK
    

    プロパティ名にハイフンが含まれている場合、ドット演算子を使用するとJSはそれを「マイナス記号」なのか「プロパティ名の一部」なのか判断できないためエラーとなります。
    プロパティ名にハイフンが含まれている場合は、ブラケット演算子を用いて明示的にStringの一部であることをJSに伝えてあげる必要があります。

    ▼プロパティ名が数字の場合(識別子に使用できないプロパティ名の場合)

    var arr = [5, 10, 15];
    //console.log(arr.0); //エラー
    console.log(arr[0]); //OK
    

    おなじみの配列オブジェクトへのプロパティアクセスにブラケット演算子を用いるのはこれが理由です。

    ▼変数の値をプロパティ名に使用する場合

    var obj2 = {x: 1, y: 2};
    var key = 'x';
    
    console.log(obj2.key); //ドット演算子だとundifined
    console.log(obj2[key]);
    
    //同じ理由でfor-inの場合もブラケット演算子でアクセスする
    for (key in obj2) {
    console.log(key, obj2[key]);
    }
    

    変数の宣言とスコープ

    ▼スコープチェーン

    var x = 1; //グローバル変数
    
    (function () {
    	  console.log('x:' + x); // 出力結果は'x:1' (ローカル変数がないのでグローバル変数を見に行く)
    })();
    
    (function () {
    	  var x = 10; //ローカル変数
    	  console.log('x:' + x); // 出力結果は'x:10'
    })();
    

    詳細は割愛しますが、スコープチェーンはJSの基礎となる概念です。必ずマスターしましょう。簡単に言うと
    「小さなスコープから変数を探し、ない場合により大きなスコープで変数を走査する」という特性です。
    ローカル変数内で見つかればそれを使用し、見つからない場合外側のスコープ(関数の外側)へ変数を探しに行くといったものです。

    ▼関数宣言の暗黙の移動

    var x = 1; //グローバル変数
    
    (function () {
    	  console.log('x:' + x); // なんとundefinedに! (1)
    	  var x = 5; // (2)
    	  console.log('x:' + x); // 想定通りローカルを見に行って5を返す
    })();
    

    ここで問題です。(1)の出力結果がundefinedになる理由はなぜでしょうか?
    僕がはじめてこのコードを見た時には、「(1)の段階ではローカル変数は宣言していないから、スコープチェーンを使ってグローバル変数である’x:1’が出力されるだろう」と思っていました。
    実は、このコードは下記コードと等価になるのです。

    var x = 1;
    
    (function () {
    	  var x; // 関数中にした変数の宣言は、関数の先頭に暗黙的に移動させられる
    	  console.log('x:' + x); // 当然宣言しただけなのでundefined。
    	  x = 5;
    	  console.log('x:' + x); // 代入済みなのでローカルを見に行って5を返す
    })();
    

    このように、JavaScriptでは関数中にある宣言文は暗黙的に関数の先頭に移動されるという特性があります。
    直感的でない挙動のため、慣れないうちはバグを生みやすいので注意が必要です。

    このため、JavaScriptではローカル変数は関数の先頭でまとめて宣言すると良いでしょう。

    参考サイト

  • どうなるECMAScript どうするActionScript | とんぶろ
  • 連載:Ajax時代のJavaScriptプログラミング再入門:第3回 変数の宣言とスコープ (1/4) – @IT
  • JavaScript 参照渡しと値渡しの罠 vol.1 – 真夜中のプログラミングTips
  • JavaScript Garden

  • ページトップへ