あもんノート TOP

速習 JavaScript

JavaScript はブラウザ上で動くスクリプト言語で、 通常、HTML の script 要素に記述されることで実行されます。 高速かつシンプルな仕組みでオブジェクト指向を実現する、非常に優れた言語です。 動的なホームページやウェブアプリの多くがこの JavaScript で書かれています。 一方、JS 実行 は JavaScript をオンラインで簡単に実行するアプリで、 JavaScript のテストや学習に適したものです。
JS 実行
ここでは JS 実行のコードを多数例示し、 JavaScript の基本事項を解説していきます。 上のリンクをクリックして JS 実行で試しながら学習を進めてください。 JS 実行のコードを書けるようになれば、JavaScript は当然のこと、 近年の多くのプログラミング言語の理解に役立つでしょう。 また、JS 実行が用意しているオブジェクトの利用により、 高度な数値計算やグラフ描画をオンラインで簡単に行えるようになります。

目次

数値と演算

JS 実行のコードエリアに以下のコードを書いてみてください (コピペでも良い)。

a = 3; b = 4
c = a / b
println( c )
実行ボタンを押すと、出力エリアに c の値である 0.75 が表示されるはずです。 変数への代入は = で行います。 複数の命令文は ; (セミコロン)で区切ります。 算術演算子には、
+ (加算)、- (減算)、* (乗算)、/ (除算)、% (剰余)、** (べき)
があります。 println は JS 実行が用意した関数(メソッド)で、 引数の値を出力エリアに行出力します。

JavaScript では変数の型を宣言する必要がありません。 このような言語は 動的型付け言語 と呼ばれます。 代入された値によって自動的に型が決まります。 変数の型を調べるには typeof という演算子を使います。

a = 3.2
println( typeof a )   // number と表示される
整数と小数の区別はありません。 ひっくるめて 数値型 (number)とみなされます。 // 以下、改行まではコメント(注釈、REM 文)とみなされ、 動作に影響を与えません。 コメントの始まりと終わりを自由に指定したい場合は、 /* 〜 */ を使います。

数値型の変数に .toFixed( n ) を付けると小数点以下 n 桁の近似表記が返されます。

a = 35.223567
println( a.toFixed( 3 ) )   // 35.224 と表示される
また、数値型の変数に .toExponential( n ) を付けると小数点以下 n 桁の指数表記が返されます。

a = 123456789
println( a.toExponential( 4 ) )   // 1.2346e+8 と表示される
. (ドット)以下に記述することで呼び出される関数を、 その変数の メンバ関数 といいます。 toFixed や toExponential は数値型の変数が持つメンバ関数です。

文字列

文字列 は " (ダブルクォーテーション)または ' (シングルクォーテーション)で囲って表します。

s = "Taro"
println( "My name is " + s )   // My name is Taro と表示される
println( typeof s )   // string と表示される
文字列は + 演算子で連結されます。 数値と文字列を + で繋ぐと自動的に数値が文字列に変換され、連結されます。 強制的に数値を文字列に変えるには String、 文字列を数値に変えるには Number という関数を使います。

a = 123; b = "456"
println( a + b )   // 123456 と表示される
println( a + Number( b ) )   // 579 と表示される
文字列型のメンバ関数は色々あり、次のようなものがよく用いられます。

s = "abcdefg"
println( s.length )   // 7 (文字列の長さ)
println( s.indexOf( "d" ) )   // 3 ( "d" が最初に現れる位置)
println( s.indexOf( "m" ) )   // -1 ( 存在しない場合は -1 になる)
println( s.substr( 2, 3 ) )   // cde (2番目から3文字抜き出す)
println( s.substr( 3 ) )   // defg (3番目以下を抜き出す)
println( s.replace( "e", "h" ) )   // abcdhfg (最初の "e" を "h" に置換)
t = "  xyz  "
println( t.trim() )   // xyz (前後の空白やタブを除去)
JavaScript では最初の文字を 0 番目と数えるので注意。 length に () は必要ありません。 これは実はメンバ関数ではなく、 メンバ変数 という扱いだからです。

真理値

truefalse真理値型 (ブーリアン型)におけるただ2つの値です。 それぞれ真(正しい)と偽(正しくない)を意味します。

a = 1; b = 2
println( a == b )   // false
println( a != b )   // true
println( a < b )   // true
println( a >= b )   // false
println( typeof true )   // boolean
== は等号で、!= は ≠ (等しくない)を意味します。 代入が = だったのに対し、等号は == なので注意してください。

論理演算子 には && (かつ)、|| (または)、! (でない)があります。

println( true && false )   // false
println( true || false )   // true
println( !false )   // true
関数 Boolean は引数の値(文字列の場合はその長さ)が 0 のとき false、 そうでない場合は true を返します。

println( Boolean( 0 ) )   // false
println( Boolean( 1 ) )   // true

条件分岐

条件分岐は if で行います。 基本的な文法を例示しておきます。

