Hatena::Diary

なんとなく日記

2010年01月31日

JavaScript基礎文法最速マスター

JavaScript 版は誰も書いていなかったようなので書いてみます。こういう解説記事的なものを書くのは初めてなので変なところがあったら指摘して頂けるとありがたいです。

1/31 23:58追記

コメント欄のos0xさんのご指摘を基に一部追記・修正を行いました。

まえがき

本記事は主に JavaScript の文法面について解説します。基本的に ECMAScript 第3版の範囲内の文法について取り扱いますが,そこからはみ出す部分については適宜注釈をつけておきます。

JavaScript の標準仕様

JavaScript仕様は標準化団体 Ecma International によって ECMAScript として定められています。この ECMAScript を各社が実装したのが Mozilla 等の JavaScriptMicrosoftJScript, AdobeActionScript などです(詳細はECMAScript - Wikipedia参照)。ただ単に JavaScript と言った場合はこれらの実装をひっくるめて呼んでいることが多いようです。

JavaScript の実行 (ブラウザ編)

JavaScript で書かれたを実行するためにはブラウザを用いるのが一番手っ取り早いです。1行で済むスクリプトを実行する場合,ブラウザのロケーションバーに以下のように打ち込みます。

javascript: 実行したいプログラム

例えば,メッセージを表示させるなら以下のようにします。

javascript: alert("aaa");

ブックマークレットはこのようにして JavaScript プログラムを実行させています。

上記の仕組みで実行できるのは1行に収まる(改行を含まない) JavaScript だけです。改行ではなくセミコロンで文を区切ることで複数の文を含むプログラムも実行できますが,そのようなプログラムは読み書きしづらく大変です。複数行の JavaScript を実行させるためには, JavaScriptソースコードだけではなく以下のような HTML を書き,それをブラウザに読み込ませる必要があります。

<html>
<head>
<title>JavaScript テスト</title>


<script type="text/javascript">

</script>


<script type="text/javascript" src="ファイル名"></script>

</head>
<body>

<script type="text/javascript"></script>
</body>
</html>
JavaScript の実行 (ブラウザ以外編)

ブラウザ上の JavaScript と比較するとマイナーですが,他の言語のようにシェル上で JavaScript を実行できる実装もあります。ここでは詳細には触れません。

基礎

print文

JavaScript の標準ライブラリには入出力に関する関数が一切定義されていません!文字列を出力する場合は,ブラウザで実装されている alert 関数を用いる事が多いです。

alert("Hello, world");

また,最近は多くのブラウザconsole.log がサポートされているので,コンソールウインドウにメッセージを出すこともできます。 console.log を使うと文字列だけでなく様々なオブジェクトを出力することができます。

console.log("Hello, world");
console.log([1, 2, 3]);
コメント

コメントは C 系の言語と同じです。

// 一行のコメント
/*
複数行の
コメント
*/
文法チェック

次世代バージョンの JavaScript (ECMAScript 5) では Strict モードという,より厳密に文法チェックが行われるモードが導入されます。メジャー処理系はまだ Strict モードをサポートしていませんが,今から JavaScript を覚えるのなら厳密な書き方を身につけておいた方が良いでしょう。

Strict モードにするためには,スクリプトの先頭に以下を書き込みます。このコード自体はただの文字列なので, Strict モードに対応していないブラウザには何の影響も与えません。

"use strict";

文の最後にはセミコロン ( ; ) を付けます。付けない場合自動で補われますがたまに変な挙動を起こすのでできるだけ付けるようにしましょう。

変数の宣言

var で宣言します。変数に型はありません。

var hoge = 1;
hoge = "a"; // 数字も文字も代入できる

宣言していない変数に値を代入することもできますが,その場合はグローバル変数が作られそこに代入されます (厳密にはちょっと違いますが)。未定義変数への代入操作は Strict モードではエラーになるのでできるだけ使わないようにしましょう。

a = 1; // エラーにならない

数値

JavaScript の数値は全てが実数型です。整数型という概念はありません。

