第1章 基本JavaScript
目次
書籍を購入する
(広告です。ブロックしないでください。)

第1章 基本JavaScript

この章では、「基本JavaScript」について説明します。これは、私が選んだJavaScriptのサブセットの名前で、できる限り簡潔でありながら、生産性を維持できるように設計されています。JavaScriptの学習を始めたばかりの方は、言語の残りの部分に進む前に、しばらくの間このサブセットでプログラミングすることをお勧めします。そうすれば、すべてを一度に学ぶ必要がなく、混乱せずに済みます。

背景

このセクションでは、JavaScriptがなぜこのようになっているのかを理解するのに役立つ、JavaScriptの背景について少し説明します。

JavaScriptとECMAScript

ECMAScriptは、JavaScriptの公式名称です。 JavaScript(もともとはSunが、現在はOracleが所有)に商標があるため、新しい名前が必要になりました。 現在、Mozillaは、以前にライセンスを取得したため、公式にJavaScriptという名前を使用することを許可されている数少ない企業の1つです。 一般的な使用法では、これらの規則が適用されます。

  • JavaScriptは、プログラミング言語を意味します。
  • ECMAScriptは、言語仕様で使用される名前です。 したがって、言語のバージョンを参照する際は、常にECMAScriptと言います。 JavaScriptの現在のバージョンはECMAScript 5です。ECMAScript 6は現在開発中です。

言語の影響と性質

JavaScriptの作成者であるBrendan Eichは、非常に迅速に言語を作成せざるを得ませんでした(さもなければ、より悪いテクノロジーがNetscapeに採用されていたでしょう)。彼はいくつかのプログラミング言語から借用しました。Java(構文、プリミティブ値とオブジェクト)、SchemeとAWK(ファーストクラス関数)、Self(プロトタイプ継承)、PerlとPython(文字列、配列、正規表現)です。

JavaScriptには、ECMAScript 3まで例外処理がありませんでした。そのため、言語が非常に頻繁に値を自動的に変換し、非常に頻繁に暗黙のうちに失敗します。最初は例外をスローできなかったからです。

一方、JavaScriptには癖があり、機能がかなり欠落しています(ブロックスコープの変数、モジュール、サブクラス化のサポートなど)。一方、これらの問題を回避できる強力な機能がいくつかあります。他の言語では、言語機能を学習します。JavaScriptでは、代わりにパターンを学習することがよくあります。

その影響を考えると、JavaScriptが関数型プログラミング(高階関数、組み込みのmapreduceなど)とオブジェクト指向プログラミング(オブジェクト、継承)の混合であるプログラミングスタイルを可能にするのも不思議ではありません。

構文

このセクションでは、JavaScriptの基本的な構文原則について説明します。

構文の概要

構文のいくつかの例:

// Two slashes start single-line comments

var x;  // declaring a variable

x = 3 + y;  // assigning a value to the variable `x`

foo(x, y);  // calling function `foo` with parameters `x` and `y`
obj.bar(3);  // calling method `bar` of object `obj`

// A conditional statement
if (x === 0) {  // Is `x` equal to zero?
    x = 123;
}

// Defining function `baz` with parameters `a` and `b`
function baz(a, b) {
    return a + b;
}

等号の2つの異なる使い方に注意してください。:

  • 単一の等号(=)は、変数に値を代入するために使用されます。
  • 三重の等号(===)は、2つの値を比較するために使用されます(等価演算子を参照)。

ステートメントと式

JavaScriptの構文を理解するには、主要な2つの構文カテゴリ、ステートメントと式があることを知っておく必要があります。

  • ステートメントは「何かを実行」します。プログラムは一連のステートメントです。以下は、変数fooを宣言(作成)するステートメントの例です。

    var foo;
  • 式は値を生成します。式は、関数引数、代入の右辺などです。以下は式の例です。

    3 * 7

ステートメントと式の区別は、JavaScriptにif-then-elseを実行する2つの異なる方法があることからもよくわかります。ステートメントとして:

var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

または式として

var x = y >= 0 ? y : -y;

後者は関数引数として使用できます(ただし前者は使用できません)

myFunction(y >= 0 ? y : -y)

最後に、JavaScriptがステートメントを期待する場所であればどこでも、式を使用することもできます。たとえば

foo(7, 1);

行全体がステートメント(いわゆる式ステートメント)ですが、関数呼び出しfoo(7, 1)は式です。

セミコロン

JavaScriptではセミコロンはオプションです。ただし、そうしないとJavaScriptがステートメントの終わりを誤って推測する可能性があるため、常にセミコロンを含めることをお勧めします。詳細については、「自動セミコロン挿入」で説明します。