cls()
a = 5
if( a > 3 ) println( 1 )
a = 5 のとき a > 3 は true なので 1 が表示されます。 もし a = 5 という文を a = 1 と書き換えると何も表示されなくなります。 cls は JS 実行が用意した関数で、出力エリアをクリアします。

複数の命令を書くときは {} を用いてブロック化します(ブロック if 文)。

cls()
a = 2
if( a > 0 ){
	a = a + 1
	println( "a の値を増やしました" )
}
println( "a = " + a )
字下げ(タブ)はコードの読みやすさ(可読性)のために行っていて、 動作には影響を及ぼしません。 if 文の後に else を書くと、"そうでない場合" という意味になります。

cls()
a = 2
if( a < 0 ){
	println( "い" )
} else if( a > 2 ){
	println( "ろ" )
} else{
	println( "は" )
}
変数 a の値を変えて動作を確認してみてください。

繰り返し処理

条件を満たす間だけ繰り返し処理を行うには while を用います。

i = 0
while( i < 10 ){
	print( i )
	i++
}
// 0123456789 と表示される
print は JS 実行が用意した関数で、 println と異なり表示後改行を行わない単純出力です。 i++ は i = i + 1 の略記で、変数 i の値を 1 だけ増やします。 これを インクリメント といいます。 同じことは i += 1 と書くこともできます。 同様に、i-- は i = i - 1 の略記で、 デクリメント といいます。

上の例は for を用いて次のように書くこともできます。

for( i = 0; i < 10; i++ ){
	print( i )
}
次のコードは九九の表を出力します。

for( i = 1; i < 10; i++ ){
	for( j = 1; j < 10; j++ ){
		print( ( i * j ) + " " )
	}
	print( "\n" )
}
"\n" は改行コードです。 while や for から強制的に抜けるには、 break というキーワード(コマンド)を使います。

i = 0
while( true ){
	print( i )
	i++; if( i > 9 ) break
}
// 0123456789 と表示される
また、while や for のループからは抜けないが、 今回の処理をキャンセルするキーワードに continue があります。

for( i = 0; i < 10; i++ ){
	if( i >= 3 && i <= 7 ) continue
	print( i )
}
// 01289 と表示される
ここまでの構文は、 C、C++、PHP、Java、C# 等、C 系の言語とそう変わりません。 異なるのは、変数の型宣言が無用であるということと、 行末においては ; を省略できるということです。 例えば上のコードを C# で書くなら、

using System;

class test{
	static void Main(){
		int i;
		for( i = 0; i < 10; i++ ){
			if( i >= 3 && i <= 7 ) continue;
			Console.Write( i );
		}
	}
}
となります。 for 文がやや冗長なのは C 言語の特徴であり欠点であるといえます(ループ変数を3回も書く必要がある)。 JavaScript や C# はこの悪習をひきずっています。 一方でこれを改善している言語に Lua、Ruby、Python 等があります。

関数

関数 (メソッド、プロシージャ)は一連の手続きをまとめたものです。 引数を持たせたり、返り値を得ることもできます。

f = function( x ){
	return x ** 2 + 1
}

println( f( 3 ) )   // 10
println( typeof f )   // function
function は関数であることの宣言、 引数があるときは続けて () 内に書きます。 return は関数を終えるキーワードで、 もしその後に式があればその値が返されます。 上の例で変数 f が 関数型 (function)に値をとっていることがわかりますが、 関数がデータ型の一種であることは、 他の言語ではあまり見られない JavaScript の特徴です。 ちなみに上のコードは次のように書き換えることもできます。

println( f( 3 ) )   // 10
println( typeof f )   // function

function f( x ){
	return x ** 2 + 1
}
このように function から書き始める文法では、 その関数はコードの先頭で宣言されたものと解釈されます。 これを関数の 巻き上げ といいます。

順列(permutation)を与える関数を作ってみましょう。

permutation = function( n, m ){
	var k = 1
	for( var i = n; i > n - m; i-- ) k *= i
	return k
}

println( permutation( 5, 3 ) )   // 60
varローカル変数 の宣言です。 ローカル変数は宣言した関数の中だけで有効な変数で、 関数を抜けると無効になります。 このような変数の スコープ (有効範囲)は、 関数のブラックボックス的な性質(独立性)のために必要です。 対して、var で宣言せずに用いた変数は グローバル変数 とみなされ、 どこでも有効ということになります。 引数もローカル変数の一種です。 k *= i は k = k * i の略記です。

ここまでのおさらいを兼ねて、少し本格的なサンプルコードを示します。

// 素数の一覧表示

isPrime = function( n ){
	if( n < 2 ) return false
	for( var i = 2; i <= n ** 0.5; i++ ){
		if( n % i == 0 ) return false
	}
	return true
}

putPrimesInRange = function( a, b ){
	var k = 0
	for( var n = a; n <= b; n++ ){
		if( isPrime( n ) ){
			print( n + " " ); k++
		}
	}
	println( "\n以上 " + k + " 個" )
}

