{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# §2. JavaScript" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## §2.1 JavaScript" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "JavaScriptとは、主にWebページに **動的な演出** を加えることを目的としたプログラミング言語です。HTMLがWebページの文書の構造を静的に記述するのに対して、JavaScriptは時刻やユーザーの操作に従い、HTMLの要素の内容を動的に変えることができます。JavaScriptは使われる場面によって更に細かく分類することが出来て、実際にユーザーが操作したり見ることが出来る、ブラウザ側の処理の部分を **フロントエンドJS(クライアントサイドJS)** と呼び、ユーザーからは見えない内部的なサーバー側の処理の部分を **バックエンドJS(サーバーサイドJS)** と呼びます。\n", " \n", "![](https://github.com/SCCP2016/botter-introduction/blob/master/img/chapter2/javascript-relationship.png?raw=true)\n", " \n", "### コラム : JavaとJavaScript\n", "\n", "プログラミング学習者の皆さんであれば、Javaと呼ばれるプログラミング言語があることはご存知でしょう。では、名前の似ているJavaとJavaScriptは何か関係があるのでしょうか? \n", "答えは **全く関係ありません** 。 \n", "JavaScriptは、1990年代にNetscape社によってLiveScriptという名前で開発されました。当時Netscape社と業務提携していたSun Microsystems社の開発したJavaという言語が大きく流行ったため、JavaScriptという名前に変更されました。そのため、JavaとJavaScriptは名前は似ていても中身は全く別物です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ECMAScript\n", "\n", "JavaScriptは以前、Webブラウザ毎に実装が異なっていました。そのため仕様の差異が生まれ完全にどのブラウザでも動作するようなプログラムを書くことが困難でした。そのため標準化団体による標準規格の策定を求める声が高まり、標準化団体*Ecma International*が、ECMAScript(エクマスクリプト)としてJavaScriptの標準を定めました。現在の多くのブラウザでは、*EcmaScript5*(*ES5*)が標準動作しています。本資料では、ES5を採用しています。EcmaScriptの最新バージョンは、ES6ですが年度毎にバージョンを更新することが決定されたので、正式には*ES2015*と表記されます。最終課題では、ES5ではなく、ES2015を採用する予定です。ES2015は、多くのブラウザでは動作しないため、ES5へ変換(*トランスパイル*)されて動作させます。現在トランスパイルには、[Babel](https://babeljs.io)を使うのがメジャーとなっています。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## §2-2. フロントエンドJS" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "この節では実際にブラウザ上で、基本的なフロントエンドJSの動作を確認します。Firefoxに標準搭載されている開発者向けのツールがあり、*Ctrl+Shift+k*をタイプすることで開くことが出来ます。HTMLの要素を視覚的に確認することが出来る*インスペクタ*やJavaScriptを実行させたり、結果を確認することが出来る*コンソール*、デバッガ等が備えられています。今回は、コンソールをタブから選択し、一番下のフォームに\n", "\n", "> $ console.log(2*5);\n", "\n", "とタイプしてみましょう。そうすると数式の結果がコンソール上に表示されるはずです。\n", "\n", "![](https://github.com/SCCP2016/botter-introduction/blob/master/img/chapter2/console.png?raw=true)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "続いて、HTML上でJavaScriptを記述して実行してみましょう。以下のようなファイルを作りブラウザで開いてみましょう。\n", "\n", "> $ firefox sample2-1.html\n", "\n", "JavaScriptをHTMLに直接記述するには、*scipt* 要素を使い、子要素として、JavaScriptのソースコードを記述します。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*~/sccp/web/sample2-1.html*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```html\n", "\n", "\n", "\n", " \n", "\n", "\n", " \n", "\n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "そうすると次のような結果になるはずです。\n", "\n", "![](https://github.com/SCCP2016/botter-introduction/blob/master/img/chapter2/sample1.png?raw=true)\n", "\n", "*console.log*は、先ほどと同じで、コンソール上での表示を行う命令ですが、*alert*は、ポップアップにメッセージの表示を行う命令です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "続いて、変数の宣言と代入、console.logとalertの違いに触れていきましょう。変数は、*var*というキーワードの後に変数名を書き代入を行います。varキーワードを付けなくても変数を使用することは出来ますが、その場合グローバルスコープ(グローバル変数)となってしまい、どこからでも参照・変更が可能になってしまうためバグが起きやすくなってしまいます。なので基本的には、varキーワードを使用するようにしましょう。JavaScriptは、Ruby同様に動的型付け言語のため、型の宣言は要りません。次の例では、*arr*という変数に配列を代入しています。プログラムが記述できたらブラウザで開いてプログラムを確認してみましょう。\n", "\n", "> $ firefox ex2-2.html" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*~/sccp/web/sample2-2.html*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```html\n", "\n", "\n", "\n", " \n", "\n", "\n", " \n", "\n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](https://github.com/SCCP2016/botter-introduction/blob/master/img/chapter2/sample2.png?raw=true)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "配列を表示した場合、console.logの方がより詳細に型の情報や見せ方をしてくれているのがわかると思います。元々コンソールは開発者のためのログを出力する場所なので、プログラムのデバッグ目的で文字を表示する場合は、console.logを使用すると良いでしょう。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## §2-2. バックエンドJS (Node.js)\n", "\n", "**Node.js**とは、サーバーサイドJavaScript環境と呼ばれているものです。 \n", "JavaScriptを解釈し実行するものを **JavaScriptエンジン** と呼びますが、Node.jsでは、Googleが開発した **V8** を採用しており、メモリ消費量が少なく、高速な処理がNode.jsの主な特長となっています。この節では、実際にjupyterNote(このノート)を使ってJavaScriptを実行してみましょう。このノートは実は、Node.jsを利用して実行しているため、Node.jsを使ったときと同様の挙動をします。また、このノートでは、ES2015が採用されています。しかし、ES2015は、ES5の文法と互換性があるため、ES5の文法でプログラムを記述しても問題なく動作します。基本的な操作は[jupyterNoteのチュートリアル](https://github.com/SCCP2016/how2jupyter/blob/master/README.md)を参照してください。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### フロントエンドJSとバックエンドJSの違い\n", "\n", "フロントエンドJSとバックエンドJSの違いを知るために、今まで習った命令を試してみましょう。今まで習った命令はたった2つです。そうconsole.logとalertです。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "8\n" ] } ], "source": [ "console.log(2*4);" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "ename": "Javascript Error", "evalue": "alert is not defined", "output_type": "error", "traceback": [ "ReferenceError: alert is not defined\n at :1:1\n at Object.Contextify.sandbox.run (/root/jupyter-nodejs/node_modules/contextify/lib/contextify.js:12:24)\n at Context.rawRun (/root/jupyter-nodejs/build/context.js:168:23)\n at /root/jupyter-nodejs/build/context.js:188:27\n at b (domain.js:183:18)\n at Domain.run (domain.js:123:23)\n at Context.rawEvaluate (/root/jupyter-nodejs/build/context.js:186:9)\n at Context.execute (/root/jupyter-nodejs/build/context.js:333:21)\n at Kernel.executeRequest (/root/jupyter-nodejs/build/kernel.js:197:16)\n at Kernel.onShell (/root/jupyter-nodejs/build/kernel.js:123:14)" ] } ], "source": [ "alert(2*4);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "console.logは正常に動作したと思いますが、alertは、*not defined*というエラーが表示されました。これは、Node.jsがコンソール上で動くことを想定しているため、ブラウザ(HTML)に関する命令は存在していません。alertはブラウザのポップアップにメッセージを表示する命令のため、ブラウザ命令に該当します。その代わりNode.jsでは、ファイルを操作したりOSに関連するプログラムが動作するようになっています。JavaScriptを調べるときは、フロントエンドJSなのかバックエンドJSなのか、ES5なのかES2015なのかを注意深く確認することが大切です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 基礎文法\n", "\n", "いくつかの基本的な文法を紹介していきます。本ノートは自由に変更して実行することが出来るので、Node.jsの文法と、その挙動を試しながら確認してみましょう。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### コメント" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "// スラッシュ文字が2つ並んだ後の文字はすべてコメントになる。\n", "/*\n", " 複数行に渡る場合は、スラッシュ+アスタリスク\n", " コメント終わりは、アスタリスク+スラッシュで閉じる。\n", "*/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "変数宣言" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var x; // 変数宣言\n", "x = 0; // xに0を代入。\n", "x; // xの値を評価する。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### さまざまな型" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1; //数値" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0.01" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 0.01; // 整数も実数も同じ数値型になる。" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"Hello world\"" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = \"Hello world\"; // 文字列はダブルクォートで囲む。" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"

hoge

\"" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// シングルクォートでも文字列扱いになる。\n", "// 中で記述する文字列にダブルクォートがある場合に有効。\n", "x = '

hoge

' " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = true; // 論理値。真。" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = false; // 論理値。偽。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "null" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = null; // 特殊な値で「値がない」ことを意味する。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "x = undefined; // 特殊な値で「値が未定義」ことを意味する。" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Array] [1,2,3,4]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var arr = [1, 2, 3, 4]; // 数値型の配列。\n", "arr;" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"topic\":\"JavaScript\",\"fat\":true}" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var book = { // オブジェクトは、中括弧で囲む。\n", " topic: \"JavaScript\", // topicプロパティは、\"JavaScript\"という文字列の値を持つ。\n", " fat: true // fatプロパティは、trueという論理値を持つ。\n", "};\n", "book;" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"JavaScript\"" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// プロパティへのアクセスは、ドット「.」\n", "book.topic;" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"JavaScript\"" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// もしくは、大括弧 「[]」を使う。\n", "book[\"topic\"];" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"topic\":\"JavaScript\",\"fat\":true,\"author\":\"Flanagan\"}" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "book.author = \"Flanagan\"; // 新たなプロパティを作ることも出来る。\n", "book;" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"topic\":\"JavaScript\",\"fat\":true,\"author\":\"Flanagan\",\"contents\":{}}" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "book.contents = {}; // {}は、何もプロパティを持たない空のオブジェクトを意味する。\n", "book;" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.length; // 配列もいくつかのプロパティを持つ。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Array] [1,2,3,4,5]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.push(5); // プロパティとして関数(メソッド)を持っている場合もある。\n", "arr;" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Array] [{\"x\":0,\"y\":0},{\"x\":1,\"y\":1}]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 配列やオブジェクトには、別の配列やオブジェクトを格納できる。\n", "var points = [\n", " {x: 0, y: 0},\n", " {x: 1, y: 1}\n", "];\n", "points;" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"x\":0,\"y\":0}" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points[0];" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points[1].x;" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": true }, "outputs": [], "source": [ "var data = {\n", " trial1: [[1, 2], [3, 4]],\n", " trial2: [[2, 3], [4, 5]]\n", "};" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Array] [1,2]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.trial1[0];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### さまざまな演算" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "3 + 2; // 四則演算" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "5 % 2; // 剰余算" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points[1].x - points[0].x; // オブジェクトを使った複雑な計算。" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"32\"" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"3\" + \"2\"; // 文字列の足し算は、連結。" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": true }, "outputs": [], "source": [ "var count = 0;" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "count++; // インクリメント\n", "count; " ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "count--; // デクリメント\n", "count; " ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "2 == 3;" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"two\" == \"tree\";" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"two\" > \"three\"; // 辞書順比較。 th -> tw の順で正しい。 " ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var x = true; // 論理演算\n", "var y = false;\n", "!(x && y);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 関数\n", "\n", "JavaScriptにおいて関数は重要な概念です。関数の使い方を学ぶことでJavaScriptへの理解度は飛躍的に上昇します。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 関数定義と呼び出し\n", "\n", "多くの言語と同じ様に、名前を付けて関数定義を定義することができます。関数を定義するには、**function**キーワードを利用します。" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "11" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function plus1(x) { // xという引数を持つ、plus1と言う名前の関数。\n", " return x+1; // xより1大きい値を返す。\n", "}\n", "\n", "plus1(10);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 無名関数\n", "\n", "JavaScriptの関数は、名前を付けずに定義することが可能です。このような関数を無名関数と呼びます。また、関数は変数に代入することが可能です。" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "25" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var square = function(x) { // 無名関数を定義し、変数squareに代入。\n", " return x * x;\n", "}\n", "\n", "square(5); // 呼び出しも可能。" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "200\n" ] } ], "source": [ "(function() { // 無名関数をその場で実行することも可能です。無名関数を即時関数と呼ぶこともあります。\n", " // 一見無駄のように見えるが、関数の中で宣言した変数をスコープを閉じ込め、\n", " // 安全にプログラムが実行出来る。\n", " var x = 10;\n", " var y = 20;\n", " console.log(x * y);\n", "})();" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x; // 無名関数のxではない。遥か昔に定義したxの値が出力される。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 高階関数\n", "\n", "JavaScriptの関数は、引数に渡したり、戻り値として関数そのものを返すことが可能です。" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function plusXFunc(x, f) { // 数値xと関数fを受け取る。\n", " return x + f(x); // xとxに受け取った関数fを適用して、足し算した結果を返している。\n", "}\n", "\n", "plusXFunc(1, plus1);" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1010" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plusXFunc(10, function(x){ // 無名関数を関数fに渡すことも可能。\n", " return x * x * x;\n", "});" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### クロージャ\n", "\n", "JavaScriptの関数は、関数の外から値を受け取り、閉じ込めることが出来ます。そのような関数をクロージャと呼びます。クロージャの例を見てみましょう。" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": true }, "outputs": [], "source": [ "function counter() { // counterの戻り値は、クロージャ\n", " var x = 0;\n", " return (function() { // この無名関数がクロージャ\n", " return x++; // 関数の外のxを受け取っている、このような変数を自由変数と呼ぶ。\n", " });\n", "}\n", "\n", "var c = counter();" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c();" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c();" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "JavaScriptの関数は、単なる宣言だけでなく、変数に代入出来たり、名前を付けずに定義出来たり、高階関数が定義出来たりすることがわかりました。このような特徴を持つ関数のことを**第一級関数**と呼びます。第一級関数を持つ言語のことを**関数型言語**と呼んだりします(呼ばなかったりもするので、基準は曖昧です)。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### チェックリスト\n", "\n", "- フロントエンドJSとバックエンドJSの違いは何か。\n", "- JavaScriptとECMAスクリプトの違いは何か。\n", "- JavaScriptにおける型を2つ上げ、その型の性質を答えよ。\n", "- JavaScriptにおける関数は、どのような性質を持っているか答えよ。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 演習\n", "\n", "高階関数を自由に定義し、実行し結果を確認せよ。正しく定義出来た場合は、Slackの#generalに投稿せよ。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### §2-3. JSON (JavaScript Object Notation)\n", "\n", "Jsonとは、JavaScriptのオブジェクトの表記をそのまま応用したデータ構文のことです。Jsonの形式になっていれば、ソースコードに貼り付けるだけでJavaScript内で利用することが出来るようになります。現在多くのプログラミング言語やツールの設定ファイルにJsonを使うケースが増えています。\n", "\n", "Jsonには大きく分けて\n", "\n", "* オブジェクト\n", "* 配列\n", "\n", "の二つのデータ形式が存在します。以下にルールとその例を挙げます。\n", "#### Jsonオブジェクト\n", "\n", "* ルール:全体を中括弧 **{ }** で囲み、 **キー** と **値** をコロン **:** で区切って表記したペアをカンマ区切りで列挙。\n", "* 例\n", "```\n", "{\"firstName\":\"John, \"lastName\":\"Doe\"}\n", "{\"1st_period\":\"LS2\", \"2nd_period\":\"csI\", \"3rd_period\":\"csI_ex\", \"4th_period\":\"literacy\"}\n", "```\n", "\n", "#### Json配列\n", "\n", "* ルール:全体を角括弧 **[ ]** で囲み、 **値** をカンマ **,** 区切りで列挙。\n", "* 例\n", "```\n", "[\n", " 1,\n", " \"string\",\n", " true\n", "]\n", "```\n", "\n", "もちろん、上で挙げたJsonオブジェクトを **値** としてカンマ **,** 区切りで列挙することも出来ます。\n", "```\n", "[\n", " {\"firstName\":\"John, \"lastName\":\"Doe\"},\n", " {\"1st_period\":\"LS2\", \"2nd_period\":\"csI\", \"3rd_period\":\"csI_ex\", \"4th_period\":\"literacy\"}\n", "]\n", "```\n", "Json配列の要素で **値** として使うことが出来るのは、基本的に以下のデータ型になります。\n", "\n", "* 数値(整数・浮動小数点)\n", "* 文字列(「\" \"」で括る)\n", "* 配列(「[ ]」で括る)\n", "* オブジェクト(「{ }」で括る)\n", "* bool(true・false)\n", "* null\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### JavaScriptでのJson\n", "\n", "JavaScript上でもJsonを扱うことが多くあります。ここでは、JSオブジェクトとJsonの変換を見ていきます。" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"{\\\"key1\\\":\\\"123\\\",\\\"key2\\\":[1,2,3],\\\"key3\\\":true}\"" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var obj = {key1: \"123\", key2: [1, 2, 3], key3: true}; // オブジェクトの定義\n", "var json = JSON.stringify(obj); // オブジェクトをJson文字列に変換。\n", "json;" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"key1\":\"123\",\"key2\":[1,2,3],\"key3\":true}" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "JSON.parse(json); // Json文字列をオブジェクトに変換。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 演習\n", "\n", "以下のJson文字列を使い、以下の問に答えよ。\n", "\n", "var json = '[{\"firstName\":\"John\", \"lastName\":\"Doe\"},{\"1st_period\":\"LS2\", \"2nd_period\":\"csI\", \"3rd_period\":\"csI_ex\", \"4th_period\":\"literacy\"}]';\n", "\n", "- firstNameとlastNameを空白区切りで表示する。\n", "- 2nd_periodの科目を表示する。\n", "- 3rd_periodの科目を表示する。" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "collapsed": false }, "outputs": [], "source": [ "var json = '[{\"firstName\":\"John\", \"lastName\":\"Doe\"},' +\n", " '{\"1st_period\":\"LS2\", \"2nd_period\":\"csI\", \"3rd_period\":\"csI_ex\", \"4th_period\":\"literacy\"}]';\n", "\n", "// 以下に解答のコードを記述。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### §2-4. Immutable.js\n", "\n", "オブジェクトや配列(Array)と言った、構造を持ったものを*コレクション*と呼びます。JavaScriptのコレクションは容易に状態の変更が出来てしまうので、バグを引き起こしやすいです。また、コレクションに対して出来る操作が少なかったりパフォーマンスが思わしくない場合も多々あります。そこで*Facebook*社が作成した、新たなコレクションライブラリ、[Immutable.js](https://facebook.github.io/immutable-js/)を、本講義では積極的に採用します。Immutable.jsは、JS既存のコレクションより高機能なコレクションを提供しますが、大きな特徴はイミュータブル(不変)であることです。JSのように状態の変更が出来てしまうデータ構造のことを対象的にミュータブル(可変)と呼びます。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ミュータブルの危険性とJSコレクション\n", "\n", "ここでは実際に、ミュータブルコレクション(オブジェクト)の危険性について見ていきます。次のようなオブジェクトのプロパティを変更し、新しいオブジェクトを返すような関数を考えてみましょう。" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "collapsed": true }, "outputs": [], "source": [ "function setX(obj, newX) { // オブジェクトと新しいxの値を受け取る\n", " var newObj = obj; // 新しいオブジェクトを生成\n", " newObj.x = newX; // 新しいxの値に設定。\n", " return newObj; // 返却\n", "}" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"x\":5,\"y\":2}" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var obj = {x: 1, y: 2};\n", "var newObj = setX(obj, 5);\n", "newObj;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "想定通り、新しいオブジェクトのxプロパティの値は、5となりました。ここで、元のobjの値も参照してみましょう。" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"x\":5,\"y\":2}" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "obj;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これは、JSコレクションが可変であることに加えて、基本的に代入や関数への実引数が参照を渡していることに原因があります。参照というのは、コピーが生成されるのではなく、元々、変数の値を保存していたメモリの番地を渡していることです。メモリの番地を渡しているということは、いくら変数を経由しても、変更は経由した変数全てに影響を与えるということです。これはとても危険です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "また、変更は容易に行えますが、コピーが難しいため、オブジェクトを併合(マージ)することは、とても大変です。マージを行うには、次のような関数を定義する必要があります。" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"name\":\"garamon\",\"city\":\"osaka\",\"tel\":\"00-0000-0000\",\"sex\":\"man\"}" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var merge = function (obj1, obj2) {\n", " if (!obj2) {\n", " obj2 = {};\n", " }\n", " for (var attrname in obj2) {\n", " if (obj2.hasOwnProperty(attrname)) {\n", " obj1[attrname] = obj2[attrname];\n", " }\n", " }\n", "};\n", "\n", "var obj1 = {\n", " name: \"pigmon\",\n", " city: \"tokyo\",\n", " tel: \"00-0000-0000\"\n", "};\n", "\n", "var obj2 = {\n", " name: \"garamon\",\n", " city: \"osaka\",\n", " sex: \"man\"\n", "};\n", "\n", "merge(obj1, obj2); // 存在するプロパティは、obj2を採用し、無い場合はobj1のものを採用する。\n", "obj1; // obj1がマージの対象となる。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "この場合でも、obj1の値が書き換わってしまうことが問題です。新しいオブジェクトを用意することでこの問題は回避可能ですが、さらにmergeの関数を書き換える必要があります。Immutable.jsでは、オブジェクトの代わりに、*Map*と呼ばれるコレクションが用意されており、この問題を解決しています。" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Map] {\"name\":\"garamon\",\"city\":\"osaka\",\"tel\":\"00-0000-0000\",\"sex\":\"man\"}" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var Immutable = require(\"immutable\");\n", "var map1 = Immutable.Map({ \n", " name: \"pigmon\",\n", " city: \"tokyo\",\n", " tel: \"00-0000-0000\"\n", "});\n", "var map2 = Immutable.Map({\n", " name: \"garamon\",\n", " city: \"osaka\",\n", " sex: \"man\"\n", "});\n", "map1.merge(map2);" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Map] {\"name\":\"pigmon\",\"city\":\"tokyo\",\"tel\":\"00-0000-0000\"}" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map1;" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Map] {\"name\":\"garamon\",\"city\":\"osaka\",\"sex\":\"man\"}" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map2;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "mergeのように他にも多くの便利なメソッドが用意されているだけでなく、一度生成されたコレクションは状態を変えることは絶対にありません。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ListとMap\n", "Immutable.jsには、便利なコレクションがいくつか用意されていますが、今回は配列とオブジェクトの代わりのコレクションのみを紹介します。配列の代わりがList, オブジェクトの代わりがMapとなります。" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [1,2,3,4]" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var Immutable = require(\"immutable\"); // Immutable.jsを使う準備、Node.jsから使う場合。\n", "// フロントエンドJSから使う場合、これ以降 Immutable 変数からコレクションが参照できる。\n", "// \n", "\n", "Immutable.List([1, 2, 3, 4]); // JSの配列からImmutable.Listに変換する。" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [1,2,3,4]" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var list1 = Immutable.List.of(1, 2, 3, 4); // 可変長引数を利用して、Listを生成する。\n", "list1;" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list1.size; // Listの長さを得る。" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [1,2,3,4,5]" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list1.push(5); // 末尾に5を追加した新しいListを生成し返す。" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [0,1,2,3,4]" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list1.unshift(0); // 先頭に0を追加した新しいListを生成し返す。" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [1,2,3,4]" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list1; // 元のListは影響を受けない。" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [1,2,3,4,5,6,7,8,10]" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Listを結合し、新しいListを生成する。\n", "// 可変長引数。\n", "list1.concat(Immutable.List.of(5, 6, 7, 8), Immutable.List.of(10)); " ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Range] [0,1,2,3,4,5,6,7,8,9]" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Immutable.Range(0, 10); // 0から9までの範囲を返す。" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "collapsed": false }, "outputs": [], "source": [ "var list2 = Immutable.Range(0, 10).toList(); // Listに変換可能。" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[List] [9,8,7,6,5,4,3,2,1,0]" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list2.reverse(); // 逆順のListを得る。" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Array] [1,2,3,4]" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list1.toJS(); // JSの配列に変換する。" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var map1 = Immutable.Map({a: 1, b: 2, c: 3});\n", "map1.get(\"a\"); // プロパティ「a」の値を得る。" ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Map] {\"a\":5,\"b\":2,\"c\":3}" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var map2 = map1.set(\"a\", 5); // aの値を5に設定した新しいMapを得る。\n", "map2;" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Map] {\"a\":5,\"b\":2,\"c\":3,\"d\":2}" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map2.set(\"d\", 2); // 存在しないプロパティは、新しいプロパティを作る。" ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{\"a\":5,\"b\":2,\"c\":3}" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map2.toJS(); // JSのオブジェクトに変換する。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 高階関数\n", "\n", "コレクションのメソッドには、高階関数が用意されており、関数を渡すことができる。" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[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]" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 二乗をする関数を渡し、すべての要素を二乗した後、それを要素として持つListを得る。\n", "Immutable.Range(0, 50).toList().map(square); " ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[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]" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Immutable.Range(0, 50).toList().map(function(x){return x + 5;}); // 無名関数を渡すことも出来る。" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[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]" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 論理値を返す関数を渡して、要素を適用し、trueとなる要素だけを取り出し新しいListを生成する。\n", "Immutable.Range(0, 50).toList().filter(function(x){return x % 2 == 0;});" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "*0*\n", "*1*\n", "*2*\n", "*3*\n", "*4*\n", "*5*\n", "*6*\n", "*7*\n", "*8*\n", "*9*\n" ] }, { "data": { "text/plain": [ "10" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 要素を取り出し、任意の処理を実行する\n", "Immutable.Range(0, 10).toList().forEach(function(x){console.log(\"*\" + x + \"*\");});" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "5050" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 合計値を求める。\n", "Immutable.Range(0, 101).toList().reduce(function(sum, x) { // 前回の計算結果(sum), 見ている要素x\n", " return sum + x // 計算\n", "}, 0); // 初期値\n", "\n", "// 0 -> 0 + 1 -> 1 + 2 -> 3 + 4 -> ... -> (0 ~ 99 の合計) + 100 -> 最終的な答え。" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[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]" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// mapやfilterは、Listを返すので、処理をつなげることが出来る。\n", "Immutable.Range(0, 100).toList().map(function(x) {\n", " return x * 2;\n", "}).filter(function(x){\n", " return x >= 50; \n", "});" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### チェックリスト\n", "\n", "- JSコレクションとImmutable.jsのコレクションの違いは何か。\n", "- JSコレクションの危険性は何か。\n", "- Immutable.jsの有用性は何か。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 演習\n", "\n", "ImmutableJSを使い、1~99のListを生成し、さらにそのListの先頭に1~99のListを反転したListを結合し、表示せよ。\n", "その後、全ての要素に13を足し、3の倍数のみを残し、Listの積を求めよ。" ] } ], "metadata": { "kernelspec": { "display_name": "NodeJS", "language": "javascript", "name": "nodejs" }, "language_info": { "codemirror_mode": "javascript", "file_extension": "js", "mimetype": "text/javascript", "name": "nodejs", "pygments_lexer": "javascript", "version": "0.10" } }, "nbformat": 4, "nbformat_minor": 0 }