セミコロンはステートメントを終了しますが、ブロックは終了しません。ブロックの後にセミコロンが表示されるケースが1つあります。関数式はブロックで終わる式です。 そのような式がステートメントの最後に来る場合は、セミコロンが続きます。

// Pattern: var _ = ___;
var x = 3 * 7;
var f = function () { };  // function expr. inside var decl.

コメント

JavaScriptには、1行コメントと複数行コメントの2種類のコメントがあります。1行コメントは//で始まり、行末で終了します。

x++; // single-line comment

複数行コメントは、/**/で区切られます。

/* This is
   a multiline
   comment.
 */

変数と代入

JavaScriptの変数は、使用する前に宣言されます。

var foo;  // declare variable `foo`

代入

変数を宣言し、同時に値を代入できます。

var foo = 6;

既存の変数に値を代入することもできます。

foo = 4;  // change variable `foo`

複合代入演算子

+=などの複合代入演算子があります。次の2つの代入は同等です。

x += 1;
x = x + 1;

識別子と変数名

識別子は、JavaScriptでさまざまな構文上の役割を果たす名前です。 たとえば、変数の名前は識別子です。識別子は大文字と小文字が区別されます。

大まかに言うと、識別子の最初の文字は、任意のUnicode文字、ドル記号($)、またはアンダースコア(_)にすることができます。後続の文字は、さらに任意のUnicode数字にすることができます。したがって、次のものはすべて有効な識別子です。

arg0
_tmp
$elem
π

次の識別子は予約語です。これらは構文の一部であり、変数名(関数名やパラメーター名を含む)として使用することはできません。

arguments

break

case

catch

class

const

continue

debugger

default

delete

do

else

enum

export

extends

false

finally

for

function

if

implements

import

in

instanceof

interface

let

new

null

package

private

protected

public

return

static

super

switch

this

throw

true

try

typeof

var

void

while

次の3つの識別子は予約語ではありませんが、予約語であるかのように扱う必要があります。

Infinity

NaN

undefined

最後に、標準のグローバル変数の名前(第23章を参照)も避ける必要があります。 ローカル変数として使用しても何も壊れませんが、コードは依然として混乱します。

JavaScriptには、プログラミング言語に期待される多くの値があります。ブール値、数値、文字列、配列などです。JavaScriptのすべての値にはプロパティがあります。[1] 各プロパティには、キー(または名前)とがあります。プロパティはレコードのフィールドのように考えることができます。ドット(.)演算子を使用してプロパティを読み取ります。

value.propKey

たとえば、文字列'abc'にはlengthプロパティがあります。

> var str = 'abc';
> str.length
3

前述の例は、次のように記述することもできます。

> 'abc'.length
3

ドット演算子は、プロパティに値を代入するために使用することもできます。

> var obj = {};  // empty object
> obj.foo = 123; // create property `foo`, set it to 123
123
> obj.foo
123

また、メソッドを呼び出すためにも使用できます。

> 'hello'.toUpperCase()
'HELLO'

上記の例では、値'hello'に対してメソッドtoUpperCase()を呼び出しました。

プリミティブ値とオブジェクト

JavaScriptでは、値の間でやや恣意的な区別が行われます。

  • プリミティブ値は、ブール値、数値、文字列、nullundefinedです。
  • その他すべての値はオブジェクトです。

2つの主な違いは、それらがどのように比較されるかです。各オブジェクトには固有の識別子があり、自分自身と(厳密に)等しいだけです。

> var obj1 = {};  // an empty object
> var obj2 = {};  // another empty object
> obj1 === obj2
false
> obj1 === obj1
true

対照的に、同じ値をエンコードするすべてのプリミティブ値は同じと見なされます。

> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true

次の2つのセクションでは、プリミティブ値とオブジェクトについて詳しく説明します。

プリミティブ値

以下は、すべてのプリミティブ値(または略してプリミティブ)です。

  • ブール値:truefalse(「ブール値」を参照)
  • 数値:17361.351(「数値」を参照)
  • 文字列:'abc'"abc"(「文字列」を参照)
  • 2つの「非値」:undefinednull(「undefinedとnull」を参照)

プリミティブには次の特性があります。

値で比較される

「コンテンツ」が比較される

> 3 === 3
true
> 'abc' === 'abc'
true
常にイミュータブル

プロパティを変更、追加、削除することはできません。

> var str = 'abc';

> str.length = 1; // try to change property `length`
> str.length      // ⇒ no effect
3

