これだけ覚えるべきJSの基礎

atak edited this page Apr 9, 2017 · 17 revisions
Clone this wiki locally

「FEのためのJS基礎」シリーズについて

「JS基礎シリーズ」は、フロントエンドエンジニア向けのJavaScript(以下JS)の中でも、特に実践に必要となる情報のみを個人的にまとめたシリーズです。
元々は、数年前の開発環境のためにまとめたドキュメントなので、偏った内容や記述方法になっている場合があります。
最新のブラウザ環境でのみ動く、ナウなコードだけを学びたいという方のためのドキュメントにはなっていませんのでご了承下さい。

本シリーズで主に以下のようなJSの基礎をまとめています。

  • フロントエンドでよく使うJS表現
  • フロントエンドモジュール運用環境構築
  • フロントエンドでのJSデザインパターン

※最近ではBabelなどのツールを利用することでES2015での記述が可能になっています。ES2015の記述法については、ES2015について (ES6)をご覧ください。
※前提として、jQueryを触って簡単なDOM操作ができる程度のHTML/CSS/JSの知識を必要としています。

1.1 基本事項

Webサイトやアプリケーション内でなんのためにJSは利用されるのでしょうか?
JSを利用する目的は様々ですが、フロントエンドで利用する場合、ほとんどはDOMを操作することを目的とします。
実践的には、下記の5つの処理のために利用することが多いです。

  1. Webサーバ(API)とのデータのやり取り
  2. クリックなどのイベント処理
  3. DOM/URLの情報取得・処理
  4. DOMの操作・レンダリング
  5. Canvas/映像・音声/地図/LocalStrageの操作

これらの処理を問題なくこなせるようになることが、このシリーズの目的の一つです(5に関しては直接的には触れませんが…)。 まずは、本記事でJSに必須の概念である「オブジェクト」、「関数」、「即時関数」、「this」、「配列」、「制御構文」を理解しましょう。

1.1.1 JSのデータ型

本チャプターからJSの基礎の解説になります。
JSの基本的なデータ型(データの種類)は下記の4種です。

  • Number (数値)
  • String (文字列)
  • Boolean (true/false)
  • Object (オブジェクト、関数、配列など上記以外の複合データ型)

特殊なデータ型として以下の2種も存在しますが、最初のうちはあまり気にしないで良いかと思います。

  • Null
  • undefined

1.1.2 変数宣言

JSでは、変数の宣言にvarを利用します。
基本的なデータ型は多くの場合、変数として宣言・代入・参照されます。
※ES2016(最新のJSの仕様)では、varの変わりにletconstを利用してより細かい定義が可能になりました。

var string = 'String'; // 文字列
var five = 5; // 数値
var obj = {}; // オブジェクト
var func = function() {}; // 関数
func();
var array = []; // 配列

1.2 オブジェクト

オブジェクト(Object)は、値や関数をまとめて保持する箱です。

1.2.1 プロパティの追加・更新・参照渡し

オブジェクトで定義したプロパティが存在しなかった場合は、新たに定義され、既に存在した場合は上書きされます。
また、オブジェクトはコピーされることはなく参照渡しされます。

var aaa = {}
aaa.bbb = 'Bob';  // aaa['bbb'] = 'Bob'; とも書けます
aaa.bbb = 'John';
aaa.bbb // 'John'

var xxx = 'Hecker';
xxx.yyy = 'Tim';
var zzz = xxx.yyy; // 'Tim' xxxとzzzは同じオブジェクトを参照している

オブジェクトを定義する際に、同名オブジェクトがすでに存在する場合は上書きせず、存在しなかった場合のみ定義したいという場合はよくあります。その場合は以下のように書きます。

var obj = obj || {};

// 上記と同じ意味
if(obj){
  var obj = obj;
}else{
  var obj = {};
}

この記述法であれば、変数を上書きして初期化する危険がなくなります。

1.2.2 オブジェクトリテラル

JSでは、オブジェクトリテラルで記述したプロパティは、全てパブリックな(どこからでも参照可能)値になります。
プライベートな(オブジェクト内でのみ参照可能)値を定義したいのであれば、メソッド(オブジェクト内のfunction)の内部で var で定義するくらいしか方法はありません。

var obj = {
     aaa: 'AAA', // プロパティ(常にpublic)
     method1: function() { // メソッド
       var bbb = 'BBB'; // varはprivate
     },
     method2: function() {
       this.ccc = 'CCC'; // メソッド内でthisはobjを指すのでpublic
     },
     obj1: {
       ddd: 'DDD'; //どこまで入れ子にしてもプロパティはpublic
     }
};

