はじめに
WEBアプリケーション開発において ブラウザ側 で何かをしたい場合、ブラウザで唯一動作する言語といっていい JavaScript を触らざるを得ません。ただ JavaScript は理解してしまえば難しくないものの、 サーバサイド側の言語と違う特徴 (特に関数まわり) があるため、慣れないうちは混乱することが多いのかなと思います。
私もまだ JavaScript を学び始めて日が浅く(本職はサーバサイド側のエンジニア)、また JavaScript での業務経験もそれほど豊富ではないのですが、 サーバサイドエンジニア側の視点 で、とっかかりとして これだけ知っていれば 理解が捗るだろうというものを挙げてみました。
ですので必ずしも網羅性はなく、ブラウザに関する機能(クライアントサイドAPI)についても触れていません(対象はコアJavaScript言語《ECMAScript5相当》)。また入門サイトに載ってるような構文等については、他の言語の経験があれば容易に把握できるものなので、ここであえて触れていません。
この投稿の想定する対象者
- サーバサイド側のWEBアプリケーションエンジニア/インフラエンジニアの方
- JavaScript の経験が無いが、片手間でも JavaScript を触らざるを得ない状況がある
- もしくは、これから JavaScript を学ぼうとしている方
でも、きちんと学ぶなら
まずはこの本(俗に サイ本 と呼ばれているもの)を読むのが良いと思います。言語仕様がしっかりと書いてあります。ただ縦に立つぐらい分厚いので、私もまだ、ちゃんと読めていないですが..
ちなみに、この投稿で出てくる用語は、基本的にこのサイ本に準じています。(サイ本に無い用語も使っています。例:無名関数)
また、この投稿の内容について、言語仕様や歴史的経緯でいうと、それは違う、というものがあるかもしれませんが、上記のとおり初心者がざっくりイメージをつかむための分りやすさ優先ということで容赦ください^^;
変数と値の型
変数の型
変数に型はありません。変数の宣言は var = XXX
のみです。
拡張仕様については、ここでは触れません。
値の型
値には型があります。型の分類は次のとおりです。※サイ本に準じています
- 基本型
- 数値、文字列、論理値、null(値なし)、undefined(未定義)
- オブジェクト型 .. プロパティ(名前と値のペア)の集合体
- オブジェクト(例:
{key1:value1, key2:valu2, ..}
)- 関数(例:
function(){..}
) .. オブジェクトの一種。プロパティも持てる - 配列(例:
[1, 2, 3, ..]
) .. こちらもオブジェクトの一種。プロパティも持てる - ほか
- 関数(例:
- オブジェクト(例:
JavaScript で注意することは、 関数は値 ということです。
(振る舞い への参照が、変数に代入されるイメージ。詳細は後述します。)
また、基本型とオブジェクト型の違いとしては、基本型はメモリ上に 値そのもの を保持しているのに対し、オブジェクト型は値の アドレス(参照) を保持している、ということです。それが何に関係するのかと言うと、他の変数に代入した際に違いがあります。
var num1 = 999;
var num2 = num1; // num2 には値そのものが渡されている
num1 = 0; // num1 の値を後から変更
console.log(num1); // 0 が出力される
console.log(num2); // 999 が出力される(num1 の変更の影響を受けていない)
var arr1 = [1, 1, 1];
var arr2 = arr1; // arr2 にはアドレスが渡されている
arr1[0] = 99; // arr1 の中身を後から変更
console.log(arr1); // [99, 1, 1] が出力される
console.log(arr2); // こちらも [99, 1, 1] が出力される(arr1 の変更の影響を受けている)
ちなみに上記で
arr1 = [..]
と書いてしまうと、 新しいメモリ領域 に配列の実体が生成され、arr1 にはその アドレス が格納される、という挙動なので注意してください。後でも出てきますがオブジェクト(hoge = {..}
)や 関数(hoge=function(){..}
)も同様です。
そんな代入しないよっと思うかもしれませんが、JavaScript では後述で説明する 関数 や オブジェクト で代入が多用されるので、その場面では アドレス(参照)が渡されている というのを理解しておく必要があります。
この投稿での「オブジェクト」について
広義には JavaScriptでは全てが オブジェクト といえるかもしれませんが、ややこしいので今回は単純に、次のようなハッシュや連想配列のようなデータを思い浮かべてもらえればよいかと思います。
var obj1 = {}; // 空のオブジェクト
var obj2 = {name:"山田", age:32}; // プロパティ(名前と値のペア)を持つオブジェクト
関数
関数の定義は次のように記載しますが、
function myFunc() { // 引数があれば () の中に書く
console.log("hoge");
}
myFunc(); // hoge と出力される。ちなみに関数を呼ぶ際は、引数が無くても () をつける
関数名は、習慣的に先頭小文字の動詞で始めます。複数の単語で構成する場合は
_
で区切るか、もしくは上記の例のように後ろの単語の先頭を大文字にします。
JavaScirptの場合は『変数と値の型』の項でも書いたように 関数は値 ですので、次のようにも書けます。(むしろ、こっちの書き方の方がよく見かけると思う。)
var myFunc = function() {
console.log("hoge");
};
myFunc();
これは変数に 実行結果 でなく 振る舞い(へのアドレス) が代入されている、と表現できるかと思います。
その①と②の間で基本的に違いは無いですが、その②はJavaScriptファイルでこの定義以降でしかこの関数が利用できないに対し、その①はJavaScriptファイルのどこでも(定義前でも)この関数を利用できます。キーワードだけ挙げておくと ホイスティング(巻き上げ) です。
関数を利用する目的
他の言語と同様に何度も使うコードを共通化することでであったり、長いコードを書く場合に見通しを良くするためにコードを関数へ外だしする、というのが一般的にあるかと思います。
ですが JavaScriptの場合は、他の関係ないコードから触られたくない 雑多な変数を隠蔽する という目的(詳しくは『変数のスコープ』の項で説明します)と オブジェクトの振る舞いを表現する という目的(これも後述します)でも作られるということに留意する必要があります。でないと、なんでここで function
が出てくるの??となってしまいます。
関数の戻り値
関数の戻り値はあっても無くてもよいです。(定義しない場合は、undefined が返される。)もし戻り値を返す場合は、次のように return
で返します。
var myFunc = function(num1, num2) {
return num1 + num2;
};
console.log(myFunc(4, 5)); // 9 と出力される
戻り値が"無い"関数を定義することに意味があるの?と思うかもしれませんが、JavaScript の場合は関数の中から 関数外部の変数やグローバル変数にアクセスできる ので、それらの操作が目的であれば戻り値がなくても事足りるためです。
(こちらも詳しくは『変数のスコープ』の項で説明します。)
ちなみにブラウザ上のエレメントもグローバル変数としてアクセスし操作できます。
また 関数 や オブジェクト 等も関数の戻り値となりえるのですが(これらも値なので)、return で受け取った側の挙動としては 新しいメモリ領域 に関数(またはオブジェクト)の実体が生成され、その アドレス が渡されている、ということに注意してください。
var myFunc = function(initName) {
return {
name: initName,
age: 32
};
};
var obj1 = myFunc("山田");
var obj2 = myFunc("鈴木");
console.log(obj1.name); // 山田 と出力される
console.log(obj2.name); // 鈴木 と出力される(obj1 と obj2 は別のオブジェクトになっている)
var myFunc = function(initName) {
return function() {
console.log(initName);
};
};
var func1 = myFunc("山田");
var func2 = myFunc("鈴木");
func1(); // 山田 と出力される
func2(); // 鈴木 と出力される(func1 と func2 は別の関数になっている)
関数内部に永続的な値は保持できない
関数は値 であるものの、呼び出される度に定義内のコードが上から下まで実行されてしまうので、何度も利用される関数でも関数内部に 状態を保持することは出来ない ので、状態を保持する必要がある場合は関数の外部に値を保存しておく必要があります。このあたりは他の言語と一緒です。
(グローバル変数に状態を保持してもいいのですが色々なところからアクセスされ推奨されないので、実際には後述の オブジェクト と組み合わせます。)
厳密には関数もプロパティとして値を保持できますが、ここでは触れません。
関数が"値"として利用される
何度も書きましたが 関数は値 ですので、ここまで何度か出てきたように変数へ代入することや関数の戻り値とすることも出来ます。混乱するかもしれませんが 実行結果 ではなく 振る舞い(へのアドレス) だけが渡されているとイメージすると良いかと思います。
また、次のようにオブジェクトのプロパティとすることも出来ます。(この書き方は頻出するので、ここで挙げました。)
var obj = {
name: "山田",
age: 32,
hello: function() {
console.log("Hello! " + obj.name) // 定義した時点では、まだ実行されない
}
};
obj.hello(); // Hello! 山田 と出力される。ちなみにメソッドを呼ぶ際は、引数が無くても () をつける
オブジェクトのプロパティとして関数が定義された場合は、それを メソッド と呼びます。
変数のスコープ
その変数が 関数の外側で定義されてる か 内側で新たに定義されている(=ローカル変数) かでアクセスできる範囲は変わります。
ざっくり言うと、
- 関数の中から外はアクセスできる
- 関数の外から中はアクセスできない
関数 = 車 で例えると スモークフィルムが張られている イメージでしょうか。(中から外は見える、外から中は見えない。)
もしくは 中の方が強い というイメージです。
var outNum = 33;
var myFunc = function() {
var inNum = 99;
〜 // 場所A:この位置からは、関数の外側の変数である outNum にアクセスできる。入れ子であっても大元までどこまでも辿れる
};
〜 // 場所B:この位置からは、関数の内側の変数である inNum にアクセスできない(そもそも見えない)
こういった性質があるので、JavaScript ファイルの大元に定義した変数(=グローバル変数)は、どこからも参照され値も変更できてしまいます。よってプログラムソースの規模が大きくなってくると グローバル変数の値が信用できない ということになっていきます。
そのため、他所から操作されたくない変数は 関数の中にローカル変数として内包して隠蔽 するという事をします。上記の例でも、場所A
から関数の外側の変数にアクセスできるものの、 他の関数の内部のローカル変数 にはアクセスできません。
アクセスできないんじゃ、本当に必要な時に関数内部の変数をどうやって利用するの?グローバル変数でやりとりするの?と思うかもしれません。
そのような場合は、returnで返したり、関数外部(または自身)のオブジェクトのプロパティとして戻したり、関数内部の別の関数経由でアクセスできるようにしたり ( 所謂 クロージャ と呼ばれるものですが、最初はキーワードだけ覚えておけばよいかと)します。
即時関数
次のように関数が (
と })()
で囲まれてるのを見かけるかもしれませんが、それは 即時関数 (または名前空間としての関数)と呼ばれるものです。
(function() {
var x = 10;
var y = 20;
console.log(x + y); // その場で 30 と出力される
})(); // 最後の行は }).call(this); と書かれていることもある
var num = (function() {
var x = 10;
var y = 20;
return x + y;
})();
// この位置の時点で num の値は 30 となっている
その名のとおり その場で関数が実行 されます。つまり例 その①
の場合だと、下記と同じ事をやっています。
function myFunc() {
var x = 10;
var y = 20;
console.log(x + y);
}
myFunc();
なんでわざわざ即時関数を使うの?という疑問があるかもしれませんが、その答えとしては、通常の例 その③
の書き方より記述量を抑えられるという理由のほかに、
- 後から呼び出すための関数名を定義する必要がないので、余分な関数をグローバルに公開しないで済む
-
例その③
でいうとmyFunc
-
- 関数なので、内部で利用する変数が ローカル変数 となり、関数の外部から 隠蔽 できる
-
例その①②
でいうとx
とy
-
上記のサンプルのように少ないコードだとあまりメリットが感じられないかもしれませんが、例えば数十行、数百行のコードだとすると、そのコード内で定義する変数(つまり値としての 関数 や オブジェクト 郡を含む)が 外部から不本意に操作されるのを防ぐ ことが実現できます。
ですので、JavaScriptのライブラリなどは、この即時関数の形式で提供されていることもあります。例えば JQuey のソースの頭出しはこんな感じです。
無名関数
これまでも何度か出てきましたが、次のように 関数名が無い 関数のことです。JavaScript ではよく出てきます。
var xxx = function() {
〜
};
関数名をつけてもいいのですが、特に後で利用する用途がなければ、わざわざつけるまでもない、といったところです。関数内部の再帰等で関数を再利用する場合は、関数名をつければいいです。
他の言語の場合は、関数を使いたい場合は名前で呼び出し、となるので不思議な感じではありますが、JavaScriptの関数の場合はこれまで見てきたように、関数へのアドレスが変数に代入されるので、その変数経由で振る舞いを利用できるから関数名をつけるまでもない、という感じかと思います。
オブジェクト
『変数と値の型』の項でも書いたように、次の形式で表現されるプロパティ(名前と値のペア)の集合体です。WEBアプリケーションを扱う場合、JavaScript ではこのオブジェクトが基本となるかと思います。
var obj = {
name: "山田",
age: 45,
hello: function() {
console.log(obj.name)
}
};
obj.hello(); // 山田 と出力される
console.log(obj.age); // 45 と出力される
上記は単純な例ですが、更にオブジェクトの階層をもったり、配列を定義したりと、より複雑な構造を表現することもできます。それぞれのプロパティとして 永続的な値 を保持することができます。
ブラウザで利用する JavaScirpt でこのオブジェクトが多用されるのは(言語仕様だからという理由だけでなく受け入れられているのは)、個人的には次のような理由であるという印象です。
- WEBアプリケーションのサーバサイドの場合はステートレス(状態が無い)なので、本格的に抽象化しない限りそれほど上記のようなオブジェクトは必要とならないが、ブラウザの場合はユーザ操作により 状態が変化 し、その状態を保持して次の操作に備える必要があるため。
- WEBアプリケーションのUIインタフェースを表現しやすい。
- 複雑な構造のデータを扱いやすい。形式も同じだし、JSONが重宝されるのと同じような理由。
また、変数に {key1:value1, key2:value2, ..}
の形式でデータが格納されればいいわけなので、次のように 関数で オブジェクトを作成することができます。
ユーザ定義オブジェクト、と呼びます。
function makeObj(lastName, firstName) {
// フルネームを生成する
var fullName = lastName + firstName;
// key:value 形式で return
return {
name: fullName,
age: 31
};
}
var obj = makeObj("山田", "太郎"); // obj の中身は {name: "山田太郎", age: 31} となる
上記のように関数を利用することで、引数を渡したり、オブジェクトを作る前段でいろいろ処理を書いたりすることができます。
(returnの度に新しいオブジェクトも作られるので、干渉し合わない複数のオブジェクトも生成できます。)
オブジェクトのプロパティとしてデータを保持できますし、メソッドとして振る舞いを定義できるので オブジェクト指向のクラスとインスタンス(のようなもの) が、このオブジェクトで表現できます。
コンストラクタ関数
単純なアプリケーションなら、上記のオブジェクトがあれば十分です。
(複雑なUIや複雑なモジュール構成、もしくは性能面でクリティカルな要件が無い限りは。)
ただ、きちんとオブジェクト指向ベースのプログラミングを行う為の コンストラクタ関数 と呼ばれるものがあり、これも既存のソースを読む場合によく出てくるので説明します。
まず先に、コードを示します。
var Person = function(lastName, firstName) {
// これはただのローカル変数の定義
var fullName = lastName + firstName;
// this は new された時のオブジェクトを指す。オブジェクトのプロパティとして name を定義
this.name = fullName;
// オブジェクトのプロパティとして age を定義
this.age = 31;
// オブジェクトのプロパティとして hello(振る舞い) を定義
this.hello = function() {
console.log(this.name);
};
// return は不要
};
var objA = new Person("山田", "太郎");
var objB = new Person("鈴木", "花子");
objA.hello(); // 山田太郎 と出力される
objB.hello(); // 鈴木花子 と出力される
コンストラクタ関数名は慣習的に(人間が通常の関数と区別するために)先頭を大文字とします。
コンストラクタ関数を利用する場合、上記のように new
を使います。new
で何が行われるかは次のようなイメージです。(あくまでイメージ。)
- 変数
objA
objB
のアドレスが指すメモリ領域に空のオブジェクト{}
が作成される。 - Person 内の記述が 頭から実行 される
- この時の
this
は、それぞれobjA
objB
となる -
this.XXX
でプロパティがセットされる
- この時の
.. 厳密にはプロトタイプうんぬんの話があるのですがここでは触れません。
最後の return の記載は不要です(return しなくてもプロパティは this.XXX
の箇所で関連づけ済み)。
ちなみに new
を記載し忘れると、this
がグローバルを指す ⇒ this.XXX
によりグローバルにプロパティがセットされてしまうので注意してください。
this
はその時に所属するオブジェクトにより動的に決定されます。例えば次のように書くと、this
はグローバルを指します。
var hoge = objA.hello;
hoge(); // 今回の場合は、グローバルに name という変数が無いのでエラーとなる
コンストラクタ関数を利用すると、本格的にオブジェクト指向のクラスっぽく活用して複数のインスタンスを作ったりプロパティを継承したりできますが、慣れないうちはきちんと使いこなすのは難しいので、(クリティカルな要件がなければ)自分で書くならまずはオブジェクトで十分、かと思います。
プロトタイプ? 継承? クラス? ○○チェーン? etc..
最初のうちは、ここまで知らなくても良いかと思います。もしくはきちんと学ぼうとする方でも、まずは他の基本的なことに慣れてから学習する、というスタンスでもよいかと。
きちんとこのあたりの事を知りたい方は、冒頭で紹介したサイ本を読むか、もしくはこちらのスライドが分りやすかったです。
⇒ 最強オブジェクト指向言語 JavaScript 再入門(Slideshare)
クロージャという言葉も良く耳にするかもしれませんが、実は既にこの投稿のサンプルソースでも似たようなものは出てきています。こちらにも書きました。
⇒ 簡単なカウンターで理解するクロージャ
(2014/09/17追記) この記事を書いた後に読んだのですが、こちらもおすすめ⇒ 開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質 薄いのでサクッと読めます。
ほか
たまに見かける $
は何?
jQuery ライブラリを使っている、ということです。HTMLのエレメント操作や、Ajax部分でよく利用されます。
また、よく既存のソースで次のような記述を見かけるかと思います。
$(function() {
〜
});
これは、例えばブラウザでサーバ上のコンテンツにアクセスした際、HTMLファイル、JavaScriptファイル、CSSファイルなどが順次ブラウザに読み込まれるのですが、上記の記述はブラウザで DOM構造の準備が終わったら実行する という意味です。
(でないと JavaScript からブラウザ上のエレメントを正しく解釈できない為。)
関数の引数に関数が指定されていることがあるけど?
関数は値ですので、引数としても渡せます。例えば次のようにコールバック関数を指定する際によくあります。
// 1秒後に hoge を出力
setTimeout(function() {
console.log("hoge");
}, 1000);
おわりに
少しでも、とっかかりの役に立てれば幸いです^^
〜以上〜