> str.foo = 3; // try to create property `foo`
> str.foo      // ⇒ no effect, unknown property
undefined

(不明なプロパティを読み取ると、常にundefinedが返されます。)

オブジェクト

すべての非プリミティブ値はオブジェクトです。最も一般的な種類のオブジェクトは次のとおりです。

  • プレーンオブジェクト。これらはオブジェクトリテラルで作成できます(「単一のオブジェクト」を参照)。

    {
        firstName: 'Jane',
        lastName: 'Doe'
    }

    上記のオブジェクトには、2つのプロパティがあります。プロパティfirstNameの値は'Jane'で、プロパティlastNameの値は'Doe'です。

  • 配列。これらは配列リテラルで作成できます(「配列」を参照)。

    [ 'apple', 'banana', 'cherry' ]

    上記の配列には、数値インデックスを使用してアクセスできる3つの要素があります。たとえば、'apple'のインデックスは0です。

  • 正規表現。これらは正規表現リテラルで作成できます(「正規表現」を参照)。

    /^a+b+$/

オブジェクトには次の特性があります。

参照で比較される

識別子が比較される。すべての値には独自の識別子がある

> ({} === {})  // two different empty objects
false

> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
デフォルトではミュータブル

通常は、プロパティを自由に変更、追加、削除できます(「単一のオブジェクト」を参照)。

> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123

undefinedとnull

ほとんどのプログラミング言語には、欠落した情報を表す値があります。 JavaScriptには、そのような「非値」が2つあります。それは、undefinednull です。

  • undefined は「値がない」ことを意味します。初期化されていない変数は undefined です。

    > var foo;
    > foo
    undefined

    欠落したパラメータは undefined です。

    > function f(x) { return x }
    > f()
    undefined

    存在しないプロパティを読み取ると、undefined が返されます。

    > var obj = {}; // empty object
    > obj.foo
    undefined
  • null は「オブジェクトがない」ことを意味します。オブジェクトが期待される場合(パラメータ、オブジェクトの連鎖の最後など)に、非値として使用されます。

警告

undefinednull にはプロパティがなく、toString() などの標準メソッドさえありません。

undefinedまたはnullのチェック

関数は通常、undefined または null のいずれかを介して欠落した値を示すことができます。 明示的なチェックによって同じことができます。

if (x === undefined || x === null) {
    ...
}

また、undefinednull の両方が false と見なされるという事実を利用することもできます。

if (!x) {
    ...
}

警告

false0NaN、および ''false と見なされます(「Truthy and Falsy」を参照)。

typeofとinstanceofを使用した値の分類

値を分類するための演算子が2つあります。typeof は主にプリミティブ値に使用され、instanceof はオブジェクトに使用されます。

typeof は次のようになります。

typeof value

value の「型」を記述する文字列を返します。以下に例をいくつか示します。

> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {} // empty object literal
'object'
> typeof [] // empty array literal
'object'

次の表は、typeof のすべての結果を示しています。

オペランド結果

undefined

'undefined'

null

'object'

真偽値

'boolean'

数値

'number'

文字列値

'string'

関数

'function'

その他すべての通常の値

'object'

(エンジンが作成した値)

JavaScriptエンジンは、typeof が(この表にリストされているすべての結果とは異なる)任意の文字列を返す値を生成することを許可されています。

typeof null'object' を返すのはバグであり、既存のコードを破壊するため、修正できません。これは、null がオブジェクトであることを意味しません。

instanceof は次のようになります。

value instanceof Constr

value がコンストラクタ Constr によって作成されたオブジェクトの場合、true を返します(「コンストラクタ:オブジェクトのファクトリ」を参照)。以下に例をいくつか示します。

> var b = new Bar();  // object created by constructor Bar
> b instanceof Bar
true

> {} instanceof Object
true
> [] instanceof Array
true
> [] instanceof Object  // Array is a subconstructor of Object
true

> undefined instanceof Object
false
> null instanceof Object
false

真偽値

プリミティブな真偽値型 は、truefalse の値で構成されます。次の演算子は真偽値を生成します。

  • 2項論理演算子:&& (And)、|| (Or)
  • 前置論理演算子:! (Not)
  • 比較演算子

    • 等価演算子:===!====!=
    • 順序演算子(文字列および数値用):>>=<<=

TruthyとFalsy

JavaScriptが真偽値を期待する場合(例えば、ifステートメントの条件)、任意の値を使用できます。それはtrueまたはfalseとして解釈されます。次の値はfalseとして解釈されます。

  • undefinednull
  • 真偽値:false
  • 数値:0NaN
  • 文字列:''