cls(); putPrimesInRange( 1, 1000 )
関数名は少し長くても適切なものにしましょう。 そうすることでコードの可読性が高まります。 また、何気ない処理でも関数化し名前を付けることで、 やはり可読性が高まります。 このようなプログラミング手法は 構造化 と呼ばれます。

配列

プリミティブ(原始的)なデータの集合体を オブジェクト といいます。 配列 はオブジェクトの一種で、 特に番号づけられた変数(メンバ)の集合体です。

x = [ "a", "b", "c" ]
println( x[0] )   // a
println( x[1] )   // b
println( x[2] )   // c
println( x.length )   // 3
println( typeof x )   // object
length は配列が自動的に持つメンバ変数で、 メンバの数を記憶しています。 空の配列にメンバを追加していくことで配列を作ることもできます。

x = []
x[0] = "a"
x[1] = "b"
x[2] = "c"
println( x[1] )   // b
配列の各メンバをさらに配列とすることで高階の配列を作ることができます。

a = []
for( i = 0; i < 10; i++ ){
	a[i] = []
	for( j = 0; j < 10; j++ ){
		a[i][j] = i * j
	}
}
println( a[3][7] )   // 21
文字列の節では紹介しませんでしたが、 文字列のメンバ関数の一つに、 文字列を指定された区切り文字で分割する split があります。返り値は分割された文字列の配列になります。

s = "ab, cde, fg, hij"
ss = s.split( "," )
for( i = 0; i < ss.length; i++ ){
	ss[i] = ss[i].trim()
}
println( ss[0] )   // ab
println( ss[1] )   // cde
また、関数の中で arguments は引数を保持する配列になります。

sumOf = function(){
	var k = 0
	for( var i = 0; i < arguments.length; i++ ){
		k += arguments[i]
	}
	return k
}
println( sumOf( 4, 5, 6, 7 ) )   // 22
一方、関数の引数列を配列で与えたい場合は、... を使います。

norm = function( a, b ){
	return ( a ** 2 + b ** 2 ) ** 0.5
}
a = [ 3, 4 ]
println( norm( ... a ) )   // 5

連想配列

番号の代わりに文字列をインデックス(添字、キー)とした配列を、 連想配列 (ハッシュ、マップ、ディクショナリ)といいます。

x = { name: "太郎", age: 16 }
println( x.name )   // 太郎
println( x.age )   // 16
この例では x が連想配列で、 メンバに name と age という名の変数を持っています。 このコードは下のように書いても同じです。

x = { name: "太郎", age: 16 }
println( x["name"] )   // 太郎
println( x["age"] )   // 16
配列と同様、 空の連想配列にメンバを追加していくこともできます。

x = {}
x.name = "太郎"
x.age = 16
println( x.age )   // 16
プリミティブな変数は、代入の際、値渡し になるのに対し、 オブジェクトは 参照渡し になります。 下の例を見てください。

a = 1
b = a   // b には a の値 1 が渡される
a = 2
println( b )   // 1

x = { fox: 1 }   // x の "値" はこのオブジェクトのアドレス
y = x   // y にはそのアドレスが渡される
x.fox = 2   // よって x のメンバの値を変えると
println( y.fox )   // 2 --- y のメンバの値も変わる
a と b は別の変数になりますが、 x と y は同一のオブジェクトを参照することになります。 このことをちゃんと理解していないと、本格的なプログラムを作った際、 予期せぬ挙動に悩まされることになります。 ちなみにアドレスはメモリ上の番地を意味します。 C 言語など低レベルな処理が可能な言語では、 これをポインタ型の変数であらわに扱いますが、 一般に低レベル処理は暴走(ハング)の原因になるため、 近年よく用いられる高級言語の多くはこのような低レベル処理を言語レベルで禁止しています。

オブジェクトを複製したい場合は Object.assign を用います。

x = { fox: 1 }
y = Object.assign( {}, x )
// {} に x の全てのメンバを追加したオブジェクトを y とする

x.fox = 2
println( y.fox )   // 1

オブジェクトの for 文

for( n of a ) ... は配列 a のメンバに対して繰り返し処理を行います。 このときループ変数 n は配列 a の各メンバの値に値を取ります。

a = [ "x", "y", "z" ]
for( n of a ) print( n )   // xyz
先に書いた関数 sumOf は、 以下のように書くこともできます。

sumOf = function(){
	var k = 0
	for( var n of arguments ) k += n
	return k
}
println( sumOf( 4, 5, 6, 7 ) )   // 22
一方、for( n in a ) ... はオブジェクト a が持つメンバに対して繰り返し処理を行います。 このときループ変数 n は a の各メンバ名(インデックス、キー)に値を取ります。

a = { x: 1, y: 2, z: 3 }
for( n in a ) print( n )   // xyz
for( n in a ) print( a[n] ) // 123
of と in の違いに注意してください。 of は配列専用ですが、in は配列でも連想配列でも使えます。