var d = 123456;  // 10進数の整数
var h = 0xffff;  // 16進数
var o = 0123;    // 8進数 (Strictモードでは10進数となります)
var f = 12.345;  // 実数
数値演算

JavaScript には実数型しかないので,演算の結果も実数型になります。ただし, ビット演算の場合は小数点以下を切り捨てて整数に変換してから行われます。

var a = 1 + 2;   // => 3
a = 3 - 2;       // => 1
a = 1 * 5;       // => 5
a = 3 / 2;       // => 1.5 (整数同士の割り算でも結果は実数)
a = 3 % 2;       // => 1   (余り)
a = 255.1 & 2.1; // == 255 & 2 => 2   (ビット演算は整数に変換(小数点以下切り捨て)してから行われる)
a = 12.3 >> 1;   // => "6" (同上)
代入演算子とインクリメント・デクリメント

C 系言語と同じように使えます。

var a = 0;
a += 3;          // => 3
a -= 2;          // => 1
a *= 3;          // => 3
a /= 3;          // => 1
a = 0;
var b = a++;     // => a == 1, b == 0
var c = ++a;     // => a == 2, b == 2
var d = a--;     // => a == 1, b == 2
var e = --a;     // => a == 0, b == 0

文字列

文字列はシングルクオート( ' )かダブルクオート ( " )で囲みます。両者は全く等価です。シングル・ダブルクオートのどちらの場合でも \t (タブ), \n (改行) などの特殊文字を利用することができます。変数展開などの便利な機能はありません。

var a = "abc\tdef"; // "abc[tab]def" ([tab]はタブ文字)
var b = 'abc\tdef"; // "abc[tab]def"
文字列操作

結合

var join1 = 'aaa' + 'bbb';
var join2 = ['aaa', 'bbb', 'ccc'].join(',');

分割

var record = 'aaa,bbb,ccc'.split(/,/);

長さ

var length = 'abcdef'.length; // => 5
var jplen  = 'あいうえお';    // => 5

切り出し (substrは多くの環境でサポートされていますが非標準なメソッドなので一応注意)

var substr    = 'abcd'.substr(1, 2); // => bc
var substring = 'abcd'.substring(1, 2); // => b

検索

// 見つかった場合はその位置,見つからなかった場合は-1が返る
var result1 = 'abcd'.indexOf('cd');  // => 2
var result2 = 'abcd'.indexOf('ef');  // => -1

配列

配列の生成方法いろいろ。

var ary1 = [1, 2, 3];          // => [1, 2, 3]
var ary2 = new Array(3);       // => [undefined, undefined, undefined]
var ary3 = new Array(3, 4, 5); // => [3, 4, 5]
配列の参照と代入
var ary = [1, 2, 3];
ary[2];               // => 3   (配列のインデックスは0オリジン)
ary[0] = 3;           // => ary == [3, 2, 3]
要素の個数
ary.length
配列の操作
var ary = [1, 2, 3];
// 先頭を取り出す
var a = ary.shift(); // => a == 1, ary == [2,3]
// 先頭に追加
ary.unshift(5);      // => ary == [5,2,3]
// 末尾を取り出す
var b = ary.pop();   // => b == 3, ary == [5,2]
// 末尾に追加
ary.push(9);         // => ary == [5,2,9]
// 部分コピーを得る
var c = ary.slice(1, 2);
                     // => c == [2, 9], ary == [5, 2, 9]
// 一部を置き換える
var d= ary.splice(1, 2, "a", "b", "c");
                     // => d == [2, 9], ary == [5, "a", "b", "c"]

連想配列 (のようなもの)

JavaScript には連想配列というものはありませんが,任意のオブジェクト連想配列のように扱うことができます。

// オブジェクトの定義。JSON はこの表記法を基にしている
var a = {a: 123, b: 456};
a['a'];                // => 123 (連想配列風アクセス)
a.b;                   // => 456 (プロパティ風アクセス)
a['c'] = 789;          // 要素の追加
a.d = 123;

以下のような書き方も可能ですが,普通はやりません。

"abc"['length'];       // => 3 (プロパティの取得)
"a,b,c"['split'](/,/); // => ["a", "b", "c"] (メソッド呼び出し)

連想配列風のアクセスプロパティ風のアクセスは表記が異なるだけで意味上の差はありません。

制御文

if文
if (条件) {
  hoge();
  fuga();
}
// if中の文が1つだけの場合は 括弧 ( "{", "}" ) を省略可能
if (条件) 
  hoge();
if-else文
if (条件) {
} else {
}
while文
var i = 0;
while (i < 5) {
i++;
}
for文
for (var i = 0; i < 5; i++) {
}
for in文
var obj = {a: 1, b: 2};
for (var i in obj) {
  alert(obj[i]);  // => 1, 2
}
for each文 (Firefoxのみ対応)
var obj = {a: 1, b: 2};
for each (var v in obj) {
  alert(v);      // => 1, 2
}
Array#forEach (最近のブラウザのみ対応)
["a", "b", "c"].forEach(function(v, i) {
  alert(i + ": " + v);  // => "0: a", "1: b", "2: c"
})

関数

関数は以下のように書きます。

function sum3a(a, b, c) {
  return a + b + c;
}

JavaScript関数ファーストクラスオブジェクトなので,無名の関数を作って変数に代入することもできます。

var sum3b = function(a, b, c) {
  return a + b + c;
};

sum3aはコンパイル時に定義され,sum3bは実行時に定義されます。それ以外の点で両者に差はありません。

Firefox だと以下のような短縮表記が使えます (式クロージャ記法)。

var sum3b = function(a, b, c) a + b + c;

ファイル入出力

そんなものはない (ブラウザ上で動作するJSの場合)

サーバとの通信は行えますが煩雑なので省略 (XMLHttpRequestでググってください)

JavaScriptオブジェクト指向

JavaScriptプロトタイプベースの(純粋?)オブジェクト指向言語です。変数に代入できるものは全てundefinedなどの特殊な値を除きほとんどがオブジェクトです。

オブジェクトの定義
var obj = {a: 123, b: 3};
obj.a;    // => 123
メソッド

メソッドはプロパティ関数を代入したものです。

var man = {
  hello: function() { alert('hello!'); },
  bye: function() { alert('bye'); }
};
man.hello();  // => hello!
クラスのようなもの

関数を作りその prototoype プロパティをいじることでクラスのようなオブジェクトを作ることができます。

// クラス (のようなもの) の定義

// コンストラクタとなる関数
var Man = function(name, age) {
  // プロパティの初期化
  this.name = name;
  this.age = age;
};
// メソッド・プロパティの定義
Man.prototype = {
  sayName: function() { alert("My name is " + this.name + "."); }
}

// インスタンスの作成
var bob = new Man('Bob', 35);
bob.sayName();  // => My name is Bob.
継承

prototype に親クラスオブジェクトを代入することで継承が実現できます。

var Animal = function() {};
Animal.prototype = {
  sleep: function() { alert('zzz...'); }
};

var Human = function() {};
Human.prototype = new Animal();
Human.prototype.workHarder = function() {
  alert("I'm tired...");
  this.sleep();
};

var me = new Human();
me.workHarder();  // => I'm tired... => zzz...

雑多なtips

真偽値

JavaScript では以下の値が false として扱われます。

  • false (Bool型)
  • 0 (実数型)
  • "" (空文字)
  • null, NaN, undefined

これ以外は全て true として扱われます。

==と===

==による比較は自動的に型変換が行われてから比較されるため,意図せぬ結果をもたらす場合があります。

'0' == 0;    // => true
'' == 0;     // => true
'100' == 100 // => true

このような事態を防ぐために,型変換を行わない比較演算子 === を用いるとよいでしょう。

'0' === 0;    // => false
'' === 0;     // => false
'100' === 100 // => false
for in文の落とし穴既存のオブジェクト拡張の落とし穴

for in 文には prototype で定義されたプロパティも列挙してしまうので注意が必要です。

Object.prototype = {doHoge: function() {}};
var obj = {a: 1, b: 2};
for (var i in obj) {
  alert("i = " + obj[i]); // => "a = 1", "b = 2", "doHoge = function() {}"
}

このような挙動を回避するためには, hasOwnProperty を使ってそのオブジェクト自体が持っているプロパティかどうかを確認しましょう。

Object.prototype = {doHoge: function() {}};
var obj = {a: 1, b: 2};
for (var i in obj) {
  if (obj.hasOwnProperty(i))
    alert("i = " + obj[i]); // => "a = 1", "b = 2"
}

既存のオブジェクト,特に全てのオブジェクトプロトタイプである Object.prototype を拡張する場合は効果範囲が大きいので慎重に行ってください。

変数のスコープ

変数のスコープは宣言した関数内全体になります。関数内のどの位置で変数宣言しても,関数内全体からその変数を参照できます。

var a = 0;
function() {
  alert(a);   // => undefined (関数内で後に定義したaを参照するため)
  var a = 1;  // ここで a に値が代入される
  if (true) {
    var b = 1; // ここで定義した b は関数内ならどこからでも参照出来る
  }
  alert(b);   // => 1 (if文の中で宣言した b を参照できる)
}

Firefox だと以下のようにしてスコープがブロック内に限定される変数を宣言することができます。

if (true) {
  let b = 1;
}
alert(b);    // => undefined (letで宣言するとスコープがif文の中に限定される)
thisの指す物

メソッド内で使われている this が指すものは,そのメソッドの呼び出され方によって変わります。具体的には, obj.hoge(); の形で呼び出された場合,thisはobjになり,hoge();の形で呼び出された場合はthisは window (グローバルオブジェクト)になります。

var smith = {
  name: "Smith",
  sayName: function() { alert(this.name); }
};
smith.sayName(); // => Smith
var john = {name: "John"};
john.sayName = smith.sayName; // 関数を代入
john.sayName();  // => John

var sayName = john.sayName;
sayName();      // => undefined (thisがwindowを指すためthis.name == window.name == undefined)
既存のオブジェクトの拡張

既存のオブジェクトprototype をいじることで既存のオブジェクトを拡張できます。

[1, 2, 3].sum(); // => Error (sum is not a function)
Array.prototype.sum = function() {
  var sum = 0;
  for (var i = 0; i < this.length; i++) {
    sum += this[i];
  }
  return sum;
};

[1, 2, 3].sum(); // => 6

JavaScript参考資料

言語リファレンス

MDCのドキュメントがよくまとまっていて使いやすいです。

JavaScriptオブジェクト指向について

自分は以下のページでオブジェクト指向を学びました

os0xos0x 2010/01/31 22:56 ブックマークレットは;で区切れば複数行相当のJavaScriptも実行できますし、どちらかというと文字数の制限のほうが厳しい(特にIE6は500文字ちょっと)かなと思います。
print文はないですが、最近はconsole.logをサポートしているブラウザが増えているのでalertを使うことはかなり減っていると思います。IE6でデバッグしないといけないときくらいでしょうか。
substrはECMAScriptの仕様には含まれていない、非標準なメソッドなので一応注意。まあ普通に使えますし、使えなくなる気配はないので気にするほどのことではないですが。
> 変数に代入できるものは全てオブジェクト
オブジェクトではないもの(undefinedなど)も代入はできるので、代入できる=>オブジェクトだと誤解があるように思います。
真偽値の"0" (文字列型)は偽として扱われませんよ。数値に変換したときは0になるので、0は偽になりますが、javascript:alert(("0")?true:false);はtrueです。
あと、個人的には「for in文の落とし穴」は「既存のオブジェクトの拡張」の落とし穴であって、prototypeは汚さないルールの方が実用的かなと思います。

gifnksmgifnksm 2010/02/01 00:13 >>os0xさん
たくさんのご指摘ありがとうございます。
さっそく本文の方に反映させて頂きました。

いつも Greasemonkey でやりたい放題スタイルな JS ばっかり書いているので,そこから離れた部分のことを書くとどうしても怪しい部分が出てきてしまいますね (標準関数とかprototype汚染とか真偽値とか)

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

おとなり日記