その他すべての値(すべてのオブジェクトを含む!)は、trueと見なされます。falseとして解釈される値はfalsyと呼ばれ、trueとして解釈される値はtruthyと呼ばれます。関数として呼び出されるBoolean()は、そのパラメータをブール値に変換します。それを使用して、値がどのように解釈されるかをテストできます。

> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({}) // empty object
true
> Boolean([]) // empty array
true

2項論理演算子

JavaScriptの2項論理演算子はショートサーキットです。つまり、最初のオペランドが結果を決定するのに十分な場合、2番目のオペランドは評価されません。 たとえば、次の式では、関数 foo() は決して呼び出されません。

false && foo()
true  || foo()

さらに、2項論理演算子は、オペランドのいずれかを返します。これは真偽値である場合もそうでない場合もあります。truthyかどうかのチェックは、どちらを返すかを判断するために使用されます。

等価演算子

JavaScriptには、2種類の等価性があります。

  • 通常または「寛容な」不等価:== および !=
  • 厳密な不等価:=== および !==

通常の等価性は、多くの値を等しいと見なします(詳細は、「通常の(寛容な)等価(==、!=)」で説明します)。バグを隠す可能性があるため、常に厳密な等価性を使用することをお勧めします。

数値

JavaScriptのすべての数値は浮動小数点数です。

> 1 === 1.0
true

特殊な数値には、次のものがあります。

NaN(「数値ではない」)

エラー値

> Number('xyz')  // 'xyz' can’t be converted to a number
NaN
Infinity

ほとんどもエラー値です。

> 3 / 0
Infinity
> Math.pow(2, 1024)  // number too large
Infinity

Infinity は、他のどの数値よりも大きい(NaN を除く)。同様に、-Infinity は他のどの数値よりも小さい(NaN を除く)。これにより、これらの数値はデフォルト値として役立ちます(例えば、最小値または最大値を検索する場合)。

演算子

JavaScriptには、次の算術演算子があります(「算術演算子」を参照)。

  • 加算:number1 + number2
  • 減算:number1 - number2
  • 乗算:number1 * number2
  • 除算:number1 / number2
  • 剰余:number1 % number2
  • インクリメント:++variablevariable++
  • デクリメント:--variablevariable--
  • 否定:-value
  • 数値への変換:+value

グローバルオブジェクトMath(「Math」を参照)は、関数を介して、より多くの算術演算を提供します。

JavaScriptには、ビット単位の演算用の演算子もあります(例えば、ビット単位のAnd。「ビット単位演算子」を参照)。

文字列

文字列は、文字列リテラルを介して直接作成できます。 これらのリテラルは、単一引用符または二重引用符で区切られます。バックスラッシュ(\)は文字をエスケープし、いくつかの制御文字を生成します。以下に例をいくつか示します。

'abc'
"abc"

'Did she say "Hello"?'
"Did she say \"Hello\"?"

'That\'s nice!'
"That's nice!"

'Line 1\nLine 2'  // newline
'Backlash: \\'

単一文字は角括弧でアクセスされます。

> var str = 'abc';
> str[1]
'b'

プロパティ length は、文字列の文字数をカウントします。

> 'abc'.length
3

すべてのプリミティブと同様に、文字列は不変です。既存の文字列を変更する場合は、新しい文字列を作成する必要があります。

文字列演算子

文字列は、プラス(+)演算子を介して連結されます。これは、オペランドのいずれかが文字列の場合、他のオペランドを文字列に変換します。

> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'

複数のステップで文字列を連結するには、+= 演算子を使用します。

> var str = '';
> str += 'Multiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Multiple pieces are concatenated.'

文字列メソッド

文字列には多くの便利なメソッドがあります(「String Prototype Methods」を参照)。以下に例をいくつか示します。

> 'abc'.slice(1)  // copy a substring
'bc'
> 'abc'.slice(1, 2)
'b'

> '\t xyz  '.trim()  // trim whitespace
'xyz'

> 'mjölnir'.toUpperCase()
'MJÖLNIR'

> 'abc'.indexOf('b')  // find a string
1
> 'abc'.indexOf('x')
-1

ステートメント

JavaScriptの条件文とループは、次のセクションで紹介します。

条件文

if ステートメントには、then 句があり、オプションの else 句があり、ブール条件に応じて実行されます。

if (myvar === 0) {
    // then
}

if (myvar === 0) {
    // then
} else {
    // else
}

if (myvar === 0) {
    // then
} else if (myvar === 1) {
    // else-if
} else if (myvar === 2) {
    // else-if
} else {
    // else
}