複合的なオブジェクト

配列と連想配列を組み合わせてデータベースを作れます。

ourList = [
	{ name: "太郎", age: 16 },
	{ name: "次郎", age: 14 },
	{ name: "花子", age: 12 }
]

println( ourList[1].name )   // 次郎
println( ourList[2].age )   // 12
OS におけるフォルダ(ディレクトリ)がいい例ですが、 箱の中に箱が複数あって、 それぞれの箱の中にまた箱が複数あるというような構造は、 ツリー構造 と呼ばれます。 木の幹と枝が成すような構造だからです。 配列や連想配列で作られるオブジェクトも一般にツリー構造を成します。 その末端部分、 すなわち一番小さい箱がプリミティブな変数で、 値を持つことになります。 プリミティブな変数はファイル、 オブジェクトはフォルダのようなものだと考えるとわかりやすいでしょう。

関数をメンバとすることで、 機能を持ったデータベースを作ることもできます。

ourList = {
	no: [
		{ name: "太郎", gender: "M", age: 16 },
		{ name: "次郎", gender: "M", age: 14 },
		{ name: "花子", gender: "F", age: 12 }
	],
	get: function( name, p ){
		for( var k of this.no ){
			if( k.name == name ) return k[p]
		}
	}
}

println( ourList.get( "次郎", "age" ) )   // 14
println( ourList.get( "花子", "gender" ) )   // F
this はその関数を呼んだオブジェクトを指すキーワードです。 今の例では ourList.get( ... ) と書いて、 オブジェクト ourList が関数 get を呼んでいるので、 この場合の this は ourList を指します。 さらに、データを編集する関数、検索する関数などを追加することで、 より本格的なデータベースにすることも可能です。

特にゲームやアプリなど、大きなプログラムを書く場合、 このようにデータと機能がパッケージングされたオブジェクトをいくつか用意し、 それらをプログラムの部品(モジュール)と考え、 コーディングするのが効率の良い方法になります(オブジェクト指向)。 具体的な例は後でアクションゲームのコードという形でお見せします。

ビルトインオブジェクト

JavaScript のメモリ領域は window という名のオブジェクトで与えられています。 その配下に色々な変数やオブジェクトが(少なくとも仮想的に)置かれています。 例えば Number や String という関数ですが、 その正式名称は window.Number、window.String です。 window. は省略できるため、単に Number、String で使用できたわけです。 ユーザーが用いたグローバル変数や関数も window の配下に作られます。

fox = 1
println( window.fox )   // 1
window の配下にあって最初から用意された変数やオブジェクトを、 ビルトインオブジェクト といいます。 Number、String の他、よく使うところでは、 document、Math、Date 等があります。

例えば JS 実行が用意している print は次のように与えられています。

print = function(){
	var ele = document.getElementById( "text" )
	if( arguments.length > 0 ) ele.value += arguments[0]
}
document の配下に getElementById という関数があります。 この関数は ID で指定された HTML ドキュメントの要素を返します。 今の場合、 "text" という ID を持つ出力用テキストエリアがオブジェクトとして返されます。 その配下のメンバ変数(プロパティ) value の値、 すなわちテキストエリアの内容に、数値や文字列を追加してやれば、 それが出力されることになるわけです。

Math オブジェクトは数学に関する変数や関数を収容しています。

cls()
println( Math.PI )   // 3.14159... (円周率)
println( Math.E )   // 2.71828... (自然対数の底)
println( Math.floor( 2.3 ) )   // 2 (切り捨て)
println( Math.ceil( 2.3 ) )   // 3 (切り上げ)
println( Math.round( 4.7 ) )   // 5 (四捨五入)
println( Math.abs( -1.2 ) )   // 1.2 (絶対値)
println( Math.sign( -1.2 ) )   // -1 (符号)
println( Math.sqrt( 2 ) )   // 1.41421... (平方根)
println( Math.exp( 2 ) )   // 7.38905... (指数関数)
println( Math.log( 2 ) )   // 0.69314... (自然対数)
println( Math.log10( 100 ) )   // 2 (常用対数)
println( Math.sin( 0.3 ) )   // 0.29552... (三角関数)
println( Math.asin( 0.5 ) )   // 0.52359... (逆三角関数)
println( Math.random() )   // 0〜1 の間の乱数
println( Math.max( 3, 4, 5 ) )   // 5 (最大値)
println( Math.min( 3, 4, 5 ) )   // 3 (最小値)

Infinity、NaN、undefined

JavaScript では 0 で除算してもエラーになりません。

a = 1 / 0
b = 0 / 0
println( a )   // Infinity
println( b )   // NaN
println( typeof a )   // number
println( typeof b )   // number
Infinity は無限大、 NaN は不定や非数を意味する数値です( Not a Number の略)。 数値とみなせない文字列を数値化しても NaN が返されます。

