Webページが作れましたので、ここからはいよいよJavaScriptのプログラムを書いていきます。この入門のとりあえずのゴールは「UFOゲームを作る」ことですが、その前にJavaScriptの文法についてひととおり説明します。
JavaScriptのプログラムを書く、といっても、JavaScriptはWebページの中で動くプログラムなので、JavaScriptのプログラムもHTMLの中に書きます。具体的には、script要素の中に書きます。以下が例です。
script要素の部分だけ赤字にしてみました。8~9行目のscript要素の中に、「alert("こんにちは!");」というJavaScriptプログラムが書いてあります。このプログラムを手で入力するときは、「こんにちは!」というメッセージ部分を除き、例によってすべて半角文字で入力してください。
このHTMLファイルをダブルクリックして実行すると、図1のように「こんにちは!」というポップアップが表示されます。
ここで、「alert」というのは、JavaScriptの処理系(この場合ブラウザ)が用意している関数(function)です。関数の後ろに「("こんにちは!")」のようにカッコで囲まれた引数(argument/ひきすう、と読みます)を書くことで、関数に引数を渡して「呼び出す」ことができます。この例では、引数はひとつで、「"こんにちは!"」というのが引数です(引数が複数ある場合は、カンマで区切って並べます)。そして、最後のセミコロン「;」を含む、「alert("こんにちは!");」全体を、文(statement)と呼びます。
alert(アラート)というのは、英語で「警告を出す」という意味です。つまり、alert関数は、ユーザに対して警告などのメッセージを出す、という機能を持つ関数です。表示したいメッセージである「こんにちは!」を引数として渡してこの関数を呼び出すことで、図1のようにメッセージが表示できたわけです。
なお、「"こんにちは!"」のように、ダブルクォーテーションで囲まれた文字の並びを、文字列(string)もしくは文字列リテラル(string literal)と呼びます。ユーザに読ませるメッセージのようなものは、ダブルクォーテーションで囲んで、それがひとつの文字列だとわかるようにしてやるわけです。JavaScriptの場合、文字列は、ダブルクォーテーション(")だけでなく、シングルクォーテーション(')で囲んでも構いません。これは、文字列の中にシングルクォーテーションやダブルクォーテーションを含めたいときに、たまに便利です。
とはいえあまりばらばらに使うのも何なので、この入門では基本的にダブルクォーテーションを使うことにします。
次は、alert関数の呼び出しをいくつか並べてみましょう。
こうすると、ポップアップが3回表示され、それぞれに「おはよう!」「こんにちは!」「こんばんは!」と表示されます。
このように、プログラムは原則として上の行から順に実行されます。これを順次実行と呼ぶことがあります。
プログラムを手で打ち込んで実行する以上、どうやってもミスは起きます。たとえばリスト1のプログラムで、「alert」をタイプミスして「alart」と打ってしまった場合、
ブラウザには、何も表示されません。
他のプログラミング言語なら、何かしらエラーメッセージが出るものですが、JavaScriptはWebページの中で動かすという性質上そうもいかないのでしょう。一般の利用者が普通にWebを見ている時に、エラーメッセージが表示されてもうざいだけです(そして、実際、世間のWebページの多くは結構な量のエラーを出しまくっています)。
とはいえJavaScriptプログラムを書く側は、何が原因で動かないのかわからないと困ります。そこで、キーボードの上端に並んでいるファンクションキーの中から、「F12」を押してみてください。ブラウザのウインドウが縦か横に分割されて、こんな表示がされるはずです。
これがEdgeの「開発者ツール」です。Edgeに限らず、IEでもFirefoxでもChromeでも、開発者ツールを表示するのは(なぜか)F12と決まっています。
初回は「Welcome」のタブが選択されているようですが、まずは「コンソール」をクリックしてコンソールを見てみましょう。
「Uncaught ReferenceError: alart is not defined」と表示されています。英語でびびった人もいるかもしれませんが、日本語に訳せば「キャッチされなかった参照エラー: alartは定義されていません」となります。alartなどという関数はないので、そんなものは定義されていない、とエラーになっているわけです。「lesson03_error.html:9」とも表示されていますが、これはlesson03_error.htmlの9行目にエラーがあると言っています。ここはリンクになっているのでクリックすると、(親切にも)該当箇所のソースを表示してくれます。
開発者ツールには、HTMLの要素を調べたり、JavaScriptのプログラムにブレークポイントを仕掛けてプログラムを止めてその時点での変数の値を見たり、といった各種の機能があるのですが、まず最初はコンソールを見るところから始めましょう。
コンピュータは電子計算機と呼ぶくらいなので、文字列を表示しているだけでは芸がないでしょう。計算させてみましょう。
これを実行すると、こう表示されます。
――電卓よりはるかに高価なパソコンを使ってやらせる計算がこれかい! と思うかもしれませんが。
今回は、alertの引数に「"1たす1は…" + (1 + 1)」という式を渡しています。
ここで出てくる「+」のような記号のことを、演算子と呼びます。JavaScriptにおいては、+演算子は、数値の加算のほか、文字列の連結の機能もあります。ここでは、「1 + 1」の部分が数値同士の足し算で、その結果である「2」と、「"1たす1は…"」という文字列をもうひとつの+演算子で連結した「"1たす1は…2"」という文字列を、alertに渡して表示しています。
実は、リスト1でalertに渡した「"こんにちは!"」も式です。「1」も式です。式と式を演算子でつないだものもまた式です。これを図にすると、以下のようになります(また木構造です)。
この式の木構造を、末端の方から(上の図で言えば、下の方から)順に計算していくと、最終的に式全体の値が得られます。これを式の評価(evaluation)と呼びます。
ところで、上の式について、カッコを使わず、「"1たす1は…" + 1 + 1」と書いてしまうと、「1たす1は…11」と表示されます。式が以下のように解釈されるためです。
JavaScriptの+演算子は、左右の式どちらかが文字列の場合、反対側の式を文字列に変換して、文字列の連結を行います。よって、「"1たす1は…" + 1」の部分がまず「"1たす1は…1"」となり、次にその文字列と1を連結しますから、「1たす1は…11」になるわけです。
なお、JavaScriptでは、「かけ算は足し算より優先して計算する」という小学校で習うルールはそのように解釈してくれます。よって、「1 + 2 * 3」は9ではなくて7です。「*」って何だ? と思った人がいるかもしれませんが、JavaScript(に限らず大抵のプログラミング言語)では、「*」はかけ算の記号です(×はASCIIコードにないですし、エックスと紛らわしいからかと思います)。また、割り算は「÷」ではなく「/」を使います(分数を表した表記なのだと思います)。足し算引き算かけ算割り算の四則演算の一覧を表1に載せておきます。
機能 | 演算子 |
---|---|
足し算 | + |
引き算 | - |
かけ算 | * |
割り算 | / |
1 + 1なんてプログラムを書いてまでやる計算じゃないだろう、そんなの電卓すら使わなくても一発で出せるよ! と思う人もいるでしょうから、次は「1から100まで整数の合計」を計算してみることにします。「1 + 2 + 3 + 4 + … + 100」の値を求めるということです。
実は、これは「等差数列の和」といって、高校で公式を習います。公式を習うまでもなく、ちょっと頭のいい小学生ならたとえば「(1 + 100) + (2 + 99) + (3 + 98) + … + (50 + 51)」という形に変形して、101×50で5050という答を出しそうなものです。
だからといって、ただ丸暗記した公式を使って答を出すのも味気ないですし、上記の「(1 + 100) + (2 + 99)…」といったやり方を思いつかない人もいるでしょう。思いついたとしても、検算は必要ですし、どんな問題を解くにも手段はたくさんあった方がよいものです。せっかくコンピュータがあるのですから、力技で1から100までの数を足す、という方法で計算してみましょう。そのプログラムがリスト4です。
リスト4では、各行にコメント(comment)を付けています。こちらでも説明したように、JavaScriptでは「//」より後ろはコメントです。コメントはプログラムの実行では完全に無視される、人間に対する説明のためのものです。この程度のプログラムに普通こんなにコメントは書きませんが、ここは入門記事なので、「プログラムを見れば一目でわかる」ことも敢えて書いています。
9行目の「let sum = 0;」は、let(レット)による変数宣言です。ここでは、sumという名前の変数(variable)を宣言し、それにゼロを代入(assign)しています。
sum(サム)というのは英語で「合計」という意味がありますが、これは私がつけた名前で、別段JavaScriptで決まっているとかではありません。英語が苦手なら、「goukei」のようなローマ字の変数名でも構いません。実は漢字で「合計」という変数名にしても動くのですが、漢字や日本語の変数名を使っている人はあまり見かけません。
変数というのは、「値を一つだけ書き留めることのできるメモ用紙のようなもの」と考えてください。9行目では、sumに対して「0」という値をメモしています。この、変数にメモする操作を「代入」と呼びます。同様に、10行目では、iという名前の変数に1を代入しています。なお、ここで変数名に「i」を使っているのは、慣習的なものです(後述)。
そして11~14行目は、while(ホワイル)文によるループ(繰り返し)です。
while文では、whileの後ろの「( )」の中に条件式を書き、この条件が成り立っている間、その後ろのブロック({ }で囲まれた部分)を繰り返して実行します。ブロックの後ろにはセミコロンを書かないことに注意してください。
このwhile文では、条件式が「i <= 100」です。これはiが100より小さいか等しい、ということを意味しています。数学なら「≦」という記号を使うところですが残念ながらこの文字はASCIIコードにないので「<=」で代用しているわけです。ちなみに逆にした「=<」は使えません。「不等号が先」と覚えておいてください。「大きいか等しい」ことを意味する「>=」※1もあります。
10行目でiに1を代入しているので、最初は、iは1です。そのため「i <= 100」という条件が成り立ち、12行目と13行目が実行されます。12行目で、sumにiを加算しています。これは「sum + i」、つまりsumにiを加えたものを、sumに対して代入する(メモ用紙のたとえで言うなら、前の値を消して上書きする)、という操作で、結果的にsumの値はiだけ増えます。
このように、代入の「=」は、数学で言うところの等号(等しいことを意味する記号)とは意味が全く違います。特に13行目の「i = i + 1;」など、数学の等号と考えればなんだこりゃですが(iが、i + 1と等しいはずはない)、JavaScriptでの意味は「i + 1をiに代入する」という意味なので、これは「iの値を1増やす」ことを意味しています。
iは最初に10行目で1にしましたから、1増やしたら2です。2はまだ100よりは小さいので、11行目の条件式が成立し、ブロックの中が繰り返して実行されます。そうすると、sumに対して1から100までの値が順に加算されて、最後にiが101になった時にwhileループを抜けます。よって、最後にはsumに「1から100までの整数の合計」が代入されていることになります。
ここまでの流れを、フローチャート(flowchart、流れ図)という記法で書くと、図11のようになります。フローチャートはめちゃくちゃ古い記法で、今時プログラムの設計とかに使うことはないですが、初心者が、プログラムの流れを追う助けにはなると思います。どうでしょうか。
リスト4のプログラムでは、sumとiというふたつの変数を使いました。sumは英語で「合計」という意味ですが、iはどこからでてきたのでしょうか。
リスト4におけるiのように、ループの回数を数えてループの終了を判定するために使う変数のことをループカウンタと呼びますが(リスト4のiは、sumに足す値も兼ねていますが……)、上でも書いたように、ループカウンタには慣習的にiを使うことが多いです。
――と言われると、じゃあその慣習はどこから来たんだ、と思うでしょう。直接の原因としては、世界最初のプログラミング言語であるFORTRANにおいて、I, J, K, L, M, Nで始まる変数は暗黙に整数型の変数として扱われた、というのが起源なのだと思います。
――それはそうだとして、じゃあそのFORTRANとやらはなぜI, J, K, L, M, Nで始まる変数を整数型にしたんだ、という疑問が当然でてきます。なにしろFORTRANでは、AからH、OからZで始まる変数は暗黙に実数型(小数が使える変数)であり、その間のIからNのところに整数型が割り込んでいるのです。たとえばAからHまでは実数型、Iから先は整数型、とかであれば、たまたま切れ目がIになっただけかな、とも思えますが、わざわざ途中に割り込むからにはそれなりの理由があるはずです。
integer(整数)の略じゃないかとか、index(目盛り、のような意味がある。特にプログラミングでは配列の添字の意味もある)の略じゃないかとか、数学の慣習(たとえば総和を意味するΣで使いますね。最近はkを使うこともあるようですが)ではないかとかの説があります。
ループは、入れ子にできます。つまり、ループの中にループを書くことができます。そのような場合は、ループカウンタをi,j,k,…と順に使う、という慣習もあるのですが、特にiとjは見た目が似ていて紛らわしいですし、そもそもの話、変数名というものはその変数の意味が分かるような名前を付けるべきで1文字の変数名など適切ではない、という考え方もあります。私の場合、1重のループならついiを使ってしまいますが、2重以上のループなら、それが何のカウンタであるのかわかるような名前をつけています。
ここまでで、一応1から100までの合計を表示することはできるようになりましたが、説明しきれなかったところを以下に書いていきます。
JavaScriptの先祖である、CとかJavaとかの言語は、プログラムにおける半角のスペース、タブ、改行は、基本的に意味を持ちません。whileのようなJavaScriptで最初から意味を持つ単語(予約語またはキーワードと呼びます)とか変数名とか2文字以上の演算子(ここでの例なら<=)の途中に空白や改行を入れてはだめですが、それ以外はたいてい自由に空白や改行を入れられます。これはJavaScriptでも基本的には同じで、リスト4のJavaScript部分はたとえば以下のように書いても動きます。
おおまかにルールを箇条書きすると、こんな感じでしょうか。
――と、CやJavaなら、こう説明すればよくて、簡単なのですが。
JavaScriptの場合、ここでもちょっと説明した「セミコロンのつけ忘れのようなちょっとしたミスは許容する」という迷惑なルールによって話がややこしくなっています。これはつまり「改行がセミコロンの代わりになる(まあだいたいは)」ということなので、改行が意味を持ってしまうわけです。そんなわけのわからないルールには頼らずに、セミコロンは省略せずに必ず書くようにすれば、「ほぼ」妙な問題ではまることはなくなります。それでもまったくはまらずにすむかといえばそうでもない、というのがJavaScriptの困ったところなのですが。この問題については、たとえばこのページなどが参考になるかと思います。
ところで、リスト4の12行目と13行目、while文の{ }の中身について、行頭に2文字の空白を入れているのは、ループの開始と終わりを見た目にわかりやすいようにするためです。こういうのを字下げとかインデント(indent)と呼びます。インデントは人間にとってわかりやすくするためにするもので、JavaScriptの処理系にとっては関係ありません。
リスト4では、sumとiという変数を使いました。こういった変数名は、プログラマ(みなさんのことですよ)が自分で決めてよいのですが、JavaScriptの文法上のルールはあります。具体的には、以下のようなものです。
await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public as async from get of set target
さて、ここまではJavaScriptの文法上の話ですが、それとは別に、慣習としてのルールもあります。以下のようなものです、
newUserNameのような名前の付け方は、大文字の出っ張ったところがラクダ(camel)の背のように見えることから、キャメルケース(camel case)と呼ばれます。
この手のルール(命名規則といいます)は、キャメルケースの他にも、以下のようなものがあります。
JavaScriptの変数名は上述の通りキャメルケースなのですが、一見してこれは変だと思う人もいるでしょう。大文字で単語の切れ目を示すなら、最初の文字も大文字にするのが自然ではないかと(つまり、Pascalケース)。JavaScriptでは、変数名はキャメルケースですが、クラス名(いつか説明します)はPascalケースを使います。これにより変数名とクラス名の区別をつけているわけです。このルールはJavaから引き継いだものです。
図9: 式と演算子において、「"1たす1は…"」という文字列と、1 + 1の計算結果である数値「2」を、+演算子で連結しました。ここで、「"1たす1は…"」という値は文字列型(String型)であり、「2」という数値は数値型(Number型)です。このように、JavaScriptでは、値は何らかのデータ型(data type)に属します。
文字列型と数値型は異なる型です。たとえ「"1"」という文字列でも、それはあくまで文字列であって数値ではありません。たとえば数値型なら「1 + 1」のように足し算すれば結果は2ですが、文字列型の「"1"」を「"1" + "1"」のように足し算すれば、結果は「"11"」という文字列になります。+演算子は、左右のどちらかあるいは両方が文字列型だと、文字列連結するという意味になるからです。その際、文字列でない方は文字列に変換されます。
ここまでに登場したデータ型は、以下の3つです。
alert("5 >= 3…" + (5 >= 3)); // 「5 >= 3…true」と表示される
もちろん、条件が偽になるときはfalseが表示されます。
alert("5 <= 3…" + (5 <= 3)); // 「5 <= 3…false」と表示される
JavaScriptの変数は、どんな型の値でも代入することができます。
言語によっては、整数型として宣言した変数には整数だけ、文字列型として宣言した変数には文字列だけ、というように、変数の型と同じ型の値しか代入できないものがあります。たとえばCとかJavaとかC#とかがそうです。それに対し、JavaScriptは、変数には型がありません。RubyとかPythonとか、スクリプト言語と呼ばれる言語には、このタイプが多いようです。
上のプログラムで、「hoge」という名前の変数を宣言しました。
「なんだそりゃ?」と思う人が多いかもしれませんが、このhogeという変数名は、「何の意味もない変数名」であることを示すために日本ではそこそこ使われています。ふたつ以上の変数を宣言する場合は、hogeの次にpiyoを使う派と、fugaを使う派がいます。
詳細は以下のページを参照してください。
なお、hogeは日本独自のもので、アメリカとかでは、foo、bar、bazあたりを使うことが多いです。
リスト4の12行目、「sum = sum + i;」は、「sumにiを足したものをsumに代入する」という意味で、こう書けば実際そのように問題なく動作しますが、「何かに何かを加算する」という処理はよく行うので、JavaScriptでは、これを簡便に書く記法として、「sum += i;」という書き方も用意されています。加算があるのですから当然「-=」、「*=」、「/=」もあります。変数に加算をしたいなら、「+=」で書いたほうが、「sum = sum + i;」より簡潔ですし、意図がはっきりして後でプログラムを読む人にも読みやすいでしょう。
また、「i = i + 1;」は、もちろん「i += 1;」とも書けますが、「1だけ加算する」というのもよく使うので、「i++;」と書くこともできます。これは実は「++i;」と書くこともできて微妙に意味が違うのですが、深入りしません。この入門では「i++;」だけを使います。
上の表1で、四則演算の演算子をひととおり紹介しましたが、JavaScriptでは、代入の「=」も、「+=」も「-=」も「*=」も「/=」も「++」も演算子です。ついでに、比較に使った「<=」も演算子です。代入の「=」も演算子で、右辺(右側)の式の値を左辺(左側)の変数に代入し、全体として今代入した右辺の値になります。そして、代入演算子は足し算や引き算の演算子と違って右から左に結合するので、これを利用すると、たとえば以下のように書くことでaとbとcにまとめて0を代入できます。
これは、「a = b = c = 0」という式が以下のように解釈されるからです。
この書き方は、JavaScriptの先祖であるC言語では割と多用されましたが、最近はあまり流行っていないように思います。なにもこんな書き方しなくても、普通に、以下のように書けばよいわけですし。
ただし、「代入も式である」ということは、知っておく必要があります。後で説明しますが、初心者は必ずこれではまるからです。
このように、演算子は、対象(オペランド(operand)と言います)に対して何らかの演算を行い、結果として値を返します。その過程で、変数への代入といった副作用が起きる演算子もあります。
足し算の「+」や比較の「<=」は、左右にふたつのオペランドを取りますから二項演算子と呼びます。「++」のオペランドはひとつなので単項演算子です。実はJavaScriptでは、マイナスの数値を「-5」のように書く時の「-」も、符号を反転させる(-1をかける)という単項演算子です。よって、「-a」と書けば、変数aに格納されている値がたとえば5なら-5を、aが-5なら5を返します。
代表的な演算子を表2にまとめておきます。すべてを見たければ、たとえばmozilla.orgのこのページに一覧があります。表2は優先順位の高い方から並べています。優先順位というのは、かけ算は足し算より優先して計算する、といった規則のことです(つまり、かけ算は足し算よりも優先順位が高い)。表1の「優先順位」の列には、上記のmozilla.orgのページの表にある優先順位の値を入れています。同じ数値が入っているところは、優先順位が等しいということです。
No. | 優先順位 | 演算子 | 機能 | 結合性 |
---|---|---|---|---|
1 | 20 | 関数(引数並び) | 関数呼び出し。 | 左から右 |
2 | 18 | 対象++ | 後置のインクリメント(対象に1加算する)。加算前の値を返す。 | なし |
3 | 対象-- | 後置のデクリメント(対象から1減算する)。減算前の値を返す。 | ||
4 | 17 | -対象 | 単項のマイナス演算子。オペランドの符号を反転させる。 | 右から左 |
5 | 15 | 左辺 * 右辺 | 乗算。かけ算を行ってその結果を返す。 | 左から右 |
6 | 左辺 / 右辺 | 除算。割り算を行ってその結果を返す。 | ||
7 | 左辺 % 右辺 | 剰余。割り算を行ってその余りを返す。 | ||
8 | 14 | 左辺 + 右辺 | 加算。足し算を行ってその結果を返す。オペランドのどちらかまたは両方が文字列の場合には、文字列ではない方を文字列に変換したうえで、文字列を連結して返す。 | 左から右 |
9 | 左辺 - 右辺 | 減算。引き算を行ってその結果を返す。 | ||
10 | 12 | 左辺 < 右辺 | 左辺が右辺より小さければtrueを、そうでなければfalseを返す。 | 左から右 |
11 | 左辺 <= 右辺 | 左辺が右辺より小さいか等しければtrueを、そうでなければfalseを返す。 | ||
12 | 左辺 > 右辺 | 左辺が右辺より大きければtrueを、そうでなければfalseを返す。 | ||
13 | 左辺 >= 右辺 | 左辺が右辺より大きいか等しければtrueを、そうでなければfalseを返す。 | ||
14 | 11 | 左辺 == 右辺 | 左辺と右辺が等しければtrueを、そうでなければfalseを返す。両辺の型が異なる場合、適当に変換して比較する。 | 左から右 |
15 | 左辺 != 右辺 | 左辺と右辺が等しくなければtrueを、等しければfalseを返す。両辺の型が異なる場合、適当に変換して比較する。 | ||
16 | 左辺 === 右辺 | 左辺と右辺が等しければtrueを、そうでなければfalseを返す。両辺の型が異なる場合、無条件にfalseを返す。 | ||
17 | 左辺 !== 右辺 | 左辺と右辺が等しくなければtrueを、等しければfalseを返す。両辺の型が異なる場合、無条件にtrueを返す。 | ||
18 | 7 | 左辺 && 右辺 | 「かつ」。両辺が論理型の場合、左辺と右辺の両方がtrueならtrueを、そうでなければfalseを返す。 | 左から右 |
19 | 6 | 左辺 || 右辺 | 「または」。両辺が論理型の場合、左辺と右辺のどちらかがtrueならtrueを、両方がfalseならfalseを返す。 | 左から右 |
20 | 3 | 左辺 = 右辺 | 代入演算子。右辺の値を左辺の変数に代入し、その代入した値を返す。 | 右から左 |
21 | 左辺 += 右辺 | 加算の複合代入演算子。右辺の値を左辺の変数に加算する。加算後の値を返す。 | ||
22 | 左辺 -= 右辺 | 減算の複合代入演算子。右辺の値を左辺の変数から減算する。減算後の値を返す。 | ||
23 | 左辺 *= 右辺 | 乗算の複合代入演算子。右辺の値を左辺の変数の値にかけてその値を左辺に代入する。乗算後の値を返す。 | ||
24 | 左辺 += 右辺 | 除算の複合代入演算子。右辺の値で左辺の変数の値を割ってその値を左辺に代入する。除算後の値を返す。 |
優先順位が等しい演算子が複数ある場合、右端の「結合性」により結合の順序が決まります。結合性はほとんど「左から右」ですが、これはたとえば「1 + 2 + 3」という式は、「(1 + 2) + 3」と解釈されるということです。「右から左」なのはNo.3の単項マイナス演算子とNo.18以降の代入演算子くらいですが、単項マイナス演算子が右から結合というのは「- - - 10;」と書いたときに「- (- (- 10))」と解釈されるという意味です※3。代入演算子が右から結合するのは、図12で説明しました。
この表に上がっている演算子で、特徴的なところといえば以下くらいでしょうか。
let hoge = alert; // 変数hogeにalertを代入する hoge("こんにちは!)"; // hogeにはalertが代入されているので、これでも表示が行われる
上で、関数呼び出し演算子について、「関数呼び出し演算子は、その関数の戻り値を返します」と書きました。しかし、ここまでに登場している唯一の関数であるalertは、役に立つ戻り値を返しません。常にundefined(未定義)という特別な値を返します。これでは関数がどういうものかいまいちわからないことでしょう。
関数(function)とは、いくつかの引数(0個かもしれません)を受け取り、なんらかの計算を行って、その結果を戻り値として返すものです。ここでは、ふたつの値を引数として受け取り、それを足し算して返す、add()という関数を作ってみることにします(add(アッド)とは、英語で足し算を意味します)。
9行目から11行目までが、関数定義(function definition)です。関数の定義はこのように、予約語functionで開始し、その後に定義したい関数名と引数の並びを書きます。
add()関数は、ふたつの値を引数として受け取るので、引数並びにはaとbというふたつの仮引数(parameter)が並べてあります。仮引数というのは、関数が呼び出されたとき、呼び出し側で与えた引数の値があらかじめセットされている変数です。仮引数の変数名は自由に決めて構いません。
10行目はreturn文です。return文は、その後ろに書いた式の値をこの関数の戻り値として返し、関数の実行を終了します。add()関数は、ふたつの引数を足して返す関数なので、ここでaとbを足し算しています。
13行目で、このadd()関数を呼び出しています。3と5を引数として呼び出しているので、ここで処理がadd()関数に移り、aに3、bに5が代入された状態で、関数add()の{ }に囲まれた部分の実行が開始されます。ここでreturn文が実行されて、add()関数は3と5を足し算した8を返します。alertの引数の中で、「"3 + 5..."」という文字列と、戻り値の8を連結していますから、「3 + 5...8」と表示されることになります。
足し算なんてわざわざ関数を呼び出さなくても+演算子でいつでもできるので、このadd()関数には実用性はないでしょう。しかし、もっと複雑な計算を何度も行う場合、関数にしておけば、一度書くだけであちこちから呼び出すことができます。関数を作らずに同じ処理をあちこちにコピー&ペーストすると、修正する場合あちこちを直して回らないといけません。その点、関数にしておけば、直すのは1か所ですみます。
式の後ろにセミコロンを書くと、式文(expression statement)という文になります。
式文を実行すると、その式が評価されます。つまり、このページの最初の
というプログラムは、「alert("こんにちは!")」という式を評価して、その結果としてポップアップが表示された、ということになります。このように、式文は、ポップアップが表示されたり、変数に値が代入されたりといった副作用を起こすために書きます。もちろん、副作用がなくても式文は書けるので、たとえば以下のような式文を書いても処理系は文句を言いませんが、
「1 + 3」という式には副作用がないので、評価しても何も起きません(無駄に4が算出されて、捨てられるだけです)※4。
ここまで、プログラムを上から順に実行する順次実行と、ループ(繰り返し)について扱いましたが、プログラムを書く上ではもうひとつ重要な要素があります。それは条件分岐です。
条件分岐というのは、ある条件式について、条件が成り立てばこちらのコードを、成り立たなければこちらのコードを実行する、という処理です。JavaScriptでは条件分岐にはif文を使います。
条件式が偽の時に実行する処理がないのなら、else以下は省略可能です。
具体的な使い方は、後の「BMI計算プログラムを読んでみる」で見ていきましょう。
複数の文を{と}で囲むと、ブロック文(block statement)というひとつの文になります。
複数の文を{と}で囲んだものは、whileやif文で登場していましたが、実はこれがブロック文です。そして、たとえばwhileで繰り返したい処理は、文がひとつであれば、{ }で囲まずそのまま書けます。つまりwhile文は、もともと「後ろに書いたひとつの文を繰り返し実行する」ものであり、繰り返したい処理を{ }で囲んだのは、こうすれば複数の文を繰り返すことができるからです。
このように、while文やif文、もしくはこの後で示すfor文では、対象の文がひとつなら、{ }で囲む必要はありません。そのように、処理がひとつの時に{ }を使わずに書く書き方を「ぶら下がり文」(dangling statement)と呼びます。
しかし、世間では、ぶら下がり文は使わず、文がひとつでも{ }で囲むようにしている、というプログラマは少なくありません(私もおおむねそうです)。最初は1文でよかったが、何らかの理由でもう1文追加しなければならなくなった、ということはよく起きるものですし、以下のようなケースでは、最後のelseがどちらのifに対応するのかわかりにくく、バグの元になるためです。
ちなみに、elseは「最も近いif」に対応します。つまり上の例では、「処理b」は、「条件式a」が真で、「条件式b」が偽の時に実行されます。インデントからして、このコードを書いた人の意図は、「条件式a」が偽の時に処理bが実行されてほしかったのでしょうが、そうはなりません。
こういう事故を避けるために、基本的には{ }は省略しないで書くのが安全でしょう。上の例なら、最初からこう書いていればよかったわけです。
ただし、よく使われる構文の中で、ぶら下がり文を利用しているものがあります。それが、以下のようにelse ifを連続させる書き方です。
この例では、elseの後ろにif文が{ }なしでくっついているのでぶら下がり文ですが、いろいろな条件でいろいろな処理をしたい場合にわかりやすいので、この書き方はよく使います。
ぶら下がり文を厳密に排除するなら、以下のように書かなければいけないことになりますが、こんなことをしても誰も嬉しくないですからね。
リスト4では、while文でループを行いましたが、実際にこの手の処理を書くときは、for文を使うことが多いでしょう。
これは、while文を使って以下のように書くのと同じことです。
while文で同じことを書けるなら、for文など必要ないのでは、と思う人もいるかもしれませんが、ループにはある程度定型的な書き方があるので、それをストレートに書けるfor文の方が実際には出番が多いものです。
for初期化式のところには変数宣言も書けます。また、初期化式、条件式、加算式はどれも省略可能です(セミコロンだけ書けばよい)。条件式を省略すると、常にtrueとみなされます。
よって、以下のように書くと、alertのポップアップが無限に出続けます(サンプルはこちら。タブごと閉じれば消せます)。
――まあ、これをインターネット上で公開すると、兵庫県警につかまったりするようですが※5。
リスト4をfor文で書き直すと、以下のようになります。
ところで、ここの例では、「1から100までの整数の合計」を計算するという問題の都合上、iを1で初期化していますが、実際のJavaScriptプログラミングでは、ループカウンタのiは0で初期化するのが普通です。その場合、条件式の方はi <= 100だとループが101回まわってしまうので、i < 100と書きます。ループの中では、iは、0から99の値を取ります。
このように書くのは、JavaScriptおよびその元になったCとかJavaのような言語では、配列の添字が0から始まるためだと思います。配列については、この入門が続いたらいずれ説明します。
上で、
while文で同じことを書けるなら、for文など必要ないのでは、と思う人もいるかもしれませんが、ループにはある程度定型的な書き方があるので、それをストレートに書けるfor文の方が実際には出番が多いものです。
と書きましたが、Googleが比較的最近(といっても2009年なのでもう10年以上前ですが)作ったプログラミング言語Goでは、while文は削除されてしまってfor文しかありません。
JavaScriptでも、for文の初期化式や加算式は省略できるので、どちらも省略すれば実質while文と同じです。
Goの場合、普通のfor文はこんな形で書きますが、
Goの場合、条件式だけ書くこともできるので、こうなるともう確かにwhileと変わりませんね。
「=」といえば、JavaScriptでは代入を意味しますが、普通、算数や数学の世界では等号であり、両辺が等しいことを意味します。JavaScriptでは等しいことを意味するのは===なんだとわかってはいても、ついつい=で書いてしまうこともあるでしょう。私自身、初心者Cプログラマだった頃に何度かやりました。というか初心者は必ずこれをやらかします。
これだと、a = 0は代入という式なので、aに0が代入されて、代入式自体は0を返します。そして、0はfalsyな値なので、「aが0の時に実行したい処理」は絶対に実行されません。
実のところこの問題は、たとえばJavaでは(ほぼ)解決されています。Javaでは、if文の( )の中のような、条件式を書くべきところには、論理型の式しか書けません。よって、論理型同士の比較でかつ左辺が変数だというあまりありそうにないケースでない限り、間違って==のつもりで=と書いてしまっても、コンパイルの時点でエラーになります。
JavaScriptだって、条件式が論理型でなければ実行時にエラーにしてくれていれば、こんなミスではまることはなかったはずです。だいたい、「0はfalseとみなす」などというのは、論理型がなかった昔のCの名残であって(大昔のBASICなんかもそうでしたが)、論理型が存在するいまどきの言語でこんなものを踏襲する必要はなかったはずです――とはいえ実のところPythonやPHPも似たようなもので、いやPHPはもっと複雑で、なんでもっとこうシンプルに「疑わしきは全部エラー」にしてくれないのかと私などは思うのですが。
JavaScriptでは、代入の演算子が「=」です。しかし、世間一般では=といえば等号です。なのでうっかり=で比較してしまって、上に書いたような問題ではまるわけです。
代入に「=」を使うというのは、世界最古のプログラミング言語であるFORTRAN以来のもので、JavaScriptはCを経由してこれを引き継いだわけですが、すべての言語で代入が=というわけではありません。たとえばPascalという言語では、代入は「:=」です。:=を代入に使う言語は、1960年に開発されたプログラミング言語ALGOLまでさかのぼるので、1972年のCでは:=を採用することもできたと思うのですが、おそらくは、使用頻度の高い代入には短い記号を割り当てたかったのでしょう。Cの元になった1966年のBCPLでも代入は=です。ただし、BCPLの元になったCPLでは:=でした。
代入に、=と:=のどちらがよいか? と聞かれたら、比較と紛らわしくないだけ:=の方がよいのでは、と思う人は多いと思います。しかし、プログラミング言語というものは、少なからず「慣れ」により使い勝手が決まってしまうところがあるので、=を使ったCが広く普及してしまった以上、以後の言語もそれを引き継ぐことになったのでしょう。
上で、「JavaScriptだって、条件式が論理型でなければ実行時にエラーにしてくれていれば、こんなミスではまることはなかったはずです。」と書きました。また、こちらには、「あなたのミスを、処理系が見つけてくれるわけですから、そういう時に「なんだこの野郎!」と思ってはいけません。エラーを見つけてくれる処理系さんに感謝して、ちゃんとミスを直しましょう。」と書きました。
いかに経験を積んだプログラマでも人間ですし、人間は、大なり小なり必ずミスを犯すものです。ましてやみなさんは初心者なので、間違えて当たり前です。じゃんじゃんミスを犯して、そのたびに処理系にエラーを出してもらって、直し方を覚えればよいのです。
どうも、プログラミング初心者の中には、処理系がエラーを出すと「怒られた」ような気がする、という人がいるようです。何度もエラーを出すと「恥ずかしい」と感じるとか。確かに、人間相手なら、しょうもないミスをすると怒る人もいるでしょう。もちろん人間はミスして当たり前なので、怒るのはどうかと私は思いますが、何度も何度も同じミスをしていたら温厚な人でもイラッとするかもしれません。人前で何度も間違えれば「恥ずかしい」というのもわかります。
しかし、さいわいにして、あなたの書いたミスだらけのJavaScriptプログラムを見るのは、人間ではなく機械(コンピュータ)です。機械は怒りませんし、機械を相手に恥ずかしがっても意味がありません。
プログラマは、ミスを犯して、それを直していくことで成長するものです。処理系がエラーを出したら、「勉強のチャンスだ!」ぐらいに思っておくのがよいでしょう。
そうはいってもエラーメッセージは英語だし……という人は、以下のページを見てください。JavaScriptのエラーの一覧があります。
JavaScript エラーリファレンス - JavaScript | MDN
ただ、この一覧は、mozilla.orgで公開されているわけですからFirefox向けのもので、Edgeだとメッセージが微妙に違います。
たとえば下図の例では、こちらでちょっと説明したstrictモードで、宣言していない変数にいきなり代入しています。そのため、「Uncaught ReferenceError: bar is not defined」というエラーメッセージが出ています(F12を押して開発者ツールを開き、「コンソール」タブのエラーメッセージをクリックしてソースを表示した状態)。
「Uncaught」はちょっと無視するとして、「ReferenceError: bar is not defined」というメッセージは、上記のエラー一覧に似たようなのが載っています。
「"bar" is not defined」ではなく「"x" is not defined」になっていますが、これはbarという変数名はこっちで勝手につけたものだからですね。
で、「ReferenceError: "x" is not defined」をクリックしてリンク先を見ると、「JavaScript の例外 "variable is not defined" は、どこかで参照している変数が存在しない場合に発生します。」とあって、確かにbarは宣言していないから存在していないわけで、この説明で満足してよさそうにも思えます。
ただ、この同じプログラムをFirefoxで実行すると、「Uncaught ReferenceError: assignment to undeclared variable bar」というメッセージが出ます。
これは、上の一覧でも「ReferenceError: "x" is not defined」のすぐ下に出ています。なのでそちらをクリックして説明を見ると、このエラーについての、ブラウザごとのメッセージが載っています。
上から順に、Firefox、Chrome、Edgeとあり、今我々が使っているのはEdgeなので最下段、ではなく、今のEdgeはGoogle Chromeと同じChromiumベースなので、Chromeと同じに扱って真ん中を見ます(一番下の「Edge」は、こちらの2番目に出ている古いEdgeのことです)。真ん中の行には確かにEdgeが出したエラーメッセージ「"x" is not defined」が載っています。
つまり、Firefoxではエラーの原因により細かく分けたメッセージを出しているところ、Edge(Chrome)では、もうちょっとおおざっぱに、同じメッセージで済ませてしまっている、ということです。まあ、これぐらいで十分わかる気もするので、それが悪いとは思いませんが。
Edge(Chrome)のエラーメッセージ一覧の日本語版は、どこかにあるのかもしれませんが、私には見つけられていません。なお、Firefoxなら、図17の「詳細」のところをクリックすればmozilla.orgの該当のエラーの説明のページに直接飛べるので、エラーメッセージの解読に手間取るようならFirefoxでデバッグする方が早いかもしれません。
なお、エラーメッセージのうち「ReferenceError」までは、ECMAScriptの仕様で決まっているので、全ブラウザ共通です。
また、「ReferenceError: bar is not defined」を普通に日本語に訳せば、「参照エラー: barが定義されていません」となります。「bar is not defined」をたとえばDeepL翻訳あたりに入力すれば「バーが定義されていない」ぐらいの翻訳はしてくれるので、そういうものを使うのも手です。そうこうするうちに、よく見る英単語は覚えてしまうことでしょう。
ちなみに、define(デファイン)というのは、「定義する」という意味の動詞です。そしてdefinedはdefineの過去分詞形で、つまりこの文は受動態です。受動態は中学で習います。学校の勉強は役に立つものです。
ここからは、「はじめに」で紹介したBMI計算プログラムを読んでいきます。足りない知識は都度補います。
BMI計算プログラムを単独のHTMLに切り出したものをここに置きました。そのHTML全体で行番号を振りなおしたものを以下に再掲します。
まずは、身長体重の入力欄、「計算する」ボタン、結果の表示欄を構成するHTMLからです。
このHTMLにより、以下のように表示されます。
身長: cm 体重: kg
ここに結果が表示されます。
HTMLの「タグリファレンス」のようなものはWeb上には山のようにあるので(たとえばここ)、細かい説明はしませんが、ここで使用している要素は以下の通りです。
18行目から、いよいよscript要素が始まります。
最初の1行には、「"use strict";」と書いてあります。
これは、こちらでも説明した、JavaScriptをstrictモードにするためのものです。これにより、エラーチェックが(多少ですが)厳格になります。これから書くJavaScriptには付けるようにしましょう。
この「"use strict";」という文は、式文です。strictモードは後になってJavaScriptに取り入れられましたが(ES5以降)、ES5以前の古いブラウザなら、この文は単なる式文として「"use strict"」という式を評価だけして何も起きません。こうすることで、古いブラウザではエラーにならず、ES5対応のブラウザならstrictモードにする、ということを実現しています。
こちらで、「HTMLは木構造ととらえることもできる。これをDOM(ドム: Document Object Model)と呼ぶ」ということを書きました。
今回の、BMI計算プログラムのHTMLは、下図のようなDOMになります。
以下のプログラムの21行目の、「document.getElementById("calc_bmi_button");」は、id属性に"calc_bmi_button"という値を持つ要素を探して取得する、という意味です(「calc」(カルク)はここではcalculateの略で、計算する、という意味です)。
getElementByIdというのは、その名の通り、要素(element)を、id属性によって(by id)、取得(get)する、という、メソッド(method)です。
「メソッド」という言葉がいきなり出てきましたが、メソッドとは、オブジェクトに付属する関数のことです。ここでは、getElementByIdメソッドは、Documentというオブジェクトに付属するメソッドであり、documentという変数がこのオブジェクトを「指している」ので、document.getElementById(...)のように呼び出しています。「オブジェクト」という言葉も突然出てきましたが細かい説明はいつか行うとして、Document オブジェクトは、図19におけるHTML要素、つまりDOMの根っこを保持しているオブジェクトです。図にすれば以下のようになります。
id属性は、HTML内で要素を区別するために付けるものなので、HTML全体の中で、別の要素に同じid属性を着けてはいけません(こういうのを、id属性はHTML内で一意でなければならないとか、ユニーク※6でなければならない、といいます)。よって、id属性で要素を探せば、見つからないか、ひとつだけ見つかるかのどちらかです(複数見つかる、ということはあり得ません)。ここでは、"calc_bmi_button"というid属性のついた要素がひとつあることがわかっているので、見つからない場合は考慮しません(絶対見つかるので)。
こうしてbutton要素を見つけたら、そのボタンに対し、「そのボタンを押したときに何をするか」を設定します。それが25行目の「button.onclick = calcBmi;」です。
これは、変数buttonが指すボタンの、onclick(オンクリック)というプロパティ(property)に、calcBmiという関数を設定しています。プロパティというのはオブジェクトに属する変数のことで、onclickプロパティは、ボタンを押したときに呼び出す関数を設定するためのプロパティです。JavaScriptでは関数は値として扱えるので、こうして関数をプロパティに代入できるのです。
ここで、「ボタンが押された(clickされた)」といった出来事のことをイベント(event)と呼びます。そして、イベントが発生した時に呼び出される関数のことをイベントハンドラ(event handler)と呼びます。
「計算する」ボタンのイベントハンドラであるcalcBmi関数は、この続きで作ります。
図20で、変数documentはDocumentオブジェクトを「指して」います。このような状態のとき、「変数documentはDocumentオブジェクトの参照(reference)を保持している」と言ったりします。
上で、変数は「メモ用紙のようなもの」と書きました。たとえば変数hogeに5という数値を代入することは、hogeという名前のメモ用紙に「5」と書き込むことだと考えることができます。次に、「let piyo = hoge;」と書いたら、それはhogeに書いてある5という値をメモ用紙piyoに書き写すことだと言えます。
このように考えるとき、「5」という値は、hogeとpiyoの両方に書き込まれていますから、ふたつに増えています。
しかし、図20にあるように、変数buttonがHTMLのbutton要素を「指している」とき、let button2 = button;のようにbuttonの内容をbutton2に代入しても、ボタンがふたつに増えたりはしません。この時、書き写されている(コピーされている)のは、ボタンを参照する値(参照値)だからです。
このことは、たとえば以下のようなプログラムで検証できます。
リスト10と同じように、11行目でgetElementByIdによりボタンの要素を取得し、それを12行目でbutton2にコピーしています。
13行目でクリックしたときに動く関数にchangeButtonを指定していますから、このボタンを押すと、15行目からのchangeButton関数が動きます。16行目では、変数button1が指すボタンの表示文言(「ボタンだよ!」)を、「変更したよ!」に書き変えています。そうしたときに、button2の表示文言はどうなるかを、17行目のconsole.log()で出力しています。この出力は、F12で開発者ツールを起動してコンソールを表示させると読むことができます。
button1のtextContentを書き変えたら、button2のtextContentも書き変わっていることがわかります。button1もbutton2も、どちらも同じボタンを指しているからです。
――ところで、実のところ、5のような数値型を変数に代入した時も、同じように、変数には参照値が格納されると考えることもできます。
リスト10では、button1からボタンのtextContentを書き変えたとき、button2が指しているボタンのtextContentも変わってしまいました(同じボタンなので当然です)。しかし、参照先のものが一切変更不能であれば(immutable:イミュータブル、と呼びます)、変数に入っているものが参照であろうが、値そのものであろうが、プログラマからは区別できません。「hoge++;」のようにして変数の内容を変更するときは、その変数が挿している先の値を書き換えるのではなく、新しい値を持ってきて変数の参照をそちらに向けます。
実際に、JavaScriptの処理系が、変数の値を内部的にどのように保持しているかといえば、おそらく数値型あたりは、メモ用紙に「5」と書き込むように、変数が実際の値を保持しているのだと思います。ただし文字列型は、参照を保持しているはずです。文字列はものによって長さが異なり、長いものもあるからです。
JavaScriptにおけるデータ型は、プリミティブ型とオブジェクト型に分けられます。今まで出てきた中では、数値型、論理型、文字列型はプリミティブ型で、関数型やDOM要素のオブジェクトはオブジェクト型です。そして、プリミティブ型はすべてイミュータブルなので、値を保持しているのか、参照を保持しているのかを我々が意識する必要はありません。
ここまで、なんというか適当にbody要素の中にscript要素を書いて、それで問題なく動いていました。script要素は、要素の階層構造が壊れるような書き方をしなければどこに書いても構いません。ただ、実行されるタイミングは意識する必要があるでしょう。JavaScriptは、HTMLがブラウザに読み込まれてDOMの木構造を構築する途中、script要素のところを読み込んだタイミングで実行されます。
BMI計算プログラム(リスト7)では、身長体重の入力欄や「計算する」ボタンを表示するためのHTMLを先に書き、その後にscript要素を書いています。この順であれば、script要素の中から、document.getElementById()でDOMの要素を取得することができます。逆に、script要素を先に書いてしまうと、document.getElementById()で要素を取得することはできません。まだその部分のDOMが作られていないからです。
この入門では、当面は、script要素をbody要素の末尾に書いていくことにします。
いろいろ事情があって最近は流行りませんが、昔は、初心者が最初に書くJavaScriptプログラムと言えば、document.write()を使うのが定番でした。
これを実行すると「1たす1は、 2 です。」と表示されます。
HTML内の「1たす1は、」と「です」の間でJavaScriptが実行され、document.write()の出力が表示にはさみ込まれていることがわかります。
上の説明を読んで、「あれ? 俺が過去見たHTMLでは、JavaScriptはたいていHTMLの前の方、head要素の中あたりに書いてあったぞ」と思った人もいるかもしれません。しかし、その場合、そのJavaScriptにはたいてい関数定義しか書いてなかったのではないでしょうか。関数定義はその場では実行されないので、HTMLの前の方に書いても問題ありません。その上で、その関数の実行の開始は、たとえば以下のように、HTMLや画像の読み込みが終わったタイミングで実行されるように仕掛けます。
JavaScriptはHTMLとは別ファイルに記述することもできます。ある程度大きなJavaScriptなら、そうする方が普通だと思います。
こう書くと、HTMLと同じフォルダにあるhoge.jsというファイルが読み込まれます。hoge.jsに記述されているJavaScriptの実行タイミングは、上記の読み込みのscript要素の中にJavaScriptをじかに書いた場合と同じです。
ここからは、「計算する」ボタンを押したときに動作する、calcBmi()関数を見ていきます。まずは冒頭から。
31~32行目で、ユーザがテキストフィールドに入力した身長・体重を、height、weightという変数に代入しています。
このheight、weightのように、関数の中で宣言された変数をローカル変数(local variable)と呼びます。ローカル変数はこの関数専用の変数で、関数の外では値を参照できません。そのため、他の関数のローカル変数と名前がかぶっても構いません。
それに対し、関数の外で宣言した変数をグローバル変数(global variable)と呼びます。グローバル変数は数が増えてくると覚えきれませんし、名前がかぶらないようにするのも大変なので、関数内でしか使わない変数は可能な限りローカル変数にします。
上でもちょっと説明しましたが、ここでletではなくconstで宣言しているのは、このheightとweightという変数は宣言時に一度代入したら二度と値を変更しないからです。値を変えなくてよい変数なら、constで宣言する方が、その意図がはっきりしますし、誤って値を変えてしまうという事故を防ぐことができます。
『あれ? 二度と値を変更しないといっても、身長・体重を変えて「計算する」ボタンを何度も押したらcalcBmi()はそのたび呼び出されるのだから、2回目にボタンを押したときにはheightやweightに2度目の代入が行われるんじゃないの?』と思った人がいるかもしれません。しかし、ローカル変数は、関数呼び出しごとに毎回新しく作られます。よって、2回目の呼び出しでも、31~32行目のheightとweightへの代入は、初回の代入とみなされます。よってconstでも大丈夫です。また、ローカル変数は関数呼び出しごとに新しく作られるので、前回の呼び出しでローカル変数に何か代入したとしても、その値は次回の呼び出し時には残っていません。
さて、まだローカル変数の宣言までしか見ていません。ローカル変数heightに代入する値を取得するところもなかなかにややこしいので、31行目をもう一度見てみます。
ここでは1行でいろいろなことを一気に行っているので、途中の値をいったん変数に代入することで、分解してみましょう。上の1行は、以下のように3行に分解することができます。
document.getElementById()での要素の取得は何度もやっていますね。そうして取得したinput要素のvalueプロパティから、ユーザがinput要素に入力した値が取り出せます。ただし、その型は文字列です。ユーザの身長が170cmだとして、「170」と入力したら、上のheightStrに代入されるのは"170"という文字列であり、170という数値ではありません。input要素は数値だけ入力するためのものではないので(名前や住所も入れたりするでしょう)、当たり前です。この文字列を数値に変換するために、Number.parseFloat()というメソッドを使っています。parse(パース)というのは英語で「解析する」というような意味で、float(フロート)は浮動小数点数を意味します。浮動小数点数というのは、小数を表現するときに、小数点の位置を動かせる(浮動させる)ようにして、大きな数も小さな数も表現できるようにしたもので、上でも説明した通りJavaScriptではIEEE754の倍精度浮動小数点数を使っています。つまりparseFloat()とは、文字列を解析して浮動小数点数に変換する、というメソッドです。
「document.getElementById()の時はdocumentは小文字なのに、Number.parseFloat()のNumberはなぜ先頭が大文字なんだ」と思う人がいるかもしれません。端的に言えば、documentは変数名だがNumberはクラス名だからです。
図20で示したように、documentは、Documentオブジェクトを指す変数であり、document.getElementById(...)と書けば、そのdocumentが指すDocument以下のDOMから、要素を探します。そういう意味で、getElementById()というメソッドは、ある特定のデータ(Document)と紐づいています。こういうメソッドをインスタンスメソッド(instance method)と呼びます。
もし、JavaScriptを使ってブラウザのウインドウ(またはタブ)をもうひとつ開いたら(window.open()というメソッドで可能です)、それぞれに異なる文書を表示することになるので、Documentオブジェクトはふたつになります。その場合でも、document.getElementById()はあくまでそのdocumentが指している方のDocumentから要素を検索します。
それに対し、parseFloat()は、引数として与えた文字列を浮動小数点数に変換するだけなので、特定のデータとは紐づきません。こういうメソッドを静的メソッド(static method)と呼びます。Number.parseFloat()のNumberは、あくまでメソッドを分類整理する意味しか持ちません。
――まあ、この辺のことはややこしいので、今の時点で理解する必要はなく、「そんなもんだ」と使っていればよいかと思います。それにしても、クラス名を先頭大文字で書いて、クラス名.メソッド名の形で静的メソッドを作るとか、これは元々Javaの書き方です。クラスのないJavaScriptでこんな書き方を無理やり真似するくらいなら、最初からクラスを導入しておいた方がよっぽど簡単だったと思います。JavaScriptは、「デザイナーのような初心者向けに簡単にしたJava」であるはずが、実際には、Javaよりはるかに難しい言語になっているように思います。
calcBmi()関数の続きを読んでいきます。
この先、いろいろなメソッドが登場しますが、先のNumber.parseFloat()を含め、いちいち覚える必要はありません。必要になったら調べればよいことです。ここからはサクサクいきます。
ここまでで、ユーザが入力した身長と体重を、それぞれheight、weightという変数に格納できた――はずですが、何しろ入力するのは人間なので、身長の入力欄に「abc」とか入れる人もいるかもしれません。そういう入力に対してはきちんとエラーチェックをする必要があります。
入力エラーに対しては、「身長は数値で入力してください」とか「体重は数値で入力してください」というエラーメッセージを表示します。まずはそのエラーメッセージの表示に使う要素を、36行目のgetElementById()で、result(リザルト/結果)という変数に取得しています。
リスト13の32~33行目で、ユーザが入力した文字列をNumber.parseFloat()で浮動小数点数に変換しましたが、Number.parseFloat()関数は、「abc」のような数値に変換できない文字列を渡されたときは、NaN(Not a Number)を返します。NaNだそりゃ? と言いたくなるかもしれませんが、これはNot a Numberという名があらわす通り、「(有効な)数値ではない」ということを意味する特別な値です。ある変数がNaNかどうかをチェックするには、Number.isNaN()メソッドを使用します。
身長がNaNだった場合、39行目のNumber.isNaN(height)がtrueになって、40~41行目が実行されます。40行目で、36行目で取得した結果表示用の要素のtextContentプロパティに、「身長は数値で入力してください」というメッセージをセットしています。これにより、HTMLのspan要素部分(リスト8の15行目参照)のところに、このメッセージが表示されます。そのうえで、もうcalcBmi()関数でやることはないので、41行目のreturn文でこの関数から戻ります。calcBmi()には戻り値は不要なので、returnの後ろの式は省略しています。
体重も同じようにチェックして、エラーチェックは終わりです。
BMIは、体重(kg)を身長(メートル)の2乗※7で割って算出します。その式を単純に計算しているのが、以下の47行目です。
ユーザの入力したheightはセンチメートルなので、BMIの定義に合わせるために100で割り、2乗するために同じものを掛け合わせています。それで体重を割ればBMIになります。なお、この入門はES2015を使うことにしたのでなのでこう書いていますが、ES2016からはべき乗演算子「**」が導入されたので、この式は以下のようにも書けます。
その後、52行目で、「あなたのBMIは○○です」という文言を作り出すのですが、この時、単純に「+」で文字列連結すると、JavaScriptがデフォルトで浮動小数点数を文字列に変換する際の変換桁数が多すぎて、「あなたのBMIは24.221453287197235です。」のようになってしまいます。まあこれでもよいのかもしれませんが、こんな精度は必要ない(というか意味がない)ので、小数点以下1桁までで文字列化するよう、数値型(Number)のインスタンスメソッドであるtoFixed()を呼び出しています。toFixed()は、引数で与えられた数が小数点以下の桁数になるように、対象の数値(この場合はbmi)を文字列化して返します。
これにより、「あなたのBMIは24.2です。」のような文言が作れます。
このプログラムでは、「あなたのBMIは24.2です。」という文言の後ろに、BMIに応じて、「痩せすぎですね。」とか「普通体重ですね。」といった言葉をくっつけて表示します。その判定をしているのが以下の部分です。
こちらで説明したelse ifの構文を使って、BMIにより処理を分岐し、後ろ半分の文字列を連結しています。
そうしてできた文字列を、68行目で、36行目で取得した表示用の要素に割り当てれば、その文字列が表示されます。これでcalcBmi()の処理は終わりです。
このBMI計算プログラムは、そんなに長いプログラムでもありませんが、ちょっと検索すれば似たような機能を公開しているWebページが山ほど見つかります。つまりそれだけ実用性があるということだと思います。
この程度のプログラムで、まあそこそこ実用的なプログラムが作れる、ということがわかったのではないでしょうか。
さて、だいぶ脱線しましたが、この入門の題材は「UFOゲームを作る」ことなので、次のページからはゲーム作りに入ります。
公開日: 2021/06/20
前のページ | 次のページ | ひとつ上のページに戻る | トップページに戻る