常に中括弧(0個以上のステートメントのブロックを示します)を使用することをお勧めします。 ただし、句が単一のステートメントのみである場合は、そうする必要はありません(制御フロー ステートメント for および while にも同じことが当てはまります)。

if (x < 0) return -x;

以下はswitchステートメントです。fruitの値は、どのcaseが実行されるかを決定します。

switch (fruit) {
    case 'banana':
        // ...
        break;
    case 'apple':
        // ...
        break;
    default:  // all other cases
        // ...
}

case の後の「オペランド」には、任意の式を指定できます。これは switch のパラメータと === で比較されます。

ループ

for ループは、次の形式を持ちます。

for (⟦«init»⟧; ⟦«condition»⟧; ⟦«post_iteration»⟧)
    «statement»

init はループの最初に実行されます。condition は各ループの反復前にチェックされ、false になるとループは終了します。post_iteration は各ループの反復後に実行されます。

この例では、配列 arr のすべての要素をコンソールに出力します。

for (var i=0; i < arr.length; i++) {
    console.log(arr[i]);
}

while ループは、条件が真である間、その本体をループし続けます。

// Same as for loop above:
var i = 0;
while (i < arr.length) {
    console.log(arr[i]);
    i++;
}

do-while ループは、条件が真である間、その本体をループし続けます。条件は本体の後にあるため、本体は常に少なくとも 1 回は実行されます。

do {
    // ...
} while (condition);

すべてのループにおいて:

  • break はループを終了します。
  • continue は新しいループの反復を開始します。

関数

関数を定義する 1 つの方法は、関数宣言 を使用することです。

function add(param1, param2) {
    return param1 + param2;
}

上記のコードは、2 つのパラメータ param1param2 を持ち、両方のパラメータの合計を返す関数 add を定義します。これがその関数の呼び出し方です。

> add(6, 1)
7
> add('a', 'b')
'ab'

add() を定義する別の方法は、関数式 を変数 add に割り当てることです。

var add = function (param1, param2) {
    return param1 + param2;
};

関数式は値を生成するため、関数を他の関数の引数として直接渡すために使用できます。

someOtherFunction(function (p1, p2) { ... });

関数宣言は巻き上げられる

関数宣言は巻き上げ られます。つまり、現在のスコープの先頭に全体が移動されます。これにより、後で宣言された関数を参照できます。

function foo() {
    bar();  // OK, bar is hoisted
    function bar() {
        ...
    }
}

var 宣言も巻き上げられますが(「変数は巻き上げられる」を参照)、それらによる割り当ては巻き上げられないことに注意してください。

function foo() {
    bar();  // Not OK, bar is still undefined
    var bar = function () {
        // ...
    };
}

特殊変数 arguments

JavaScript では、任意の数の引数で任意の関数を呼び出すことができます。言語は決して文句を言いません。ただし、特殊変数 arguments を介してすべてのパラメータを利用できるようにします。arguments は配列のように見えますが、配列メソッドは一切持っていません。

> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0]  // read element at index 0
'a'

引数が多すぎる場合、または少なすぎる場合

JavaScript でパラメータが多すぎる場合、または少なすぎる場合にどのように処理されるかを探るために、次の関数を使用してみましょう(関数 toArray() は「arguments を配列に変換する」に示されています)。

function f(x, y) {
    console.log(x, y);
    return toArray(arguments);
}

追加のパラメータは(arguments を除いて)無視されます。

> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]

不足しているパラメータはundefined を取得します。

> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]

オプションのパラメータ

以下は、パラメータにデフォルト値を割り当てるための一般的なパターンです。

function pair(x, y) {
    x = x || 0;  // (1)
    y = y || 0;
    return [ x, y ];
}

(1)行目では、|| 演算子は、x が truthy (nullundefined などではない) であれば x を返します。それ以外の場合は、2 番目のオペランドを返します。

> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]

引数の数を強制する

もし引数の数(特定の数のパラメータ)を強制したい場合は、arguments.length を確認できます。

function pair(x, y) {
    if (arguments.length !== 2) {
        throw new Error('Need exactly 2 arguments');
    }
    ...
}

例外処理

例外を処理する最も一般的な方法は(第 14 章」を参照)次のとおりです。

function getPerson(id) {
    if (id < 0) {
        throw new Error('ID must not be negative: '+id);
    }
    return { id: id }; // normally: retrieved from database
}

function getPersons(ids) {
    var result = [];
    ids.forEach(function (id) {
        try {
            var person = getPerson(id);
            result.push(person);
        } catch (exception) {
            console.log(exception);
        }
    });
    return result;
}