a = Number( "hello" )
println( a )   // NaN
NaN は自身と等しくないという風変わりな性質を持つため、 NaN の判定には関数 isNaN を用います。

a = NaN
println( isNaN( a ) )   // true
一方、定義されていない変数を参照するとエラーになります。 また、定義されていないメンバの値は undefined になります。 undefined は undefined 型の値です。

a = [ 0, 10, 100 ]
println( a[3] )   // undefined
println( typeof a[3] )   // undefined
定義されていない変数の型も undefined 型とみなされるので、 このことは変数の有無の判定に用いることができます。

x = 1
println( typeof x == "undefined" )   // false
println( typeof y == "undefined" )   // true
Infinity、NaN、undefined、isNaN はビルトインオブジェクトです。

グラフィックス

JS 実行ではサイズ 800×800 のグラフィックスエリア( Canvas )を用意しています。 そのオブジェクトは、

g = document.getElementById( "graphics" ).getContext( "2d" )
で参照できます。例えば全体を黄色く塗りつぶしたかったら、

g = document.getElementById( "graphics" ).getContext( "2d" )
g.fillStyle = "yellow"
g.fillRect( 0, 0, 800, 800 )
とします。fillStyle は塗りつぶしの色を保持するメンバ変数で、 fillRect は矩形塗りつぶしを行うメンバ関数です。 線や円や文字列を描くのはちょっと面倒です。

g = document.getElementById( "graphics" ).getContext( "2d" )

// 赤で ( 200, 100 ) から ( 600, 700 ) に線を描く
g.strokeStyle = "red"
g.beginPath()
g.moveTo( 200, 100 ); g.lineTo( 600, 700 )
g.stroke()

// 青で中心 ( 400, 200 )、半径 60 の円を描く
g.strokeStyle = "blue"
g.beginPath()
g.arc( 400, 200, 60, 0, 2 * Math.PI )
g.stroke()

// 緑で中心 ( 200, 400 )、半径 100 の塗りつぶされた円を描く
g.fillStyle = "green"
g.beginPath()
g.arc( 200, 400, 100, 0, 2 * Math.PI )
g.fill()

// 茶色、32px の大きさで Hello の文字列を ( 200, 700 ) に描画する
g.fillStyle = "brown"
g.font = "32px sans-serif"
g.fillText( "Hello", 200, 700 )
そこで JS 実行では、特にモノクロに限っては簡単に描画を行えるよう、 論理座標のオブジェクト Coord を用意しました。 詳しくは JS 実行の画面にある Coord (青字)をクリックして説明をお読みください。 以下のコードは関数 y = x^2 - 3 のグラフを描きます。

c = Coord
c.range( -5, 5, -5, 5 )
c.axis( 1, 1 )
f = function( x ){ return x**2 - 3 }
c.graph( f )

イベントドリブン

JavaScript ではキー操作やマウスクリック等の各イベントに対し、 実行する処理を登録できます。 イベントによってプログラムが呼び出される仕組みを、 イベントドリブン、あるいは 非同期処理 といいます。 例えば、キー操作については、 document.onkeydown および document.onkeyup が実行される関数になります。

document.onkeydown = function( e ){
	cls()
	println( "コード " + e.keyCode + " のキーが押されました" )
}
document.onkeyup = function( e ){
	println( "コード " + e.keyCode + " のキーが離されました" )
}
引数 e にはキーイベントの情報が入ったオブジェクトが渡ってきます。 押されたキーのキーコードはメンバ変数 keyCode が持っています。

また、一定時間ごとに呼び出される関数の登録もできます(インターバル)。 下は 0.1 秒ごとに数字を出力するコードです。

putFrame = function(){
	print( tCount + " " )
	tCount++
	if( tCount > 20 ) clearInterval( iid )
}
cls()
iid = setInterval( "putFrame()", 100 )
tCount = 0
setInterval で実行する関数および何ミリ秒ごとに実行するかを指定します。 その返り値はインターバルの ID になるので、 適当な変数名でこれを受け取ります。 インターバルを止める場合は ID を引数として clearInterval を実行します。

これらの機能を使えば、 アニメーションやゲームを作ることができるでしょう。 ただしイベントドリブンを用いたプログラミングには多少のコツが必要です。 以下は少し長いですが、簡単なアクションゲームのコードです。 良かったら参考にしてください。

//=== 弾避けゲーム ===

key = {
  init: *{
    .left = 66   // [B]
    .right = 77   // [M]
    .up = 72   // [H]
    .down = 78   // [N]
    .start = 90   // [Z]
    .in = []
    document.onkeydown = function( e ){
      key.in[e.keyCode] = true
    }
    document.onkeyup = function( e ){
      key.in[e.keyCode] = false
    }
  }
}

