JavaでわかるJavaScript入門
JavaでわかるJavaScript入門:
「JavaができればJavaScriptできるよね?」とか言う人に実際に会ったことはないのですが、人には言ってたりします。
もちろん実のところはそう簡単にはいきません。
いきませんが、2018年も年末になってすら、ふと迷い込むとJavaScriptはJSPのおまけぐらいに思われていたりするわけです。さすがにJavaアプレットと混同している人はそんなに…いや、いるっぽいですね。私のパーソナライズの結果だといいんですが。
甚だしくは新人でない人がそのようなことをもっともらしく言ってみたり、90年代の知識で(今は2010年代の後半なんですよ?驚きですよね…)、JavaScriptをちょっとしたhtmlの<marquee>アクセント</marquee>程度にしか考えてなかったりするわけです。
JavaScriptがJavaプログラマにとって絶妙な加減で難しい位置にいるのも確かです。JavaScriptは頭にJavaと付いているだけあって、一応にもJavaの親の友達の子供ぐらいの関係にはあります。要するに他人なんですが、他人でありながらもJavaの幼馴染のようなもので、Javaプログラマならなんとなくで結構書けてしまううぐらいには似ています。しかし本質的には赤の他人なので、そういう立場で接していると地雷を踏んでしまい、よくわからないので深入りはしないようにしよう、となってしまうわけです。
この記事ではなんとなくJavaScriptできるんだけど、なんとなくでしかないJavaプログラマに対して、JavaScriptを学ぶ際に知っておけるとよいことを並べています。これだけでJavaScriptができるようになるとは思いませんが、その助けになれば幸いです。
なお、この記事の原本はけっこう昔に書いたので、多少古く、Symbolやasync/awaitなどの最近のJavaScriptらしいものは端折っています。来年だともうそこに触れないわけにはいかないので、これがJavaの知識ですんなりJavaScriptに入れる最後のチャンスです、ということにしておいてください。個人的には、JavaScriptのコア部分さえ分かれば最新のJavaScriptの理解は容易だと思います。
JavaScriptとか本当に何も知らないんだけど、という人はごめんなさい。とりあえず文法ぐらいはまあわかるよ、という人向けです。技術的におかしいものがあれば遠慮なく突っ込んでください。あと、本当かよと思ったらnode.js環境とか用意して実際にやってみてください。Javaに慣れると(Javaにも今やJShellがありますが)REPL環境のことを忘れがちですよね。
まずは型の話からスタートしましょう。JavaScriptは動的型付けです。動的型付けというのは、型がないのではなく型をあらかじめ固定しておかないことですので、型自体はあります。主要な型はだいたい
JavaScriptの型の特性はおおよそJavaと似た仕組みで考えることができます。JavaScriptで特に利用頻度の高い型は
Stringのようなものです。ただし、JavaScriptにおいて、char型は存在しないため、
JavaScriptならではのメソッドもありますが、ほとんど気にする必要はないメソッドばかりです。
慣れるまで少し戸惑うかもしれないのはequals()が無く、
また、Javaと異なり、
JavaScriptにおいて、
ただし、ビット演算するときには32ビット整数として扱われるので、ビット演算時にはちょっと注意が必要です。逆に、このことを利用して少数を整数化するテクニックもあります。
また、結局はdoubleですので、32ビット整数であるint範囲は表現できますが、64ビット整数であるlong範囲は単体の
もちろんこれは別にJavaScriptの限界を示すものではなく、必要に応じてjava.math.BigDecimalのようなライブラリを使うか作れば、任意精度演算できます。
Javaでいうと参照型と呼ばれてる、いわゆるオブジェクトの型です。JavaのObjectに相当するのはJavaScriptでも
しかし、JavaのObjectよりも重要な性質があります。
関数の型です。Javaにもjava.util.functionやラムダ式ができたので、大雑把なところでは理解が早いと思いますが、ああいうのの、もうちょっとネイティブなものだと思ってください。Javaのラムダ式はインターフェースありきでしたが、JavaScriptの場合はもっとフリーダムです。
関数という型があるということは、変数に関数型の値を代入できるということです。関数型の値はfunction宣言またはfunction式で生成できます。
また、
ラッパーオブジェクトは基本的にはユーティリティメソッドを提供してくれるものであり、
また、
JavaScriptではすべての型はbooleanとして評価できます。
論理演算子
このことを利用して、論理演算子によって、値が設定されていない場合のデフォルト値を設定することができます。
ただし、後者のような、if文の代わりに使うような使い方は一般的に嫌われるスタイルです。
前者のような値のみの組み合わせでも好まれない場合はあります。
すべてのオブジェクトがObjectというルートを持っているのは同じですが、JavaとJavaScriptでObjectと
順を追って見ていきましょう。
JavaのObjectインスタンスはあまり直接的に使用しませんが、JavaScriptの
オブジェクトリテラルのキーは、自動的に文字列として扱われます(上記の
また、JavaScriptのオブジェクトは
文法上の制約により、キーが空白や記号の場合、
これも一般的にはキーが変数であったり
また、一方でJavaと同じように、
しかし、通常はこのようなことはしませんし、しないように注意すべきです。
従って、次のような挙動を示します。
仕組みが分かっていれば単純なルールですが、必ずしも直感的ではないので落とし穴になりやすい部分です。
Arrayは自動伸長できる配列です。あえてJavaらしく言うとjava.util.ArrayListみたいなものですが、JavaScriptにおいては普通の配列です。
しかしJavaScriptを正しく理解するためには、Arrayが我々の知っている配列であるという意識はいったん捨ててください。
Arrayは配列のように使える
Map<String, Object>であり、
ArrayがArrayとしての機能を果たすのは、Array.prototypeが揃える各関数が使えるかどうか以外には、
見た目上はlengthプロパティだけです。Arrayのlengthは、最も大きい添え字+1になる性質を持っています。
これだけが配列の機能です。もちろん、そのように使えるということと、そうであるということは違います。何らかのオブジェクトをArrayの代わりに使うことは避けなければなりません。
Arrayが必要なときはArrayを使い、Arrayの添え字には(非負の)整数のみを使用するべきです。これは単純にソースコードの意味の問題だけでなく、処理系への影響が大きくなります。
圧倒的に
JavaScriptはクラスベースではなくプロトタイプベースの言語であるというようなことは聞いたことがあるかと思います。JavaScriptのオブジェクトにはプロトタイプチェーンと呼ばれる仕組みがあります。プロトタイプというのは、オブジェクトにカスケードされたオブジェクトです。
と言ってもなかなか想像が付きにくいので、まず、Javaで複数のMapからキーを探すことを考えてみましょう。
複数のMapを束ねて、キーを探索するときに、一つ目のMapになければ、二つ目のMapを参照し、二つ目のMapになければ三つ目というように、見つかるか最後のMapにたどり着くまで探すようにします。
また、このMap群にキーをセットするときは、一つ目のMapにputするものとします。
こうすると、先頭のMapは、すべての値を持っているように見えますし、値を変更することもできます。しかし、裏側のMapの値は実際には変化しません。
JavaScriptのオブジェクトは自動的にこれをやってくれる仕組みを持っていて、そのオブジェクトの連なりをプロトタイプチェーンと呼び、一つ目のMapすなわち対象のオブジェクトから見た二つ目のMapをプロトタイプと呼びます。
実際にも、node.jsやChromeのJavaScriptエンジンの場合、
あるキーで問い合わせがあったとき、そのキーが自分自身になければ、
値.や[]で代入するコンテキストの場合はそれと異なり、そのオブジェクトのみにput()されます。
これはJavaな人からすると、newという概念を分解して考えた方が分かりやすいです。Javaのnewは指定されたクラスのメモリを確保して、オブジェクトを割り当て、コンストラクタを呼び出すという複合的な演算子です。
JavaScriptにおける
また、あるオブジェクトZがXのインスタンスであるというのは、
ちなみに、
さて、既存のDateや適当なX()でごまかしましたが、JavaScriptにおけるクラスのように見えるもの、
したがって、コンストラクタとして扱うつもりの関数の
なお、これはたまたまメソッドのように見えているのではなく、JavaScriptにおけるメソッドとはこれです。コンストラクタのエッセンスを取り出したわけでもなく、JavaScriptにおけるコンストラクタとは単にそのようにふるまう関数です。
ただし、通常はやはりコーディング規約により、コンストラクタ関数は、大文字で始める(Javaにおけるクラス命名規則と同じ)などが規定されています。
JavaScriptにおけるメソッドは、プロパティとしての関数ですが、ただの関数と異なる点があります。それが
もちろん、通常の関数で0番目の引数というものは指定できません。関数として使った場合には、
しかし、メソッドとしてオブジェクトとともに使うと
このときの
Pythonの
おそらくJava風にthisを考えると何が起こっているか分からないと思いますが、JavaScriptの仕組みは非常に単純です。
ということになります。それ以外の特別な機能は
もちろんそうすると、メソッドをonClickなどのイベントハンドラに設定するのが難しくなります。そこで、Function.prototype.apply()やFunction.prototype.call()、
Function.prototype.bindによって、
非同期でイベントドリブンなんだと言われても、想像がつきにくいのがJavaScriptの実行モデルです。setTimeoutで呼び出される関数はスレッドセーフでなくていいのか?いや、考えてみるとonClickで呼び出される関数はどうなんだ?いや、ロックとかあったっけ?処理の途中で割り込まれたら値はどうなるんだ?などと不安になったりします。シグナルハンドラや割込みプログラミング、マルチスレッドの経験があったりすると余計に混乱することだと思います。
JavaScriptにおいては、プログラマから見たときのスレッドは基本的に1つしか存在しません。またコードがスレッドセーフになっているのか心配する必要もありません。割込みやイベントはすべてキューイングされて、順序良く処理されます。
しかし、イベント駆動モデルを意識しないとまるでマルチスレッドで動いているように見えるかもしれません。また、ブラウザ環境では画面描画イベントも同じスレッドで実行されるため、スレッドが1つしかないことを忘れると、画面描画を止めてしまいがちです。
非同期あるいはJavaScriptというのはおそらく最初に想像するよりも非常に単純な仕組みです。たった1つのスレッドはmainスレッドではなくExecutors.newSingleThreadExecutor()だと考えられます。
全てのJavaScriptプログラムは、このExecutorServiceでのみ実行されます。クリックのような画面操作イベントもタスクとして登録され、先行するタスクが終わるまでキューイングされます。
実際に、JavaScriptの実装はおおまかにはだいたいこのようになっていて、背後では複数のスレッドが動いていますが、JavaScriptのコードを呼び出すのは常に同じたった一つのスレッドです。このスレッドの仕組みはExecutorServiceの中身がそうであるように、イベントループと呼ばれます。GUIプログラミングで使われているようなものとまったく同じです。
イベントループによる実行は、コードがスレッドセーフである必要はありませんが、一つの処理が終わらない限り、どのようなイベントが発生しても処理できなくなります。したがってJavaScriptが無限ループに入るとキューが消化されず、画面描画も滞ってフリーズしてしまうというわけです。
なお、現代的なJavaScriptでは、Web Workerという仕組みにより、このイベントループを複数作り、マルチスレッドで動かすこともできますが、依然としてスレッドセーフを意識する必要はありません。(逆に言えば、そういう密接な連携ができません)
説明上飛ばしたりしたその他の解説です。
文末の;は省略することができます。まったく書くべきでないという派閥もありますが、何が起こるか理解するまでは、しっかり書いておいた方がいいと思います。
なお、この「省略できる」という機能は、正確には;を挿入する機能なので、例えば次のような悲劇を起こします。
こういう事態を防ぐためにも、なるべく静的解析のあるエディタやlintを導入するとよいでしょう。
varによる変数宣言はJavaと多少似ているようでまったく違うネームスコープを持っています。varにより宣言した変数は関数の中括弧
今のJavaScriptには、Javaや一般的なC言語スタイルの言語と同じように
この記事が全面的に
varはこのような奇妙なスコープを持っているので、古くはネームスコープを利用するために即時関数が利用されてきました。
このようなコードは特に古いコードではしょっちゅう見かけるかと思いますが、これはそのためのイディオムです。
現代的なJavaScriptでは、すでにclass構文が導入されています。
そんなものがあるなら最初に紹介しろと思われるかもしれません。
詳細はMDNのクラスを読んでいただくのが一番早いのですが、これは別に新しい仕組みを導入したわけではなく、今までの機能をきちんと書けるようにしただけです。
Javaプログラマにとって、これはおそらく危険な構文で、Javaっぽく書けるせいで余計に理解から遠のくのではないかと思います。しかし、今から書く場合には、まずclass構文で書くようにしましょう。
これは単なる落とし穴ですが、[]内がtoString()されたとしても[]で普通の配列と同じように使えるので問題ないだろ、と思っていると、次のような恐ろしい挙動に遭遇したりします。
配列であっても[]によるアクセスはオブジェクトと同じであり、[]の中身はtoStringされてキーになるというルールと、数値はすべてnumberであり、intのつもりでもnumberということを思い出せばこのコードに何が起きたかはわかっていただけると思います。
JavaScriptでは簡単には正しく解釈できないにも関わらず、JSONに64ビット整数を含めることは可能です。Jacksonなどでも遠慮なく突っ込めます。RFC8259では、数値幅の規定はありませんが、整数なら[-(2*53)+1, (2*53)-1]の幅で使うといいっすよと書かれています。
JavaScriptのスタイルガイドまとめ(おすすめ4選)
最低限のJavaScript開発環境についても触れておきます。
最近のJavaScriptの規格としては、ECMAScript3、ECMAScript5.1、ECMAScript 2015(旧名ECMAScript 6)、2016、2017、2018と結構な多様性があるように見えます。実際にはブラウザごとに最新規格のどこから実装するかとInternet Explorerぐらいの違いしかないので、IEの対応を考えなければ、ECMAScript 2015ぐらいはだいたい使えるため、ECMAScript 2015が現代の最低ラインでしょうか。
とはいえIE対応とは言え、今さら2015より前に戻りたくもなければ、新しい規格はどんどん便利になっているので、悩むよりもbabelなどのトランスパイラに任せてbabelがサポートしている規格で書くということも多いと思います。
コンパイラのない(ことの多い)JavaScriptにとって静的解析ツールは非常に重要です。そんなわけでJavaにおけるSpotBugs(FindBugs)以上にデファクトスタンダードなツールがeslintです。今から環境を整えるのならeslint以外は考えられないのですが、とりあえずJava屋が使ってみるというところではもはや過去とされるjshintでも許されるのではないでしょうか。jshintであればeclipseでもNetBeansでもプラグインだけで入り、IntelliJシリーズには最初から同封されていて、node.js環境がなくても動くのでサクッと導入するのに向いているかと思います。
node.js環境がなくてもとは言いましたが、多少なりともちゃんと開発しようと思う場合には、今どきnode.jsやnpm無しの開発というのは考えられない状況です。JDKなしでJavaを書くぐらいのものです。
これも最近はJavaScriptで書くのにbabelのない開発も少ないぐらいだと思います。トランスパイラと呼ばれていますが、古語で言うところのトランスレータです。新しい規格で書かれたJavaScriptを古い規格で動くようなコードに変換できます。これでIE対応も(まあまあ)進みます。
開発時はChromeなどの新しいブラウザでネイティブで行い、最終段階で古い規格に合わせるか、すべてbabelに合わせたコードを書いて、実行時には常にトランスパイルするか、様々な手法があるのではないでしょうか。たぶん。
通常は、.jsファイルをそのまま埋め込んだりせずに、一つのファイルにまとめてコメント除去やコード圧縮を図ります。個人的には、パフォーマンス性の問題よりも、minifyやbabelの過程を踏むことでコメントを除去したり、変数名を変えられるのが大きいと思います。特に官公庁のサイトのソースとか。かといって製品コードにコメントが入れると恥ずかしいから入れないというのも馬鹿らしいですし…
一般的なJavaプログラマであれば、JMLとまでは言わなくても、型を付けて静的検査ぐらいはしたくなると思います。大雑把に言って、JavaScriptに型を付けた言語がTypeScriptやFacebook/Flowです。
個人的にはJava屋の場合は、JSDocで型を書いて、WebStormにチェックしてもらうぐらいが一番だと思います。Google Closure Linterという(残念ながら)マイナーなトランスパイラテクノロジーもあるのですが、そろそろ滅んでしまいそうです。
それ以外のAltJS言語(死語)については、Java屋がやる仕事なら避けたほうがよいのではないでしょうか。ClojureScriptとかScala.jsとかロマンはありますね。大規模環境だとGWTもひょっとするといいかも知れません。いずれにせよ、JavaScriptが分かってないのに突っ込むのはお勧めしません。
eclipseのJavaScript対応はJavaの対応レベルに比べるとかなり微妙です。VSCodeか、有料ですがWebStormがお勧めです。
JavaScriptは非常にシンプルな言語であり、したがって難しい言語であり、楽しい言語です。ぜひ習得して、JavaにScriptが付いたようなものだと嘯いてください。
(ちなみにJava Advent Calendar 2018 11日目としては「真面目にJavaを書く話」的なものを予定していたんですが、遅延しまくった上にポエミィになったので差し替えました。)
はじめに
「JavaができればJavaScriptできるよね?」とか言う人に実際に会ったことはないのですが、人には言ってたりします。もちろん実のところはそう簡単にはいきません。
いきませんが、2018年も年末になってすら、ふと迷い込むとJavaScriptはJSPのおまけぐらいに思われていたりするわけです。さすがにJavaアプレットと混同している人はそんなに…いや、いるっぽいですね。私のパーソナライズの結果だといいんですが。
甚だしくは新人でない人がそのようなことをもっともらしく言ってみたり、90年代の知識で(今は2010年代の後半なんですよ?驚きですよね…)、JavaScriptをちょっとしたhtmlの<marquee>アクセント</marquee>程度にしか考えてなかったりするわけです。
JavaScriptがJavaプログラマにとって絶妙な加減で難しい位置にいるのも確かです。JavaScriptは頭にJavaと付いているだけあって、一応にもJavaの親の友達の子供ぐらいの関係にはあります。要するに他人なんですが、他人でありながらもJavaの幼馴染のようなもので、Javaプログラマならなんとなくで結構書けてしまううぐらいには似ています。しかし本質的には赤の他人なので、そういう立場で接していると地雷を踏んでしまい、よくわからないので深入りはしないようにしよう、となってしまうわけです。
この記事ではなんとなくJavaScriptできるんだけど、なんとなくでしかないJavaプログラマに対して、JavaScriptを学ぶ際に知っておけるとよいことを並べています。これだけでJavaScriptができるようになるとは思いませんが、その助けになれば幸いです。
なお、この記事の原本はけっこう昔に書いたので、多少古く、Symbolやasync/awaitなどの最近のJavaScriptらしいものは端折っています。来年だともうそこに触れないわけにはいかないので、これがJavaの知識ですんなりJavaScriptに入れる最後のチャンスです、ということにしておいてください。個人的には、JavaScriptのコア部分さえ分かれば最新のJavaScriptの理解は容易だと思います。
JavaScriptとか本当に何も知らないんだけど、という人はごめんなさい。とりあえず文法ぐらいはまあわかるよ、という人向けです。技術的におかしいものがあれば遠慮なく突っ込んでください。あと、本当かよと思ったらnode.js環境とか用意して実際にやってみてください。Javaに慣れると(Javaにも今やJShellがありますが)REPL環境のことを忘れがちですよね。
型
まずは型の話からスタートしましょう。JavaScriptは動的型付けです。動的型付けというのは、型がないのではなく型をあらかじめ固定しておかないことですので、型自体はあります。主要な型はだいたいtypeof
演算子で確認することができます。typeof
var x = 1; console.log(typeof x); // number x = "str"; console.log(typeof x); // string
string
、number
、boolean
、object
、function
の5つでしょうか。Javaと違って、intやdoubleなどの数値型はnumber
ただ1つになることと、string
が基本型であること、function
型が存在していることが大きな違いです。また、JavaScriptにはそれ以外の型もいくつかあります。それぞれ順番に見てみましょう。
string
Stringのようなものです。ただし、JavaScriptにおいて、char型は存在しないため、''
と""
に区別は一切ありません。シェルスクリプトのように変数展開もしません。(新しいJavaScriptでは``を使うと${}
で変数展開できます。)一般的なコーディングスタイルでは''
と""
はソースコード中でどちらか片方だけを使うことが推奨されています。string
に対して使用できるメソッドもStringとよく似ているというか、ほぼそのままです。ただし、少し前のStringなので、現代を生きるJavaプログラマからすると、Stringのサブセットになるので注意が必要です。equalsIgnoreCase()やisEmpty()のような便利なメソッドはありません。しかし、startsWith()
のように後から入ったものもあります。JavaScriptならではのメソッドもありますが、ほとんど気にする必要はないメソッドばかりです。
慣れるまで少し戸惑うかもしれないのはequals()が無く、
===
で比較できることでしょう。==
でもできますが、現代的JavaScriptでは==
は使いません。また、Javaと異なり、
charAt()
だけでなく配列と同様に[]
で一文字ずつ取得可能です。string
console.log('abc' === "abc"); // true console.log('abc'[1]); // 'b'
number
JavaScriptにおいて、number
はJavaのdoubleに相当する唯一の数値型です。64ビットの浮動小数点型で、doubleがそうであるように、整数ももちろん扱うことができます。逆に、整数用の型というものはなく、すべてnumber
です。ただし、ビット演算するときには32ビット整数として扱われるので、ビット演算時にはちょっと注意が必要です。逆に、このことを利用して少数を整数化するテクニックもあります。
number
var x = 0.1; console.log(x); // 0.1 x = (0.1 | 0); console.log(x); // 0
number
で扱うことはできず、丸めた値として扱われてしまいます。このため、たとえばIDが64ビット数値になってしまったTwitter APIなどでは、JavaScriptのために文字列版のIDが用意されていたりします。大きすぎて浮動小数点の丸めが入る
console.log(12341234123412341234); // 12341234123412340000
boolean
boolean
はJavaのbooleanとだいたい100%ぐらい同じです。equals()がないぐらい。
object
Javaでいうと参照型と呼ばれてる、いわゆるオブジェクトの型です。JavaのObjectに相当するのはJavaScriptでもObject
であり、JavaのようにすべてのオブジェクトはObject
を継承しています。しかし、JavaのObjectよりも重要な性質があります。
Object
については後述します。
function
関数の型です。Javaにもjava.util.functionやラムダ式ができたので、大雑把なところでは理解が早いと思いますが、ああいうのの、もうちょっとネイティブなものだと思ってください。Javaのラムダ式はインターフェースありきでしたが、JavaScriptの場合はもっとフリーダムです。関数という型があるということは、変数に関数型の値を代入できるということです。関数型の値はfunction宣言またはfunction式で生成できます。
functionの値
var f = function(x) { console.log('f:' + x); } f('abc'); // f:abc f = function(x) { console.log('f2:' + x); } f('xyz'); // f2:xyz
function
は実際にはオブジェクトでもあります。これは後述します。
オートボクシングと型変換
number
やstring
、boolean
にはラッパーオブジェクトNumber
やString
、Boolean
があります。JavaScriptではオートボクシングとは言いませんが、必要に応じてオートボクシングと同じことをしてくれます。たとえば、文字列定数"abc"
はstring型でStringのインスタンスではないのですが、"abc".substring(2)
などとしてメソッドを使うことができます。ラッパーオブジェクトは基本的にはユーティリティメソッドを提供してくれるものであり、
new String()
やnew Number()
して使うことはありません。が、使えてしまうのでJava以上に警戒が必要です。(※Javaでも別な視点から、new String()とか、new Integer()は使わないですよね。)String
はstring
ではないので、同じようには使用できず、あえて使う機会もありません。ラッパーオブジェクト
var str = 'abc'; var STR = new String(str); console.log(str === 'abc'); // true console.log(STR === 'abc'); // false console.log(typeof str); // 'string' console.log(typeof STR); // 'object' console.log(str instanceof String); // false; console.log(STR instanceof String); // true;
function
はそのままでFunction型ですので、この分類には入りません。また、
String()
やNumber()
、Boolean()
は型変換にも使えます。が、どの方法も別な変換イディオムがあるため、直接的にはあまり使われません。型変換
var str = '100'; console.log(str + 200); // 100200 console.log(Number(str) + 200); // 300 console.log(String(1+1) + 0); // 20 console.log(Boolean(0)); // false
正規表現リテラル
typeof
ではobject
に分類されますが、正規表現リテラル(定数)があります。JavaのPattern.compile()に相当するのはnew RegExp()
ですが、固定の正規表現であれば、正規表現リテラルを利用できます。RegExp
var regexp = /.+/; // regexp = new RegExp('.+'); と同等 console.log(regexp instanceof RegExp); // true
booleanへの型変換
JavaScriptではすべての型はbooleanとして評価できます。0
や空文字''
、null
、undefined
がfalse
として扱われます。boolean変換
if (0) { // 実行されない }
論理演算子による記法
論理演算子 ||
、&&
は値に対して型変換を行ってbooleanで評価を行い、型変換を行う前の値を結果として返します。このことを利用して、論理演算子によって、値が設定されていない場合のデフォルト値を設定することができます。
論理演算子による計算
var a; ... var b = a || 'test'; console.log(b); // aが0、''、null、undefinedでなければその中身、そうでなければ'test' a && console.log(a); // ショートサーキット(短絡評価)も働きます。aがfalsyでなければ中身を表示します。
前者のような値のみの組み合わせでも好まれない場合はあります。
Object
すべてのオブジェクトがObjectというルートを持っているのは同じですが、JavaとJavaScriptでObjectとObject
の利用感はまったく違います。JavaScriptのオブジェクトはすべて単なる連想配列であり、機能としてはJavaで言うところのMap<String, Object>です。これがJavaScriptの言語として重要な要素となっています。順を追って見ていきましょう。
基本的性質
JavaのObjectインスタンスはあまり直接的に使用しませんが、JavaScriptのObject
は連想配列やPOJO、場合によっては関数を持たせてクラスオブジェクトのように使うなど、さまざまな用途で利用するため、使用頻度の高いオブジェクトです。Object
のインスタンスはnew Object()
で生成することもできますし、単に{}
で生成することもできます。一般的にはコーディングルールにより{}
で生成することが推奨されていることが多いです。このときに、JSON風に初期値を与えることもできます。むしろJSONがここから生まれたのだから、JSON風っておかしくないかと思うかもしれませんが、JSONのように仕様に縛られておらず、JSONよりも柔軟です。Objectの使用例
var x = {}; // x = new Object(); x = { 'a b c': 'xy' + 'z', 'test': 123, example: /.+/ }; console.log(x['test']); // 123 console.log(x['a b c']); // xyz console.log(x['example']); // /.+/
example
がそうです)。キーは必ず文字列ですので、''は不要なケースが多いのですが、通常はコーディングルールによって使い分けがなされるか、どちらかに偏らせます。また、JavaScriptのオブジェクトは
[]
または.
を使うことでその連想配列からget/putができます。文法上の制約により、キーが空白や記号の場合、
.
でアクセスすることはできませんが、値の出し入れでは両者は同じように扱うことができます。Objectの使用例もう少し
var x = {}; // new Object(); console.log(x.test); // undefined x.test = 123; // オブジェクトにキーがない場合でも代入すると生成されます x['abc'] = 'xyz'; // 同じく生成されます console.log(x.test); // 123 console.log(x['test']); // 123 console.log(x.abc); // xyz
.
では使えない文字列である場合のみ[]
を使い、定数キーは.
を使うなど、使い分けを行うことでコード上の意味を統一させることが多いです。また、一方でJavaと同じように、
Object
はすべてのobject
型の祖です。したがって、上記の特性はすべてのオブジェクトで通用する事柄ですので注意が必要なシーンがあります。たとえば、Dateインスタンスのように直接Object
ではないものもこの性質をもっています。JavaScriptにおいてはすべてはオブジェクトですので、Dateそのものにキーを追加することもできます。何でも代入
var x = new Date(); x.test = 'abc'; // 問題なし Date.xyz = 123; // 問題なし console.log(x.test); // abc console.log(Date.xyz); // 123
[]演算子
.
と同じと言いましたが、もちろん文法上の違いにより、[]
の中には文字列のみならず、どのような型であれ含めることが可能です。ただし、Object
はあくまでMap<String, Object>ですので、[]
の中の値はString()
されて文字列として評価されます。(String()
すると、null
は'null'
になり、undefined
は'undefined'
になり、それ以外の場合はtoString()
かvalueOf
が呼び出されます)従って、次のような挙動を示します。
[]の挙動チェック
var x = {'1': 9, 'a': 3, 'y': 2, 'test': 4, 'null': 8 }; console.log(x[1]); // 9 console.log(x['1']); // 9 var y = { toString: function() { return 'test'; } } console.log(x[y]); // 4 console.log(x[null]); // 8
[]の間違いやすい挙動
var x = {}; var y = { a: 3 } x[y] = 5; console.log(x[y]); // 5 console.log(x[{a: 3}]); // 5 // まるで、オブジェクトをキーにできているように見えますが、、、 console.log(x[{a: 1000}]); // 5 console.log(x['[object Object]']); // 5 console.log(y.toString()); // '[object Object]' console.log(x); // { '[object Object]': 5 } // 単にString()されてるだけ
Array
Arrayは自動伸長できる配列です。あえてJavaらしく言うとjava.util.ArrayListみたいなものですが、JavaScriptにおいては普通の配列です。object
がnew Object()
で生成できるように、new Array()
で生成できますが、一般的には単なる[]
が好まれます。Array
var x = []; // x = new Array(); var y = ['a', 'b', 'c']; console.log(y[1]); // b
Arrayは配列のように使える
object
であり、[]
の効果も同じです。Objectの時点でMap<String, Object>であることはすでに見ました。Map<String, Object>であり、
[]
に含めた数値がtoString()されて検索されるということは、そもそもobject
であれば、メモリが許す限りいくらでもオブジェクトを詰め込めることになります。object
の時点で、自動伸長できる配列のように取り扱うことができるというわけです。ObjectとArray(違いはない)
// Array var x = []; x[0] = 1; x[1] = 2; console.log(x[1]); // 2 console.log(x['1']); // 2 // Object var y = {}; y[0] = 1; y[1] = 2; console.log(y[1]); // 2 console.log(y['1']); // 2 // Date(Objectを継承している適当なオブジェクト例) var z = new Date(); z[0] = 1; z[1] = 2; console.log(z[1]); // 2 console.log(z['1']); // 2
見た目上はlengthプロパティだけです。Arrayのlengthは、最も大きい添え字+1になる性質を持っています。
Arrayとlength
var x = []; x[1000] = 0; console.log(x.length); // 1001 var y = {}; y[1000] = 0; console.log(y.length); // undefined
Arrayが必要なときはArrayを使い、Arrayの添え字には(非負の)整数のみを使用するべきです。これは単純にソースコードの意味の問題だけでなく、処理系への影響が大きくなります。
時間計測
# Object time node -e 'let x = {}; for (let k = 0; k < 1000; k++) for (let i = 0; i < 1000000; i++) x[i] = i;' # Array time node -e 'let x = []; for (let k = 0; k < 1000; k++) for (let i = 0; i < 1000000; i++) x[i] = i;'
x = []
のときの方が速いはずです。
Objectとプロトタイプとプロトタイプチェーン
JavaScriptはクラスベースではなくプロトタイプベースの言語であるというようなことは聞いたことがあるかと思います。JavaScriptのオブジェクトにはプロトタイプチェーンと呼ばれる仕組みがあります。プロトタイプというのは、オブジェクトにカスケードされたオブジェクトです。と言ってもなかなか想像が付きにくいので、まず、Javaで複数のMapからキーを探すことを考えてみましょう。
複数のMapを束ねて、キーを探索するときに、一つ目のMapになければ、二つ目のMapを参照し、二つ目のMapになければ三つ目というように、見つかるか最後のMapにたどり着くまで探すようにします。
また、このMap群にキーをセットするときは、一つ目のMapにputするものとします。
こうすると、先頭のMapは、すべての値を持っているように見えますし、値を変更することもできます。しかし、裏側のMapの値は実際には変化しません。
Javaによる擬似コード
public class CascadedMap extends HashMap<String, Object> { private CascadedMap parent; @Override public Object get(String key) { if (containsKey(key)) { return super.get(key); } if (parent == null) { return NULL; } return parent.get(key); } @Override public Object put(String key, Object v) { super.put(key, v); } }
実際にも、node.jsやChromeのJavaScriptエンジンの場合、
__proto__
という特殊な変数(キーと値)があり、これがプロトタイプを指しています。あるキーで問い合わせがあったとき、そのキーが自分自身になければ、
__proto__
に問い合わせを行います。__proto__
のObjectでも同じように自分自身にあれば返し、無ければ__proto__
を辿るということを繰り返します。値.や[]で代入するコンテキストの場合はそれと異なり、そのオブジェクトのみにput()されます。
Javaによる擬似コード
public class JSObject { private HashMap<String, Object> myValue; private JSObject __proto__; public Object get(String key) { if (myValue.containsKey(key)) { return myValue.get(key); } if (__proto__ == null) { return JS_UNDEFINED; } return __proto__.get(key); } public Object put(String key, Object v) { myValue.put(key, v); } }
プロトタイプの設定とインスタンス
__proto__
という名前から感じられるとおり、__proto__
は直接触れることのない変数ですし、触ることのできない処理系もあります。では通常はプロトタイプをどうやって設定するかというと、prototype
というキーとnew
演算子を使います。newとprototype
Date.prototype.test = 'abc'; var x = new Date(); console.log(x.__proto__ === Date.prototype); // true console.log(x.test); // 'abc'
JavaScriptにおける
new X()
あるいはnew演算子とは、空のObjectを生成し、その__proto__
にX.prototype
を代入したあと、X()という関数をコンストラクタとして呼び出す役目を持った演算子です。また、あるオブジェクトZがXのインスタンスであるというのは、
Z.__proto__ === X.prototype
(または再帰的にZ.__proto__.__proto__ === X.prototype
・Z.__proto__.__proto__.__proto__ === X.prototype
…)を満たしている場合であり、instanceof
演算子がそれをやってくれていると考えることができます。ちなみに、
__proto__
へX.prototype
を代入するのは本当に単なる代入なので、次のようなことにもなります。newとprototype(順番入れ替えたもの)
var x = new Date(); // xの__proto__はDate.prototype Date.prototype.test = 'abc'; console.log(x.__proto__ === Date.prototype); // true console.log(x.test); // 'abc'
コンストラクタ
さて、既存のDateや適当なX()でごまかしましたが、JavaScriptにおけるクラスのように見えるもの、new
の後ろの名前は何でしょうか?困ったらtypeof
console.log(typeof Date); // function
function
です。Javaにおけるクラスという概念はいったん忘れてください。JavaScriptにはJavaにおけるクラスはありません。クラスのようなものを実現するために、new
演算子とプロトタイプチェーンがあると思ってもらった方がわかりやすいと思います。(これは逆から見ると、プロトタイプのようなものを実現するために余計なクラス機構を取り入れてるJavaという言語、と考えることもできます)new
演算子の機能はもう少し正確には、「指定されたコンストラクタ関数のprototype
プロパティをプロトタイプに持つオブジェクトを生成し、そのオブジェクトをthisとした上で、コンストラクタ関数を実行する」ということになります。何でもない関数をnewしてみる
function hoge(v) { console.log(v); } var x = new hoge('abc'); // 'abc' console.log(x instanceof hoge); // true
prototype
プロパティに値を入れておくと、そのインスタンスで取り出すことができます(プロトタイプチェーンで説明したように、書き込みはそのインスタンスになります)。プロパティに関数を入れておくと、メソッドとして使うことができます。prototypeを設定してみる
function hoge() { } hoge.prototype.value = 1023; hoge.prototype.test = function(v) { console.log(v); } var x = new hoge(); x.test('abc'); // 'abc' console.log(x.value); // 1023
ただし、通常はやはりコーディング規約により、コンストラクタ関数は、大文字で始める(Javaにおけるクラス命名規則と同じ)などが規定されています。
thisとメソッド
JavaScriptにおけるメソッドは、プロパティとしての関数ですが、ただの関数と異なる点があります。それがthis
です。Javaとは異なり、JavaScriptのthis
はどこにでも存在するのが話をややこしくしていますが、this
はすべての関数の0番目の引数だと考えるのが一番簡単な理解です。もちろん、通常の関数で0番目の引数というものは指定できません。関数として使った場合には、
this
はglobal
という環境を表すオブジェクトになっています。thisは何?
function hoge() { console.log(this === global); } hoge(); // true
this
が設定されるようになります。何なの?
function hoge() { console.log(this === global); } var v = {}; v.method = hoge; hoge(); // true v.method(); // false; v['method'](); // false;
this
は何かというと、もちろんオブジェクトが入っています。呼び出し方次第
function hoge() { console.log(this === v); } var v = {}; v.method = hoge; hoge(); // false v.method(); // true; v['method'](); // true;
self
が省略されているようなものと考えるのがいいかもしれませんね。少し戸惑うであろう点は、上記のhoge
はv
の専属メソッドではなく、かつメソッドは単なる関数のプロパティでしかないということです。呼び方いろいろ
function hoge() { console.log(this === v); } var v = {}; v.method = hoge; hoge(); // false v.method(); // true; v['method'](); // true; var x = {}; x.method = v.method; // 呼び出しではなく値(関数)の取り出し x.method(); // false;
this
は明示的には書かれない第0引数で、.
や[]
を使って関数を取り出し、即座に実行したときには、その取り出し元のオブジェクトが設定されるということになります。それ以外の特別な機能は
this
にはありません。第0引数の指定を省略するとglobal
が設定されるというわけです。もちろんそうすると、メソッドをonClickなどのイベントハンドラに設定するのが難しくなります。そこで、Function.prototype.apply()やFunction.prototype.call()、
Function.prototype.bindによって、
this
を設定した呼び出しや、this
を設定した呼び出しを行う関数を作っておくことができるようになっています。thisを明示的に設定
function hoge() { console.log(this === v); } var v = {}; hoge.call(v); // true var b = hoge.bind(v); b(); // true
JavaScriptのスレッドモデル
非同期でイベントドリブンなんだと言われても、想像がつきにくいのがJavaScriptの実行モデルです。setTimeoutで呼び出される関数はスレッドセーフでなくていいのか?いや、考えてみるとonClickで呼び出される関数はどうなんだ?いや、ロックとかあったっけ?処理の途中で割り込まれたら値はどうなるんだ?などと不安になったりします。シグナルハンドラや割込みプログラミング、マルチスレッドの経験があったりすると余計に混乱することだと思います。JavaScriptにおいては、プログラマから見たときのスレッドは基本的に1つしか存在しません。またコードがスレッドセーフになっているのか心配する必要もありません。割込みやイベントはすべてキューイングされて、順序良く処理されます。
しかし、イベント駆動モデルを意識しないとまるでマルチスレッドで動いているように見えるかもしれません。また、ブラウザ環境では画面描画イベントも同じスレッドで実行されるため、スレッドが1つしかないことを忘れると、画面描画を止めてしまいがちです。
非同期あるいはJavaScriptというのはおそらく最初に想像するよりも非常に単純な仕組みです。たった1つのスレッドはmainスレッドではなくExecutors.newSingleThreadExecutor()だと考えられます。
全てのJavaScriptプログラムは、このExecutorServiceでのみ実行されます。クリックのような画面操作イベントもタスクとして登録され、先行するタスクが終わるまでキューイングされます。
疑似コード
ExecutorService service = Executors.newSingleThreadExecutor(); public void setTimeout(JSFunction f, long millisec) { new Thread(() -> { TimeUnit.MILLISECONDS.sleep(millisec); service.submit(f); }).start(); } // 画面操作も平等にsubmitされる public void click(JSFunction onClick) { service.submit(onClick); } public void main(String maincode) { service.submit(() -> execute(maincode)); }
setTimeout
を呼び出しても、すぐに制御が戻り、時間が来ると関数fがsubmitされて実行されることがわかるかと思います。同時に、先行してsubmitされたタスクが無限ループなどで処理が終わらないと、後続の処理が並列実行されることはなく、永久に実行されないこともわかるかと思います。実際に、JavaScriptの実装はおおまかにはだいたいこのようになっていて、背後では複数のスレッドが動いていますが、JavaScriptのコードを呼び出すのは常に同じたった一つのスレッドです。このスレッドの仕組みはExecutorServiceの中身がそうであるように、イベントループと呼ばれます。GUIプログラミングで使われているようなものとまったく同じです。
イベントループによる実行は、コードがスレッドセーフである必要はありませんが、一つの処理が終わらない限り、どのようなイベントが発生しても処理できなくなります。したがってJavaScriptが無限ループに入るとキューが消化されず、画面描画も滞ってフリーズしてしまうというわけです。
なお、現代的なJavaScriptでは、Web Workerという仕組みにより、このイベントループを複数作り、マルチスレッドで動かすこともできますが、依然としてスレッドセーフを意識する必要はありません。(逆に言えば、そういう密接な連携ができません)
戸惑いがちな、または現代的な構文など
説明上飛ばしたりしたその他の解説です。
===と!==
==
を使わない話はしましたが、==
は型変換付の比較になります。たとえば'1' == 1
ですが、'1' !== 1
です。==
を使いこなして安全なコードを書くのは楽ではなく意味もないので、現代的なJavaScriptでは一般的に===
と!==
の使用が推奨されています。===と==
console.log('1' == 1); // true console.log('1' === 1); // false
文末の;省略
文末の;は省略することができます。まったく書くべきでないという派閥もありますが、何が起こるか理解するまでは、しっかり書いておいた方がいいと思います。なお、この「省略できる」という機能は、正確には;を挿入する機能なので、例えば次のような悲劇を起こします。
;の自動挿入
function x() { return 100; } console.log(x()); // undefined
変数宣言
varによる変数宣言はJavaと多少似ているようでまったく違うネームスコープを持っています。varにより宣言した変数は関数の中括弧{}
内で有効です。たとえば、次のような挙動になります。varの範囲
for (var i = 0; i < 100; i++) {} console.log(i); // 100 for (var i = 0; i < 200; i++) {} // 2回目のiの宣言です
{}
内でのみ使える変数を宣言するためのconst
やlet
がありますので、新しい環境であればそちらを使ってください。letの範囲
for (let i = 0; i < 100; i++) {} console.log(i); // undefined
var
なのは古いJavaScriptに慣れてしまった人に違和感を覚えていただかないようにであって、通常はもはやvar
を使うシーンはありません。IE対応などでどうしても直接的にvar
を使わなければならないのであれば、静的解析のあるエディタやlintを導入しましょう。
ネームスコープのための即時関数
varはこのような奇妙なスコープを持っているので、古くはネームスコープを利用するために即時関数が利用されてきました。即時関数
(function() { ... })() // 引数が詰まってることもあります
class構文
現代的なJavaScriptでは、すでにclass構文が導入されています。class
class Test { constructor(v) { this.value = v; } method1() { console.log(this); } } const t = new Test(3); t.method1(); // Test { value: 3 }
詳細はMDNのクラスを読んでいただくのが一番早いのですが、これは別に新しい仕組みを導入したわけではなく、今までの機能をきちんと書けるようにしただけです。
Javaプログラマにとって、これはおそらく危険な構文で、Javaっぽく書けるせいで余計に理解から遠のくのではないかと思います。しかし、今から書く場合には、まずclass構文で書くようにしましょう。
[]と数値インデックス
これは単なる落とし穴ですが、[]内がtoString()されたとしても[]で普通の配列と同じように使えるので問題ないだろ、と思っていると、次のような恐ろしい挙動に遭遇したりします。本当は怖い数値インデックス
var i; var a = [0, 1 ,2, 3, 4]; var b = []; for (i = 0; i < 10; i++) { b[i] = a[i / 2]; } console.log(b); // [ 0, undefined, 1, undefined, 2, undefined, 3, undefined, 4, undefined ]
a['0'], a['0.5'], a['1'], a['1.5'], a['2'], ...
となっているわけです。
JSON
JavaScriptでは簡単には正しく解釈できないにも関わらず、JSONに64ビット整数を含めることは可能です。Jacksonなどでも遠慮なく突っ込めます。RFC8259では、数値幅の規定はありませんが、整数なら[-(2*53)+1, (2*53)-1]の幅で使うといいっすよと書かれています。
コーディング規約
JavaScriptのスタイルガイドまとめ(おすすめ4選)
開発環境など
最低限のJavaScript開発環境についても触れておきます。
規格
最近のJavaScriptの規格としては、ECMAScript3、ECMAScript5.1、ECMAScript 2015(旧名ECMAScript 6)、2016、2017、2018と結構な多様性があるように見えます。実際にはブラウザごとに最新規格のどこから実装するかとInternet Explorerぐらいの違いしかないので、IEの対応を考えなければ、ECMAScript 2015ぐらいはだいたい使えるため、ECMAScript 2015が現代の最低ラインでしょうか。とはいえIE対応とは言え、今さら2015より前に戻りたくもなければ、新しい規格はどんどん便利になっているので、悩むよりもbabelなどのトランスパイラに任せてbabelがサポートしている規格で書くということも多いと思います。
eslint
コンパイラのない(ことの多い)JavaScriptにとって静的解析ツールは非常に重要です。そんなわけでJavaにおけるSpotBugs(FindBugs)以上にデファクトスタンダードなツールがeslintです。今から環境を整えるのならeslint以外は考えられないのですが、とりあえずJava屋が使ってみるというところではもはや過去とされるjshintでも許されるのではないでしょうか。jshintであればeclipseでもNetBeansでもプラグインだけで入り、IntelliJシリーズには最初から同封されていて、node.js環境がなくても動くのでサクッと導入するのに向いているかと思います。
node.jsとnpm
node.js環境がなくてもとは言いましたが、多少なりともちゃんと開発しようと思う場合には、今どきnode.jsやnpm無しの開発というのは考えられない状況です。JDKなしでJavaを書くぐらいのものです。
babel
これも最近はJavaScriptで書くのにbabelのない開発も少ないぐらいだと思います。トランスパイラと呼ばれていますが、古語で言うところのトランスレータです。新しい規格で書かれたJavaScriptを古い規格で動くようなコードに変換できます。これでIE対応も(まあまあ)進みます。開発時はChromeなどの新しいブラウザでネイティブで行い、最終段階で古い規格に合わせるか、すべてbabelに合わせたコードを書いて、実行時には常にトランスパイルするか、様々な手法があるのではないでしょうか。たぶん。
minify
通常は、.jsファイルをそのまま埋め込んだりせずに、一つのファイルにまとめてコメント除去やコード圧縮を図ります。個人的には、パフォーマンス性の問題よりも、minifyやbabelの過程を踏むことでコメントを除去したり、変数名を変えられるのが大きいと思います。
型を付けたい
一般的なJavaプログラマであれば、JMLとまでは言わなくても、型を付けて静的検査ぐらいはしたくなると思います。大雑把に言って、JavaScriptに型を付けた言語がTypeScriptやFacebook/Flowです。個人的にはJava屋の場合は、JSDocで型を書いて、WebStormにチェックしてもらうぐらいが一番だと思います。Google Closure Linterという(残念ながら)マイナーなトランスパイラテクノロジーもあるのですが、そろそろ滅んでしまいそうです。
それ以外のAltJS言語(死語)については、Java屋がやる仕事なら避けたほうがよいのではないでしょうか。ClojureScriptとかScala.jsとかロマンはありますね。大規模環境だとGWTもひょっとするといいかも知れません。いずれにせよ、JavaScriptが分かってないのに突っ込むのはお勧めしません。
エディタ
eclipseのJavaScript対応はJavaの対応レベルに比べるとかなり微妙です。VSCodeか、有料ですがWebStormがお勧めです。
おわりに
JavaScriptは非常にシンプルな言語であり、したがって難しい言語であり、楽しい言語です。ぜひ習得して、JavaにScriptが付いたようなものだと嘯いてください。(ちなみにJava Advent Calendar 2018 11日目としては「真面目にJavaを書く話」的なものを予定していたんですが、遅延しまくった上にポエミィになったので差し替えました。)
コメント
コメントを投稿