try 句は重要なコードを囲み、catch 句は try 句の中で例外がスローされた場合に実行されます。上記のコードを使用すると:

> getPersons([2, -5, 137])
[Error: ID must not be negative: -5]
[ { id: 2 }, { id: 137 } ]

厳格モード

厳格モード(「厳格モード」を参照)では、より多くの警告が有効になり、JavaScript がよりクリーンな言語になります(非厳格モードは「ずさんモード」と呼ばれることがあります)。それを有効にするには、JavaScript ファイルまたは <script> タグの最初に次の行を入力します。

'use strict';

関数ごとに厳格モードを有効にすることもできます。

function functionInStrictMode() {
    'use strict';
}

変数スコープとクロージャ

JavaScript では、変数を使用する前に var で宣言します。

> var x;
> x
undefined
> y
ReferenceError: y is not defined

1 つの var ステートメントで複数の変数を宣言および初期化できます。

var x = 1, y = 2, z = 3;

ただし、変数ごとに 1 つのステートメントを使用することをお勧めします(理由は構文」で説明されています)。したがって、上記のステートメントを次のように書き直します。

var x = 1;
var y = 2;
var z = 3;

巻き上げのため(「変数は巻き上げられる」を参照)、通常は関数の最初に変数を宣言するのが最善です。

変数は関数スコープを持つ

変数のスコープは常に、現在のブロックとは対照的に、完全な関数です。例:

function foo() {
    var x = -512;
    if (x < 0) {  // (1)
        var tmp = -x;
        ...
    }
    console.log(tmp);  // 512
}

変数 tmp が (1) 行目から始まるブロックに限定されず、関数の最後まで存在することがわかります。

変数は巻き上げられる

各変数宣言は巻き上げ られます。つまり、宣言は関数の先頭に移動されますが、そこで行われる割り当てはそのまま残ります。例として、次の関数の (1) 行目の変数宣言について考えてみましょう。

function foo() {
    console.log(tmp); // undefined
    if (false) {
        var tmp = 3;  // (1)
    }
}

内部的には、上記の関数は次のように実行されます。

function foo() {
    var tmp;  // hoisted declaration
    console.log(tmp);
    if (false) {
        tmp = 3;  // assignment stays put
    }
}

クロージャ

各関数は、作成されたスコープから離れた後でも、その周囲の関数の変数への接続を維持します。例:

function createIncrementor(start) {
    return function () {  // (1)
        start++;
        return start;
    }
}

(1)行目から始まる関数は、作成されたコンテキストから離れますが、start のライブバージョンへの接続を維持します。

> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8

クロージャ は、関数とその周囲のスコープの変数への接続の組み合わせです。したがって、createIncrementor() が返すものはクロージャです。

IIFE パターン:新しいスコープの導入

グローバルにならないように変数を防ぐなど、新しい変数スコープを導入したい場合があります。JavaScript では、ブロックを使用してこれを行うことはできません。関数を使用する必要があります。ただし、ブロックのような方法で関数を使用するためのパターンがあります。これは IIFE (即時実行関数式、発音は「イフィー」) と呼ばれます。

(function () {  // open IIFE
    var tmp = ...;  // not a global variable
}());  // close IIFE

上記の例は、コメントを除いて、正確に示されているとおりに入力してください。IIFE は、定義した直後に呼び出される関数式です。関数内には、新しいスコープが存在し、tmp がグローバルになるのを防ぎます。IIFE の詳細については、「IIFE を介した新しいスコープの導入」を参照してください。

IIFE のユースケース:クロージャを介した意図しない共有

クロージャは、外側の変数への接続を維持しますが、これは場合によっては望ましくないものです。

var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)

(1) 行目で返される値は常に i の現在の値であり、関数が作成されたときの値ではありません。ループが完了すると、i は値 5 を持ち、これが配列内のすべての関数がその値を返す理由です。(1) 行目の関数に i の現在の値のスナップショットを受け取らせたい場合は、IIFE を使用できます。

for (var i=0; i < 5; i++) {
    (function () {
        var i2 = i; // copy current i
        result.push(function () { return i2 });
    }());
}

オブジェクトとコンストラクタ

このセクションでは、JavaScript の 2 つの基本的なオブジェクト指向メカニズムである、単一のオブジェクトと、(他の言語のクラスに似たオブジェクトのファクトリである)コンストラクタ について説明します。

単一オブジェクト