player = {
  init: *{
    .size = 5
    .speed = 3
    .score = 0
    .x = 100; .y = 50
  },
  move: *{
    if( key.in[key.left] && .x > 5 + .size ) .x -= .speed
    if( key.in[key.right] && .x < 195 - .size ) .x += .speed
    if( key.in[key.up] && .y < 150 - .size ) .y += .speed
    if( key.in[key.down] && .y > 10 + .size ) .y -= .speed
  },
  draw: *{
    var c = Coord
    var x = .x, y = .y, s = .size
    var s1 = s / 5, s2 = s * 2 / 5
    c.line( x, y+s, x-s2, y+s1 )
    c.line( x, y+s, x+s2, y+s1 )
    c.line( x-s2, y+s1, x+s2, y+s1 )
    c.line( x-s2, y+s1, x-s2, y-s )
    c.line( x+s2, y+s1, x+s2, y-s )
    c.line( x-s, y-s, x+s, y-s )
    c.line( x-s, y-s, x-s2, y-s1 )
    c.line( x+s, y-s, x+s2, y-s1 )
    c.line( x, y-s1, x, y-s )
  }
}

enemy = {
  init: *{
    .size = 2
    .num = 20
    .no = []
    for( var i = 0; i < .num; i++ ){
      .no[i] = { x: 0, y: 0, vx: 0, vy: 0 }
    }
  },
  add: *{
    for( var k of .no ){
      if( k.vy == 0 ){
        k.x = Math.random() * 200
        k.y = 220
        k.vx = 2 * Math.random() - 1
        k.vy = -2 * Math.random() - 2
        return
      }
    }
  },
  move: *{
    for( var k of .no ){
      if( k.vy != 0 ){
        k.x += k.vx; k.y += k.vy
        if( k.y < - .size * 2 ) k.vy = 0
      }
    }
  },
  draw: *{
    for( var k of .no ){
      if( k.vy != 0 ) Coord.circle( k.x, k.y, .size )
    }
  },
  hit: *{
    var obj = arguments[0]
    var dx, dy, d
    for( var k of .no ){
      if( k.vy != 0 ){
        dx = obj.x - k.x; dy = obj.y - k.y
        d = ( dx**2 + dy**2 )**0.5
        if( d < .size + obj.size ) return true
      }
    }
    return false
  }
}

game = {
  run: *{
    Coord.range( 0, 200 )
    key.init(); player.init(); enemy.init()
    .mode = "STANDBY"
    .tCount = .highScore = .explosionR = 0
    .s1 = "HI-SCORE "; .s2 = "  SCORE "
    setInterval( "game.frame()", 25 )
  },
  drawScores: *{
    Coord.str( .s1 + .highScore + .s2 + player.score, 5, 190 )
  },
  enemyAdd: *{
    var r = Math.round( 10 + 8 * Math.cos( .tCount / 300 ) )
    if( .tCount > 50 && .tCount % r == 0 ) enemy.add()
  },
  frame: *{
    .tCount++
    Coord.clear()
    .drawScores()
    .enemyAdd(); enemy.move(); enemy.draw()
    if( .mode == "PLAY" ){
      if( .tCount < 50 ) Coord.str( "START", 85, 100 )
      player.score++; player.move(); player.draw()
      if( enemy.hit( player ) ){
        if( player.score > .highScore ){
          .highScore = player.score
        }
        .mode = "EXPLOSION"
        .explosionR = 0
      }
    }
    if( .mode == "EXPLOSION" ){
      Coord.circle( player.x, player.y, .explosionR )
      .explosionR += 10
      if( .explosionR > 300 ) .mode = "STANDBY"
    }
    if( .mode == "STANDBY" ){
      Coord.str( "PRESS [Z] TO START", 55, 100 )
      if( key.in[key.start] ){
        player.init(); enemy.init()
        .mode = "PLAY"
        .tCount = 0
      }
    }
  }
}