obj.aaa; // 'AAA'
obj.method1.bbb // undefined
obj.method2.ccc; // 'CCC'
obj.obj1.ddd; // 'DDD'

※オブジェクトリテラルで書かれたキーとバリューの組み合わせをプログラミング用語で連想配列と呼ばれます。rubyでは Hash と呼ばれる概念ですが、JSでは単なるオブジェクトの一種として認識されています。

1.2.3 プロパティの追加と削除

プロパティはオブジェクトの定義後にも自由に追加・削除が可能です。

var obj = {};
obj.aaa = 'Eric'; // 追加
delete obj.aaa; // 削除

1.3 関数

関数はもらった引数を用いて記述した処理を行い return で指定された値を返すオブジェクトです。
しかし、JSの関数はスコープを使ったクロージャという特性があるために非常に多岐に渡る利用されます。
例えば、モジュールパターンのように、プライベートメンバを実装するためのオブジェクトの代替であったりします。
そのため function と書いてあるからといって全てが return で値を返すことが目的とは限りません。この関数の特徴がJSの基礎を理解するポイントの一つです。

1.3.1 即時関数

関数の記述は「定義」と「実行」の二段階があります。

var func = function() { // 定義
  return 'AAA';
};
func(); // 実行 'AAA'

上記の「定義」と「実行」を同時に処理する、「即時関数」という記述法があります。

var func = (function() { // 定義
  return 'AAA';
})(); // 実行 'AAA'

「即時関数」を使うことにより、関数はオブジェクトと同じ「値と関数をまとめて保持する箱」として利用することができます。つまり即時関数は、関数の見た目ですが実際はオブジェクトのように機能します。
更に、オブジェクトでは実現できなかったプライベートなプロパティを利用できるようになるため、オブジェクトよりも多機能になっています。

var obj = (function() {
  var x = x; // プライベートな変数宣言
  var y = y;
  var z = z;
  return { // パブリックAPI
    x: x,
    y: y
  };
})();
obj.x // 'x' パブリック
obj.y // 'y' パブリック
obj.z // undefined プライベート

1.3.2 関数内の「this」

1.3.2.1 メソッド呼び出しパターン

関数がオブジェクトのプロパティとして格納されている場合、それをメソッドと呼びます。
メソッドの中の this は格納されているオブジェクトにアクセスできます。

var obj = {
  val: 0,
  increment: function() {
    this.val += typeof inc === 'number' ? inc : 1; // thisはobj
  }
};
obj.increment();
1.3.2.2 関数呼び出しパターン