すべての値と同様に、オブジェクトにはプロパティがあります。実際、オブジェクトをプロパティの集合と考えることができます。各プロパティは (キー、値) のペアです。キーは文字列であり、値は任意の JavaScript 値です。

JavaScript では、オブジェクトリテラル を使用してプレーンオブジェクトを直接作成できます。

'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};

上記のオブジェクトには、プロパティ namedescribe があります。プロパティの読み取り(「取得」)と書き込み(「設定」)ができます。

> jane.name  // get
'Jane'
> jane.name = 'John';  // set
> jane.newProperty = 'abc';  // property created automatically

describe のような関数値のプロパティは、メソッド と呼ばれます。メソッドは、それらを呼び出すために使用されたオブジェクトを参照するために this を使用します。

> jane.describe()  // call method
'Person named John'
> jane.name = 'Jane';
> jane.describe()
'Person named Jane'

in 演算子は、プロパティが存在するかどうかを確認します。

> 'newProperty' in jane
true
> 'foo' in jane
false

存在しないプロパティを読み取ると、値 undefined が得られます。したがって、前の 2 つのチェックは、次のようにも実行できます。[2]

> jane.newProperty !== undefined
true
> jane.foo !== undefined
false

delete 演算子はプロパティを削除します。

> delete jane.newProperty
true
> 'newProperty' in jane
false

任意のプロパティキー

プロパティキーには任意の文字列を指定できます。これまで、オブジェクトリテラルとドット演算子の後のプロパティキーを見てきました。ただし、それらが識別子である場合にのみ、そのように使用できます(「識別子と変数名」を参照)。他の文字列をキーとして使用する場合は、オブジェクトリテラルでそれらを引用符で囲み、角括弧を使用してプロパティを取得および設定する必要があります。

> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> obj['not an identifier'] = 456;

角括弧を使用すると、プロパティのキーを計算することもできます。

> var obj = { hello: 'world' };
> var x = 'hello';

> obj[x]
'world'
> obj['hel'+'lo']
'world'

メソッドの抽出

メソッドを抽出すると、オブジェクトとの接続が失われます。関数自体はもうメソッドではなく、this の値は (厳格モードでは) undefined になります。

例として、前のオブジェクト jane に戻りましょう。

'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};

jane からメソッド describe を抽出し、それを変数 func に入れて呼び出したいとします。ただし、これはうまくいきません。

> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undefined

解決策は、すべての関数が持つメソッド bind() を使用することです。これは、this が常に指定された値を持つ新しい関数を作成します。

> var func2 = jane.describe.bind(jane);
> func2()
'Person named Jane'

メソッド内の関数

すべての関数は、独自の特別な変数 this を持っています。メソッド内に関数をネストすると、関数の内部からメソッドの this にアクセスできなくなるため、これは不便です。以下は、配列を反復処理するために関数で forEach を呼び出す例です。

var jane = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    logHiToFriends: function () {
        'use strict';
        this.friends.forEach(function (friend) {
            // `this` is undefined here
            console.log(this.name+' says hi to '+friend);
        });
    }
}

logHiToFriends を呼び出すとエラーが発生します。

> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined

これを修正する2つの方法を見てみましょう。まず、this を別の変数に保存できます。

logHiToFriends: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' says hi to '+friend);
    });
}

または、forEach には this の値を指定できる2番目のパラメータがあります。

logHiToFriends: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' says hi to '+friend);
    }, this);
}

関数式は、JavaScript の関数呼び出しで引数としてよく使用されます。これらの関数式の中から this を参照する場合は、常に注意してください。

コンストラクタ:オブジェクトのファクトリ

これまで、JavaScript オブジェクトは、他の言語のマップ/辞書リテラルのように見える JavaScript のオブジェクトリテラルが示すように、文字列から値への単なるマップであると考えていたかもしれません。しかし、JavaScript オブジェクトは、真にオブジェクト指向な機能である継承もサポートしています。このセクションでは、JavaScript の継承がどのように機能するかを完全に説明するわけではありませんが、入門するための簡単なパターンを示します。詳細を知りたい場合は、第17章を参照してください。

関数は、「本物の」関数およびメソッドであることに加えて、JavaScript では別の役割を果たします。それは、new 演算子を介して呼び出された場合、コンストラクタ、つまりオブジェクトのファクトリになることです。したがって、コンストラクタは他の言語のクラスに大まかに似たものです。慣例として、コンストラクタの名前は大文字で始まります。例:

// Set up instance data
function Point(x, y) {
    this.x = x;
    this.y = y;
}
// Methods
Point.prototype.dist = function () {
    return Math.sqrt(this.x*this.x + this.y*this.y);
};