game.run()
キーコードは Windows PC のそれです。 他の環境の方、あるいは他のキーを用いたい方は、 コード先頭にある key オブジェクトの内容を修正してください。 this. を . と略記しています。 また、function(){ を *{ と略記しています。 これらは JS 実行の機能です。

上のコードではグローバル領域に key、player、enemy、game の4つのオブジェクト(連想配列)が置かれているだけです。 この他にグローバル変数は一切書かれていません。 OS のデスクトップにファイルが沢山散らかっていたら嫌でしょう? これと同じことで、データと機能の集合体をオブジェクトとしてモジュール化し、 グローバル変数をできるだけ用いないことが、 オブジェクト指向プログラミングの基本になります。 このようなプログラミング手法は特に多人数で大きなプログラムを作るときに有効となります。

HTML ファイル

HTML はウェブページを書くための書式です。 JavaScript を含んだ HTML を作成する場合、 その基本的な書式は次のようなものになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="shift_jis">
<title>簡易電卓</title>
<style>
*{ font-family: sans-serif; font-size: 16px; }
#box{ width: 640px; margin: 50px auto; }
.txt{ width: 250px; margin: 0 10px; padding: 5px; }
</style>
<script>
ele = function( s ){
	return document.getElementById( s )
}
onload = function(){
	ele( "exe" ).value = "="
	ele( "exe" ).onclick = function(){
		var s = ele( "in" ).value
		ele( "out" ).value = eval( s )
	}
}
</script>
</head>
<body>
<div id="box">
<input type="text" id="in" class="txt">
<input type="button" id="exe">
<input type="text" id="out" class="txt">
</div>
</body>
</html>
これを拡張子 .html のテキストファイルとして保存すれば HTML ファイルになります。それをブラウザで開けば、 書かれている JavaScript が機能し、 この場合は簡単な電卓になります。 meta 要素における charset には保存したテキストファイルの文字コードを指定します。 onload はページ読み込み終了時に実行される関数です。 onclick はドキュメント要素クリック時に実行される、 ドキュメント要素のメンバ関数です。 eval は引数の文字列をコードとみなし、その値を返す関数です。

HTML と CSS を知っていれば、あるいは学習すれば、 上のスクリプトは容易に解読でき、 様々なオンラインアプリを作れるようになるでしょう。 OS、HTML、CSS、JavaScript はコンピューターを学ぶ上で重要となる項目です。

クラスとインスタンス

JavaScript には数値や文字列がプリミティブな型として用意されていますが、 複素数の型は用意されていません。 そこで、複素数の型を作ってみたいとします。 それは実部(re)と虚部(im)をメンバ変数に持ち、 さらに大きさ(abs)や和(sum)をメンバ関数として持つものとします。 これは素朴には次のように書けばいいでしょう。

// 素朴な複素数生成関数(駄目な例)
Complex = function( re, im ){
	return {
		re: re,
		im: im,
		abs: function(){
			return ( this.re ** 2 + this.im ** 2 ) ** 0.5
		},
		sum: function( z ){
			return Complex( this.re + z.re, this.im + z.im )
		}
	}
}

// 以下は使用例
a = Complex( 1, 4 )
b = Complex( 3, -1 )
c = a.sum( b )
println( c.re + ", " + c.im + ", " + c.abs() )   // 4, 3, 5
関数 Complex の返り値を連想配列としていることに注意。 例えば a = Complex( 1, 4 ) において、a は、
{ re: 1, im: 4, abs: function ... , sum: function ... }
というオブジェクトへの参照になります。

これで Complex が(演算機能を内包した)複素数を生成する関数になっているわけですが、 よく考えると少し不満があります。 生成する複素数が数個ならまだしも、何百個も生成する場合、 それぞれの複素数がまったく同じ関数である abs と sum を保持しているのはあまりに不経済です。 メンバ関数は型に共通なので、どこか一か所に保管されているべきです。 そこで次のように改良します。

Complex = {
	new: function( re, im ){
		return {
			re: re,
			im: im,
			__proto__: this.prototype
		}
	},
	prototype: {
		abs: function(){
			return ( this.re ** 2 + this.im ** 2 ) ** 0.5
		},
		sum: function( z ){
			return Complex.new( this.re + z.re, this.im + z.im )
		}
	}
}

a = Complex.new( 1, 4 )
b = Complex.new( 3, -1 )
c = a.sum( b )
println( c.re + ", " + c.im + ", " + c.abs() )   // 4, 3, 5
メンバ変数 __proto__ には、 呼び出そうとするメンバが見つからない場合に探す場所を指定できます。 それをここでは Complex.prototype に指定しています。 こうしてメンバ関数 abs と sum は Complex.prototype 配下に置いておけば良いことになるわけです。

Complex は複素数という型の情報を集約した、 "型の設計図" を意味するオブジェクトになっています。 このようなオブジェクトを クラス といい、 クラスから生成されたオブジェクトを インスタンス といいます。 上の例では変数 a, b, c がクラス Complex のインスタンスになります。 Complex.prototype 配下に様々なメンバ関数を追記していくことで、 本格的な複素数のクラスを作成することができるでしょう。

ちなみに、__proto__: this.prototype という部分は、 __proto__: Complex.prototype とあらわに書いても良さそうですが、 this が使える所はできるだけ this を使うようにします。 これはクラスの継承と関係した、いわば "お約束" です。

ラッパーオブジェクト

数値型や文字列型の変数はオブジェクトでなくプリミティブなはずだったのに、 メンバ変数やメンバ関数を持っていることを不思議に思った人がいるかもしれません。 この疑問はもっともで、 実はこれらプリミティブな型は、そのメンバが呼ばれると、 一時的にオブジェクト(インスタンス)に変換されて扱われます。 このようなオブジェクトを ラッパーオブジェクト といいます( wrap = 包む )。 数値型のメンバ参照先は Number.prototype、 文字列型のメンバ参照先は String.prototype です。 例えば数値型のメンバ関数である toExponential の実態は、 Number.prototype.toExponential になります。

そうすると、「あれ? Number や String は関数だったはず。 なんで prototype というメンバを持ってるの?」 という新たな疑問を生じるかもしれません。 この辺りは JavaScript の八方美人的なところで、 実は関数はオブジェクトの一種であり、作成時自動的に name や prototype 等のメンバを持ちます。

例えば Number.prototype 配下に関数を追加することで、 数値型のメンバ関数を作ることができます。

Number.prototype.put = function(){
	println( this )
}

a = 3
a.put()   // 3
ただしビルトインオブジェクトに手を入れることは行儀が悪いことと考えられるので、 このようなことは実際にはすべきではないでしょう。

クラス構文とクラスベース言語

先に述べた複素数のクラスですが、次のように書くこともできます。

class Complex{
	constructor( re, im ){
		this.re = re
		this.im = im
	}
	abs(){
		return ( this.re ** 2 + this.im ** 2 ) ** 0.5
	}
	sum( z ){
		return new Complex( this.re + z.re, this.im + z.im )
	}
}

a = new Complex( 1, 4 )
b = new Complex( 3, -1 )
c = a.sum( b )
println( c.re + ", " + c.im + ", " + c.abs() )   // 4, 3, 5
これを クラス構文 といいます。 new はインスタンス生成のためのキーワードで、 指定されたクラスのコンストラクタ関数 constructor を呼び出し、インスタンスを生成します。 コンストラクタ関数内における this は生成されるインスタンスを指します。 new によるインスタンス生成を インスタンス化 といいます。

クラス構文は Java や C# 等の クラスベース の言語に似せた仕様です。 例えば C# では上のコードは次のように書かれます。

using System;

class Complex{
	public double re;
	public double im;
	public Complex( double _re, double _im ){
		re = _re;
		im = _im;
	}
	public double abs(){
		return Math.Sqrt( Math.Pow( re, 2 ) + Math.Pow( im, 2 ) );
	}
	public Complex sum( Complex z ){
		return new Complex( re + z.re, im + z.im );
	}
}

class test{
	static void Main(){
		Complex a, b, c;
		a = new Complex( 1, 4 );
		b = new Complex( 3, -1 );
		c = a.sum( b );
		Console.Write( c.re + ", " + c.im + ", " + c.abs() );
	}
}
一般にクラスベースの言語では、クラスが構文として先にあって、 オブジェクトはクラスから生成されます。 また、クラスはモジュールの役割も果たし、 エントリーポイント(最初に呼ばれる関数)もあるクラス内の静的(static)な関数として書かれます。 static はクラスに固有な関数や変数を意味し、 逆に静的でない( static 宣言のない)関数や変数はインスタンスに固有なものになります。 public は他のクラスに公開することを意味します。

一方、JavaScript は、基本的には プロトタイプベース の言語であり、 オブジェクト(連想配列)が先にあって、クラスはオブジェクトによって実現されます。 スマートでわかりやすいのは JavaScript のような関数がデータ型のプロトタイプベース言語だと思いますが、 近年よく普及しているのはクラスベースの言語です。 クラス構文の導入によって、 JavaScript はプロトタイプベースとクラスベースのハイブリッドに位置付けられることになります。

インスタンス化の例

最後に new キーワードを用いるインスタンス化の例を2つ紹介しておきます。

Date は日時に関するクラスです。

cls()
d = new Date()
println( d.getTime() )   // ある瞬間からの経過ミリ秒
println( d.getFullYear() )   // 年
println( d.getMonth() )   // 月 (0〜11)
println( d.getDate() )   // 日 (1〜31)
println( d.getDay() )   // 曜日 (0〜6)
println( d.getHours() )   // 時 (0〜23)
println( d.getMinutes() )   // 分 (0〜59)
println( d.getSeconds() )   // 秒 (0〜59)
println( d.getMilliseconds() )   // ミリ秒 (0〜999)
d = new Date() により現在の日時のインスタンスが生成されます。 Date クラスのメンバ関数により日時に関する様々な情報を取得できます。 月は 0〜11 に値を取るので注意。0 は1月を意味します。 特定の日時のインスタンスを生成する場合は、
d = new Date( 2017, 3, 26, 9, 41, 0 )
のように書きます。 これは 2017年4月26日9時41分0秒 を意味します。

また、Image は画像データに関するクラスです。

<!DOCTYPE html>
<html>
<head>
<script>
onload = function(){
	gra = document.getElementById( "gra" ).getContext( "2d" )
	img = new Image(); img.src = "a.png"
	document.onkeydown = function( e ){
		if( e.keyCode == 90 ) gra.drawImage( img, 0, 0 )
	}
}
</script>
</head>
<body>
<canvas id="gra" width="800" height="800"></canvas>
</body>
</html>
この HTML ファイルは、 Z キー(コード 90 のキー)を押すことで、 同じパス(フォルダ内)にあるファイル a.png の画像を Canvas の座標 (0,0) に表示します。 ゲームでキャラクタや背景などに画像を使用したい場合、 この Image クラスを利用することになります。


inserted by FC2 system