一般的な関数の呼び出しでは this はグローバルオブジェクト window `を指します。

var add = function (a, b){
  console.log(this); // addでなくwindow
  return a + b;
}
var sum = add(3, 4); // '7'

関数内で this を使いたい場合は thatitthis を退避させたりして、正しい this をバインドさせる必要があります。

var obj = {
  val: 0,
  increment: function () {
    this.val += typeof inc === 'number' ? inc : 1; // thisはobj
  }
};
obj.double = function () {
  var that = this; // ここでのthisはメソッド呼び出しなのでobjを指す
  var helper = function () {
    // ここでthisを使うとwindowにアクセスしてしまうのでthatを使う
    that.val = add(that.val, that.val);
  };
  helper();
};

1.3.3 関数のまとめ

関数はオブジェクトと異なり、スコープチェーンを利用したクロージャ機能を持っています。
そのため、関数として利用する以外にも、オブジェクトでは実現できなかったプライベート/パブリックを管理可能なオブジェクトとして使用することもできます。

this を利用する際は、メソッド呼び出しパターン(とコンストラクタ呼び出しパターン)以外の方法での関数内部での this の使用はやめましょう。
基本的に関数内でプライベートで一時的なプロパティを定義する場合は var を使います。
さらにプロパティをパブリックにしたい場合は、「即時関数」を利用して return で返します。
この特性を用いたデザインパターンは、「モジュールパターン」と呼ばれ、今後の記事で紹介していきます。

var func = (function () {
  this.aaa = 'AAA'; // thisはwindowを指すのでダメ!
  this.bbb = function(){ thiswindowを指すのでダメ!
   return 'BBB';
  };
  var ccc = 'CCC'; // プライベートプロパティ
  var ddd = function(){ // プライベートメソッド
   return 'DDD';
  };
  var eee = function(){ // プライベートメソッドだが、下でreturnされているのでパブリックにできる
   return 'EEE';
  };
  return { // パブリックにしたいプロパティ・メソッドはここでreturnする
   eee: eee
  };
})();
func.ccc; // undefined
func.ddd(); // undefine
func.eee(); // 'EEE'

スコープチェーンやクロージャをより詳しく知りたい場合は下記リンクを参照ください。

1.4 配列

配列は見た目はオブジェクトによく似ており、キー(index)が0からの整数で固定されているオブジェクトとして考えるとわかりやすいです。
また、オブジェクトと異なり、配列には配列操作のための独自メソッドが存在します。

1.4.1 配列の操作

まずは、配列の基本的なメソッドを紹介します。
配列の削除のみ、indexを正常に保つために splice を利用した特殊な方法になります。

var array = ['a', 'b', 'c', 'd'];
array[2]; // 'c' 3番目の配列を取り出す。
array.length // 配列の長さ
array.push('e'); // 配列に要素を追加
array.splice[2, 1]; // 配列の3番目の要素を削除、2番目の引数は削除する値の個数を指定するがほとんどの場合は1

配列操作の際に頻出する,配列の値を特定の条件で取り出す一番シンプルな方法は以下になります。
for を利用した方法でどんな環境でも利用できるので少し煩雑ですがいつでもすぐに使えるようにしておきましょう。

var array = ['a', 'b', 'c', 'd'];
var result = [];
var i;
for (i = 0; i < array.length; i+= 1) { //ここの記述方法はいくつかある
  if(array[i] === 'd'){ // 'd'にマッチするindexを取り出したい
    result.push(i);
  }
}
console.log(result); // [3]

このようにネイティブだけで汎用的なコードを書くとなると配列操作のロジックは少々煩雑になりがちです。
下記のようにもう少し、便利なメソッドがES5で追加されていますが、IE8以下の環境では利用できないという問題があるので利用する際は注意してください。

var array = ['a', 'b', 'c', 'd'];
// forEach()
var result1 = [];
array.forEach(function(val, index){
  if(val === 'd') result.push(index);
});
console.log(result1); // [3]

// map()
var result2 =array.map(function(val, index){
  if(val === 'd') return val;
});
console.log(result2); // [3]
var array = [0, 1, 2, 3];
// filter()
var resultFilter = array.filter(function(element){
  return element >= 2;
});
console.log(resultFilter); // [2, 3]

// reduce()
var resultReduce = array.reduce(function((previousVal, currentVal,){
  return previousVal +currentVal;
});
console.log(resultReduce); // 5

// indexOf()
var index = array.indexOf(2);
console.log(index); // 2

レガシーブラウザをサポートしつつ、もっときれいにソースを管理したいとなると、jQueryやunderscore.jsなどのライブラリに頼らなければなりません。
jQueryとunderscoreに関しては後の記事で紹介予定です。

1.5 制御構文

JSにはたくさんの制御構文が存在しますが、ここでは最低限必要な2つの制御構文のみを取り上げます。

1.5.1 if/else

一番利用機会の多いであろう条件分岐です。
以下のパターンを覚えておけばほぼ全てのパターンを網羅できるでしょう。

if(a === x){
  funcX(); // a = xの時
}else{
  funcY(); // それ以外の時
}

// 三項演算子(上記と同じ意味)
(a === b) ? funcX() : funcY();


// else ifも使えます
if(a === y || a === z){
  ...  // a = yもしくはa = zの時
}else if(a === y && b === z){
  ... // a=y b=zの時
}

1.5.2 for

条件分岐の次に利用機会の多い繰り返し処理です。

既に配列のチャプターで紹介済みですが、配列に対して繰り返し処理を行えます。
モダンブラウザではより使いやすい forEach() を利用できたり、jQuery/underscoreなどのライブラリでも each() が利用できるので、そちらを利用する機会の方が多いかもしれません。
しかし、パフォーマンスを気にする場合は for が有効です。 for の書き方でも最もパフォーマンスを発揮できる書き方は、デクリメントを利用した以下のようになります。

var i, array = [];
for (i = array.length; i--;){
     ...
}

今回はここまでです。
JSの特徴である「関数」の性質をしっかり捉えられればここまでは問題ないかと思います。
次回は、よく使うjQueryの機能についてまとめる予定です。

FEのためのJS基礎

  1. これだけ覚えるべきJSの基礎
  2. 現場で使うjQuery
  3. FEの実践的なJS頻出表現
  4. フロントエンド環境構築とnpm
  5. CommonJSモジュール運用
  6. JS小規模なView設計
  7. JSモジュール設計