JavaScriptとは、主にWebページに 動的な演出 を加えることを目的としたプログラミング言語です。HTMLがWebページの文書の構造を静的に記述するのに対して、JavaScriptは時刻やユーザーの操作に従い、HTMLの要素の内容を動的に変えることができます。JavaScriptは使われる場面によって更に細かく分類することが出来て、実際にユーザーが操作したり見ることが出来る、ブラウザ側の処理の部分を フロントエンドJS(クライアントサイドJS) と呼び、ユーザーからは見えない内部的なサーバー側の処理の部分を バックエンドJS(サーバーサイドJS) と呼びます。
プログラミング学習者の皆さんであれば、Javaと呼ばれるプログラミング言語があることはご存知でしょう。では、名前の似ているJavaとJavaScriptは何か関係があるのでしょうか?
答えは 全く関係ありません 。
JavaScriptは、1990年代にNetscape社によってLiveScriptという名前で開発されました。当時Netscape社と業務提携していたSun Microsystems社の開発したJavaという言語が大きく流行ったため、JavaScriptという名前に変更されました。そのため、JavaとJavaScriptは名前は似ていても中身は全く別物です。
JavaScriptは以前、Webブラウザ毎に実装が異なっていました。そのため仕様の差異が生まれ完全にどのブラウザでも動作するようなプログラムを書くことが困難でした。そのため標準化団体による標準規格の策定を求める声が高まり、標準化団体Ecma Internationalが、ECMAScript(エクマスクリプト)としてJavaScriptの標準を定めました。現在の多くのブラウザでは、EcmaScript5(ES5)が標準動作しています。本資料では、ES5を採用しています。EcmaScriptの最新バージョンは、ES6ですが年度毎にバージョンを更新することが決定されたので、正式にはES2015と表記されます。最終課題では、ES5ではなく、ES2015を採用する予定です。ES2015は、多くのブラウザでは動作しないため、ES5へ変換(トランスパイル)されて動作させます。現在トランスパイルには、Babelを使うのがメジャーとなっています。
この節では実際にブラウザ上で、基本的なフロントエンドJSの動作を確認します。Firefoxに標準搭載されている開発者向けのツールがあり、Ctrl+Shift+kをタイプすることで開くことが出来ます。HTMLの要素を視覚的に確認することが出来るインスペクタやJavaScriptを実行させたり、結果を確認することが出来るコンソール、デバッガ等が備えられています。今回は、コンソールをタブから選択し、一番下のフォームに
$ console.log(2*5);
とタイプしてみましょう。そうすると数式の結果がコンソール上に表示されるはずです。
続いて、HTML上でJavaScriptを記述して実行してみましょう。以下のようなファイルを作りブラウザで開いてみましょう。
$ firefox sample2-1.html
JavaScriptをHTMLに直接記述するには、scipt 要素を使い、子要素として、JavaScriptのソースコードを記述します。
~/sccp/web/sample2-1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
console.log("コンソールに表示されるかな?");
alert("アラート!");
</script>
</body>
</html>
そうすると次のような結果になるはずです。
console.logは、先ほどと同じで、コンソール上での表示を行う命令ですが、alertは、ポップアップにメッセージの表示を行う命令です。
続いて、変数の宣言と代入、console.logとalertの違いに触れていきましょう。変数は、varというキーワードの後に変数名を書き代入を行います。varキーワードを付けなくても変数を使用することは出来ますが、その場合グローバルスコープ(グローバル変数)となってしまい、どこからでも参照・変更が可能になってしまうためバグが起きやすくなってしまいます。なので基本的には、varキーワードを使用するようにしましょう。JavaScriptは、Ruby同様に動的型付け言語のため、型の宣言は要りません。次の例では、arrという変数に配列を代入しています。プログラムが記述できたらブラウザで開いてプログラムを確認してみましょう。
$ firefox ex2-2.html
~/sccp/web/sample2-2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
var arr = [1,2,3,4];
console.log(arr);
alert(arr);
</script>
</body>
</html>
配列を表示した場合、console.logの方がより詳細に型の情報や見せ方をしてくれているのがわかると思います。元々コンソールは開発者のためのログを出力する場所なので、プログラムのデバッグ目的で文字を表示する場合は、console.logを使用すると良いでしょう。
Node.jsとは、サーバーサイドJavaScript環境と呼ばれているものです。
JavaScriptを解釈し実行するものを JavaScriptエンジン と呼びますが、Node.jsでは、Googleが開発した V8 を採用しており、メモリ消費量が少なく、高速な処理がNode.jsの主な特長となっています。この節では、実際にjupyterNote(このノート)を使ってJavaScriptを実行してみましょう。このノートは実は、Node.jsを利用して実行しているため、Node.jsを使ったときと同様の挙動をします。また、このノートでは、ES2015が採用されています。しかし、ES2015は、ES5の文法と互換性があるため、ES5の文法でプログラムを記述しても問題なく動作します。基本的な操作はjupyterNoteのチュートリアルを参照してください。
フロントエンドJSとバックエンドJSの違いを知るために、今まで習った命令を試してみましょう。今まで習った命令はたった2つです。そうconsole.logとalertです。
console.log(2*4);
8
alert(2*4);
ReferenceError: alert is not defined at <anonymous>:1:1 at Object.Contextify.sandbox.run (/root/jupyter-nodejs/node_modules/contextify/lib/contextify.js:12:24) at Context.rawRun (/root/jupyter-nodejs/build/context.js:168:23) at /root/jupyter-nodejs/build/context.js:188:27 at b (domain.js:183:18) at Domain.run (domain.js:123:23) at Context.rawEvaluate (/root/jupyter-nodejs/build/context.js:186:9) at Context.execute (/root/jupyter-nodejs/build/context.js:333:21) at Kernel.executeRequest (/root/jupyter-nodejs/build/kernel.js:197:16) at Kernel.onShell (/root/jupyter-nodejs/build/kernel.js:123:14)
console.logは正常に動作したと思いますが、alertは、not definedというエラーが表示されました。これは、Node.jsがコンソール上で動くことを想定しているため、ブラウザ(HTML)に関する命令は存在していません。alertはブラウザのポップアップにメッセージを表示する命令のため、ブラウザ命令に該当します。その代わりNode.jsでは、ファイルを操作したりOSに関連するプログラムが動作するようになっています。JavaScriptを調べるときは、フロントエンドJSなのかバックエンドJSなのか、ES5なのかES2015なのかを注意深く確認することが大切です。
いくつかの基本的な文法を紹介していきます。本ノートは自由に変更して実行することが出来るので、Node.jsの文法と、その挙動を試しながら確認してみましょう。
// スラッシュ文字が2つ並んだ後の文字はすべてコメントになる。
/*
複数行に渡る場合は、スラッシュ+アスタリスク
コメント終わりは、アスタリスク+スラッシュで閉じる。
*/
変数宣言
var x; // 変数宣言
x = 0; // xに0を代入。
x; // xの値を評価する。
0
x = 1; //数値
1
x = 0.01; // 整数も実数も同じ数値型になる。
0.01
x = "Hello world"; // 文字列はダブルクォートで囲む。
"Hello world"
// シングルクォートでも文字列扱いになる。
// 中で記述する文字列にダブルクォートがある場合に有効。
x = '<p id="xxx">hoge</p>'
"<p id=\"xxx\">hoge</p>"
x = true; // 論理値。真。
true
x = false; // 論理値。偽。
false
x = null; // 特殊な値で「値がない」ことを意味する。
null
x = undefined; // 特殊な値で「値が未定義」ことを意味する。
var arr = [1, 2, 3, 4]; // 数値型の配列。
arr;
[Array] [1,2,3,4]
var book = { // オブジェクトは、中括弧で囲む。
topic: "JavaScript", // topicプロパティは、"JavaScript"という文字列の値を持つ。
fat: true // fatプロパティは、trueという論理値を持つ。
};
book;
{"topic":"JavaScript","fat":true}
// プロパティへのアクセスは、ドット「.」
book.topic;
"JavaScript"
// もしくは、大括弧 「[]」を使う。
book["topic"];
"JavaScript"
book.author = "Flanagan"; // 新たなプロパティを作ることも出来る。
book;
{"topic":"JavaScript","fat":true,"author":"Flanagan"}
book.contents = {}; // {}は、何もプロパティを持たない空のオブジェクトを意味する。
book;
{"topic":"JavaScript","fat":true,"author":"Flanagan","contents":{}}
arr.length; // 配列もいくつかのプロパティを持つ。
4
arr.push(5); // プロパティとして関数(メソッド)を持っている場合もある。
arr;
[Array] [1,2,3,4,5]
// 配列やオブジェクトには、別の配列やオブジェクトを格納できる。
var points = [
{x: 0, y: 0},
{x: 1, y: 1}
];
points;
[Array] [{"x":0,"y":0},{"x":1,"y":1}]
points[0];
{"x":0,"y":0}
points[1].x;
1
var data = {
trial1: [[1, 2], [3, 4]],
trial2: [[2, 3], [4, 5]]
};
data.trial1[0];
[Array] [1,2]
3 + 2; // 四則演算
5
5 % 2; // 剰余算
1
points[1].x - points[0].x; // オブジェクトを使った複雑な計算。
1
"3" + "2"; // 文字列の足し算は、連結。
"32"
var count = 0;
count++; // インクリメント
count;
1
count--; // デクリメント
count;
0
2 == 3;
false
"two" == "tree";
false
"two" > "three"; // 辞書順比較。 th -> tw の順で正しい。
true
var x = true; // 論理演算
var y = false;
!(x && y);
true
JavaScriptにおいて関数は重要な概念です。関数の使い方を学ぶことでJavaScriptへの理解度は飛躍的に上昇します。
多くの言語と同じ様に、名前を付けて関数定義を定義することができます。関数を定義するには、functionキーワードを利用します。
function plus1(x) { // xという引数を持つ、plus1と言う名前の関数。
return x+1; // xより1大きい値を返す。
}
plus1(10);
11
JavaScriptの関数は、名前を付けずに定義することが可能です。このような関数を無名関数と呼びます。また、関数は変数に代入することが可能です。
var square = function(x) { // 無名関数を定義し、変数squareに代入。
return x * x;
}
square(5); // 呼び出しも可能。
25
(function() { // 無名関数をその場で実行することも可能です。無名関数を即時関数と呼ぶこともあります。
// 一見無駄のように見えるが、関数の中で宣言した変数をスコープを閉じ込め、
// 安全にプログラムが実行出来る。
var x = 10;
var y = 20;
console.log(x * y);
})();
200
x; // 無名関数のxではない。遥か昔に定義したxの値が出力される。
true
JavaScriptの関数は、引数に渡したり、戻り値として関数そのものを返すことが可能です。
function plusXFunc(x, f) { // 数値xと関数fを受け取る。
return x + f(x); // xとxに受け取った関数fを適用して、足し算した結果を返している。
}
plusXFunc(1, plus1);
3
plusXFunc(10, function(x){ // 無名関数を関数fに渡すことも可能。
return x * x * x;
});
1010
JavaScriptの関数は、関数の外から値を受け取り、閉じ込めることが出来ます。そのような関数をクロージャと呼びます。クロージャの例を見てみましょう。
function counter() { // counterの戻り値は、クロージャ
var x = 0;
return (function() { // この無名関数がクロージャ
return x++; // 関数の外のxを受け取っている、このような変数を自由変数と呼ぶ。
});
}
var c = counter();
c();
0
c();
1
c();
2
JavaScriptの関数は、単なる宣言だけでなく、変数に代入出来たり、名前を付けずに定義出来たり、高階関数が定義出来たりすることがわかりました。このような特徴を持つ関数のことを第一級関数と呼びます。第一級関数を持つ言語のことを関数型言語と呼んだりします(呼ばなかったりもするので、基準は曖昧です)。
高階関数を自由に定義し、実行し結果を確認せよ。正しく定義出来た場合は、Slackの#generalに投稿せよ。
Jsonとは、JavaScriptのオブジェクトの表記をそのまま応用したデータ構文のことです。Jsonの形式になっていれば、ソースコードに貼り付けるだけでJavaScript内で利用することが出来るようになります。現在多くのプログラミング言語やツールの設定ファイルにJsonを使うケースが増えています。
Jsonには大きく分けて
の二つのデータ形式が存在します。以下にルールとその例を挙げます。
{"firstName":"John, "lastName":"Doe"}
{"1st_period":"LS2", "2nd_period":"csI", "3rd_period":"csI_ex", "4th_period":"literacy"}
[
1,
"string",
true
]
もちろん、上で挙げたJsonオブジェクトを 値 としてカンマ , 区切りで列挙することも出来ます。
[
{"firstName":"John, "lastName":"Doe"},
{"1st_period":"LS2", "2nd_period":"csI", "3rd_period":"csI_ex", "4th_period":"literacy"}
]
Json配列の要素で 値 として使うことが出来るのは、基本的に以下のデータ型になります。
JavaScript上でもJsonを扱うことが多くあります。ここでは、JSオブジェクトとJsonの変換を見ていきます。
var obj = {key1: "123", key2: [1, 2, 3], key3: true}; // オブジェクトの定義
var json = JSON.stringify(obj); // オブジェクトをJson文字列に変換。
json;
"{\"key1\":\"123\",\"key2\":[1,2,3],\"key3\":true}"
JSON.parse(json); // Json文字列をオブジェクトに変換。
{"key1":"123","key2":[1,2,3],"key3":true}
以下のJson文字列を使い、以下の問に答えよ。
var json = '[{"firstName":"John", "lastName":"Doe"},{"1st_period":"LS2", "2nd_period":"csI", "3rd_period":"csI_ex", "4th_period":"literacy"}]';
var json = '[{"firstName":"John", "lastName":"Doe"},' +
'{"1st_period":"LS2", "2nd_period":"csI", "3rd_period":"csI_ex", "4th_period":"literacy"}]';
// 以下に解答のコードを記述。
オブジェクトや配列(Array)と言った、構造を持ったものをコレクションと呼びます。JavaScriptのコレクションは容易に状態の変更が出来てしまうので、バグを引き起こしやすいです。また、コレクションに対して出来る操作が少なかったりパフォーマンスが思わしくない場合も多々あります。そこでFacebook社が作成した、新たなコレクションライブラリ、Immutable.jsを、本講義では積極的に採用します。Immutable.jsは、JS既存のコレクションより高機能なコレクションを提供しますが、大きな特徴はイミュータブル(不変)であることです。JSのように状態の変更が出来てしまうデータ構造のことを対象的にミュータブル(可変)と呼びます。
ここでは実際に、ミュータブルコレクション(オブジェクト)の危険性について見ていきます。次のようなオブジェクトのプロパティを変更し、新しいオブジェクトを返すような関数を考えてみましょう。
function setX(obj, newX) { // オブジェクトと新しいxの値を受け取る
var newObj = obj; // 新しいオブジェクトを生成
newObj.x = newX; // 新しいxの値に設定。
return newObj; // 返却
}
var obj = {x: 1, y: 2};
var newObj = setX(obj, 5);
newObj;
{"x":5,"y":2}
想定通り、新しいオブジェクトのxプロパティの値は、5となりました。ここで、元のobjの値も参照してみましょう。
obj;
{"x":5,"y":2}
これは、JSコレクションが可変であることに加えて、基本的に代入や関数への実引数が参照を渡していることに原因があります。参照というのは、コピーが生成されるのではなく、元々、変数の値を保存していたメモリの番地を渡していることです。メモリの番地を渡しているということは、いくら変数を経由しても、変更は経由した変数全てに影響を与えるということです。これはとても危険です。
また、変更は容易に行えますが、コピーが難しいため、オブジェクトを併合(マージ)することは、とても大変です。マージを行うには、次のような関数を定義する必要があります。
var merge = function (obj1, obj2) {
if (!obj2) {
obj2 = {};
}
for (var attrname in obj2) {
if (obj2.hasOwnProperty(attrname)) {
obj1[attrname] = obj2[attrname];
}
}
};
var obj1 = {
name: "pigmon",
city: "tokyo",
tel: "00-0000-0000"
};
var obj2 = {
name: "garamon",
city: "osaka",
sex: "man"
};
merge(obj1, obj2); // 存在するプロパティは、obj2を採用し、無い場合はobj1のものを採用する。
obj1; // obj1がマージの対象となる。
{"name":"garamon","city":"osaka","tel":"00-0000-0000","sex":"man"}
この場合でも、obj1の値が書き換わってしまうことが問題です。新しいオブジェクトを用意することでこの問題は回避可能ですが、さらにmergeの関数を書き換える必要があります。Immutable.jsでは、オブジェクトの代わりに、Mapと呼ばれるコレクションが用意されており、この問題を解決しています。
var Immutable = require("immutable");
var map1 = Immutable.Map({
name: "pigmon",
city: "tokyo",
tel: "00-0000-0000"
});
var map2 = Immutable.Map({
name: "garamon",
city: "osaka",
sex: "man"
});
map1.merge(map2);
[Map] {"name":"garamon","city":"osaka","tel":"00-0000-0000","sex":"man"}
map1;
[Map] {"name":"pigmon","city":"tokyo","tel":"00-0000-0000"}
map2;
[Map] {"name":"garamon","city":"osaka","sex":"man"}
mergeのように他にも多くの便利なメソッドが用意されているだけでなく、一度生成されたコレクションは状態を変えることは絶対にありません。
Immutable.jsには、便利なコレクションがいくつか用意されていますが、今回は配列とオブジェクトの代わりのコレクションのみを紹介します。配列の代わりがList, オブジェクトの代わりがMapとなります。
var Immutable = require("immutable"); // Immutable.jsを使う準備、Node.jsから使う場合。
// フロントエンドJSから使う場合、これ以降 Immutable 変数からコレクションが参照できる。
// <script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
Immutable.List([1, 2, 3, 4]); // JSの配列からImmutable.Listに変換する。
[List] [1,2,3,4]
var list1 = Immutable.List.of(1, 2, 3, 4); // 可変長引数を利用して、Listを生成する。
list1;
[List] [1,2,3,4]
list1.size; // Listの長さを得る。
4
list1.push(5); // 末尾に5を追加した新しいListを生成し返す。
[List] [1,2,3,4,5]
list1.unshift(0); // 先頭に0を追加した新しいListを生成し返す。
[List] [0,1,2,3,4]
list1; // 元のListは影響を受けない。
[List] [1,2,3,4]
// Listを結合し、新しいListを生成する。
// 可変長引数。
list1.concat(Immutable.List.of(5, 6, 7, 8), Immutable.List.of(10));
[List] [1,2,3,4,5,6,7,8,10]
Immutable.Range(0, 10); // 0から9までの範囲を返す。
[Range] [0,1,2,3,4,5,6,7,8,9]
var list2 = Immutable.Range(0, 10).toList(); // Listに変換可能。
list2.reverse(); // 逆順のListを得る。
[List] [9,8,7,6,5,4,3,2,1,0]
list1.toJS(); // JSの配列に変換する。
[Array] [1,2,3,4]
var map1 = Immutable.Map({a: 1, b: 2, c: 3});
map1.get("a"); // プロパティ「a」の値を得る。
1
var map2 = map1.set("a", 5); // aの値を5に設定した新しいMapを得る。
map2;
[Map] {"a":5,"b":2,"c":3}
map2.set("d", 2); // 存在しないプロパティは、新しいプロパティを作る。
[Map] {"a":5,"b":2,"c":3,"d":2}
map2.toJS(); // JSのオブジェクトに変換する。
{"a":5,"b":2,"c":3}
コレクションのメソッドには、高階関数が用意されており、関数を渡すことができる。
// 二乗をする関数を渡し、すべての要素を二乗した後、それを要素として持つListを得る。
Immutable.Range(0, 50).toList().map(square);
[List] [0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961,1024,1089,1156,1225,1296,1369,1444,1521,1600,1681,1764,1849,1936,2025,2116,2209,2304,2401]
Immutable.Range(0, 50).toList().map(function(x){return x + 5;}); // 無名関数を渡すことも出来る。
[List] [5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54]
// 論理値を返す関数を渡して、要素を適用し、trueとなる要素だけを取り出し新しいListを生成する。
Immutable.Range(0, 50).toList().filter(function(x){return x % 2 == 0;});
[List] [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48]
// 要素を取り出し、任意の処理を実行する
Immutable.Range(0, 10).toList().forEach(function(x){console.log("*" + x + "*");});
*0* *1* *2* *3* *4* *5* *6* *7* *8* *9*
10
// 合計値を求める。
Immutable.Range(0, 101).toList().reduce(function(sum, x) { // 前回の計算結果(sum), 見ている要素x
return sum + x // 計算
}, 0); // 初期値
// 0 -> 0 + 1 -> 1 + 2 -> 3 + 4 -> ... -> (0 ~ 99 の合計) + 100 -> 最終的な答え。
5050
// mapやfilterは、Listを返すので、処理をつなげることが出来る。
Immutable.Range(0, 100).toList().map(function(x) {
return x * 2;
}).filter(function(x){
return x >= 50;
});
[List] [50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198]
ImmutableJSを使い、1~99のListを生成し、さらにそのListの先頭に1~99のListを反転したListを結合し、表示せよ。 その後、全ての要素に13を足し、3の倍数のみを残し、Listの積を求めよ。