コンストラクタには2つの部分があることがわかります。まず、関数 Point はインスタンスデータを設定します。次に、プロパティ Point.prototype にはメソッドを持つオブジェクトが含まれます。前者のデータは各インスタンスに固有であり、後者のデータはすべてのインスタンスで共有されます。

Point を使用するには、new 演算子を介して呼び出します。

> var p = new Point(3, 5);
> p.x
3
> p.dist()
5.830951894845301

pPoint のインスタンスです。

> p instanceof Point
true

配列

配列は、ゼロから始まる整数インデックスを介してアクセスできる要素のシーケンスです。

配列リテラル

配列リテラルは、配列を作成するのに便利です。

> var arr = [ 'a', 'b', 'c' ];

前の配列には、文字列 'a''b'、および 'c' の3つの要素があります。これらは整数インデックスを介してアクセスできます。

> arr[0]
'a'
> arr[0] = 'x';
> arr
[ 'x', 'b', 'c' ]

length プロパティは、配列に含まれる要素の数を示します。これを使用して要素を追加したり、要素を削除したりできます。

> var arr = ['a', 'b'];
> arr.length
2

> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3

> arr.length = 1;
> arr
[ 'a' ]

in 演算子は配列にも機能します。

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr // is there an element at index 1?
true
> 5 in arr // is there an element at index 5?
false

配列はオブジェクトであり、したがってオブジェクトプロパティを持つことができることに注意してください。

> var arr = [];
> arr.foo = 123;
> arr.foo
123

配列メソッド

配列には多くのメソッドがあります(配列プロトタイプメソッドを参照)。以下にいくつかの例を示します。

> var arr = [ 'a', 'b', 'c' ];

> arr.slice(1, 2)  // copy elements
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]

> arr.push('x')  // append an element
4
> arr
[ 'a', 'b', 'c', 'x' ]

> arr.pop()  // remove last element
'x'
> arr
[ 'a', 'b', 'c' ]

> arr.shift()  // remove first element
'a'
> arr
[ 'b', 'c' ]

> arr.unshift('x')  // prepend an element
3
> arr
[ 'x', 'b', 'c' ]

> arr.indexOf('b')  // find the index of an element
1
> arr.indexOf('y')
-1

> arr.join('-')  // all elements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'

配列の反復処理

要素を反復処理するための配列メソッドがいくつかあります(反復処理(非破壊的)を参照)。最も重要な2つは forEachmap です。

forEach は配列を反復処理し、現在の要素とそのインデックスを関数に渡します。

[ 'a', 'b', 'c' ].forEach(
    function (elem, index) {  // (1)
        console.log(index + '. ' + elem);
    });

上記のコードは次の出力を生成します。

0. a
1. b
2. c

(1)行の関数は、引数を自由に無視できることに注意してください。たとえば、パラメータ elem のみを持つことができます。

map は、既存の配列の各要素に関数を適用して、新しい配列を作成します。

> [1,2,3].map(function (x) { return x*x })
[ 1, 4, 9 ]

正規表現

JavaScript には正規表現の組み込みサポートがあります(第19章では、チュートリアルを参照し、それらがどのように機能するかを詳しく説明しています)。それらはスラッシュで区切られます。

/^abc$/
/[A-Za-z0-9]+/

メソッド test():一致があるか?

> /^a+b+$/.test('aaab')
true
> /^a+b+$/.test('aaa')
false

Math

Math (第21章を参照) は、算術関数を持つオブジェクトです。以下にいくつかの例を示します。

> Math.abs(-2)
2

> Math.pow(3, 2)  // 3 to the power of 2
9

> Math.max(2, -1, 5)
5

> Math.round(1.9)
2

> Math.PI  // pre-defined constant for π
3.141592653589793

> Math.cos(Math.PI)  // compute the cosine for 180°
-1

標準ライブラリのその他の機能

JavaScript の標準ライブラリは比較的質素ですが、他にも使用できるものがたくさんあります。

Date (第20章)
日付の主な機能が、日付文字列の解析と作成、および日付のコンポーネント(年、時など)へのアクセスである日付のコンストラクタ。
JSON (第22章)
JSON データを解析および生成するための関数を持つオブジェクト。
console.* メソッド (コンソールAPIを参照)
これらのブラウザ固有のメソッドは、言語の一部ではありませんが、一部は Node.js でも動作します。


[1] 2つの「非値」である undefined および null にはプロパティがありません。

[2] 注意:このチェックでは、存在するが値が undefined であるプロパティが存在しないと報告されます。

次へ:II. 背景