前書き

前書き

JavaScript Garden はJavaScriptというプログラム言語の一番奇妙な部分についてのドキュメント集です。 このドキュメントはJavaScriptという言語に慣れていないプログラマーがこの言語について深く知ろうとする際に遭遇する、良くある間違い・小さなバグ・パフォーマンスの問題・悪い習慣などを避ける為のアドバイスを与えます。

JavaScript GardenはJavaScriptを教える事を目的にしていません。このガイドの項目を理解する為には、この言語に対する前提知識がある事を推奨します。この言語の基礎部分についてはMozilla Developer Networkのガイド がオススメです。

著者

このガイドは愛すべきStack Overflowの2人のユーザーIvo Wetzel (執筆)とZhang Yi Jiang (デザイン)によって作られました。

貢献者

ホスティング

JavaScript GardenはGitHubでホスティングされていますが、Cramer DevelopmentJavaScriptGarden.infoというミラーサイトを作ってくれています。

ライセンス

JavaScript GardenはMIT licenseの下で公開されており、GitHubでホスティングされています。もしもエラーやtypoを見つけたらfile an issueに登録するかリポジトリにプルリクエストを送ってください。 またStack OverflowチャットのJavaScript roomに私達はいます。

オブジェクト

オブジェクトの使用法とプロパティ

JavaScriptの全ての要素は2つの例外を除いて、オブジェクトのように振る舞います。 その2つとはnullundefinedです。

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

良くありがちな誤解として、数値リテラルがオブジェクトとして使用できないというものがあります。この理由としては、JavaScriptパーサーが浮動小数点のドットをドット記法として解釈しようとしてしまうからです。

2.toString(); // シンタックスエラーが発生する

数値リテラルをオブジェクトとして使用する為の回避策がいくつかあります。

2..toString(); // 2つ目のドットが正しく解釈される
2 .toString(); // ドットの左隣のスペースがポイント
(2).toString(); // 2が一番最初に評価される

オブジェクトはデータタイプ

JavaScriptのオブジェクトはハッシュマップとしても使用されます。これは名前付きのプロパティと値として構成されています。

オブジェクトリテラル({}記法)を使用すると、オブジェクトそのものを作る事ができます。この方法で作られたオブジェクトはObject.prototypeから継承され、own propertiesが何も設定されてない状態になります。

var foo = {}; // 新しい空のオブジェクト

// 12という値の'test'というプロパティを持った新しいオブジェクト
var bar = {test: 12}; 

プロパティへのアクセス

オブジェクトのプロパティには2通りのアクセス方法があります。1つはドット記法によるアクセス、もう1つはブラケット記法です。

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // シンタックスエラー
foo['1234']; // 動作する

どちらの記法も働きとしての違いは無いですが、唯一の違いとしてブラケット記法は通常のプロパティ名と同様に動的にプロパティを設定する事ができます。これ以外で動的にプロパティを設定しようとするとシンタックスエラーになります。

プロパティの削除

実際にオブジェクトからプロパティを削除する唯一の方法はdelete演算子を使う事です。プロパティにundefinednullをセットしても、プロパティ自身ではなく、キーに設定されたを削除するだけです。

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

上記の例では、bazは完全に削除されて出力がされていませんが、それ以外の2つbar undefinedfoo nullはどちらも出力されてしまっています。

キーの記法

var test = {
    'case': 'I am a keyword so I must be notated as a string',
    delete: 'I am a keyword too so me' // シンタックスエラーが起こる
};

オブジェクトのプロパティは普通の文字か文字列として記述する事が出来ます。JavaScriptパーサーの設計ミスが原因ですが、ECMAScript5以前では上記のコードはシンタックスエラーを表示するでしょう。

このエラーはdelete予約語になっているのが原因なので、古いJavaScriptエンジンに正しく解釈させる為には文字リテラルを使って記述する事を推奨します。

プロトタイプ

JavaScriptはクラスベース継承モデルは実装されておらず、この代わりにプロトタイプを用いています。

プロトタイプモデルを使っている事が、JavaScriptの弱点の一つになっていると良く考えられがちですが、プロトタイプ継承モデルはクラスベース継承モデルよりパワフルだというのは事実です。この事はちょっとしたものでもクラスベースの継承で実装しようとすると、プロトタイプベースの継承よりも作業が難しくなるという事でも分かります。

JavaScriptはプロトタイプベースが採用されている唯一の広範に使用されている基本的なプログラミング言語という現実があるので、プロトタイプベースとクラスベースの違いを時々調整しないとなりません。

最初の大きな違いはJavaScriptの継承はプロトタイプチェーンと呼ばれるもので実行されているという事です。

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// BarのプロトタイプをFooの新しいインスタンスとしてセットする
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// Barを実際のコンストラクタとして確実にする為に代入する
Bar.prototype.constructor = Bar;

var test = new Bar() // 新しくbarインスタンスを作成

// プロトタイプチェーンの結果
test [instance of Bar]
    Bar.prototype [instance of Foo] 
        { foo: 'Hello World', value: 42 }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* その他 */ }

上記ではtestBar.prototypeFoo.prototypeの2つのオブジェクトより継承されます。その為Fooの中で設定されたmethod関数にアクセスできるようになります。また、Fooのプロトタイプとしてのインスタンスそれ自体valueプロパティにもアクセスが可能です。new Bar()Fooのインスタンスを新しく作りませんが、プロトタイプに割り合てられたFooインスタンスを再利用している事は注目に値します。従って全てのBarインスタンスは同じvalueプロパティを共有します。

プロパティ探索

オブジェクトのプロパティにアクセスする時には、JavaScriptはプロトタイプチェーンを要求された名前を見つけるまで遡って探索します。

チェーンの先頭(すなわちObject.prototype)に到達した際に、まだ指定されたプロパティが見つからなければ、代わりにundefinedという値を返します。

プロトタイププロパティ

プロトタイププロパティはJavaScriptの中でプロトタイプチェーンを構築する為に使われていますが、任意の値を代入する事も可能になっています。しかし、プロトタイプとしてプリミティブが代入された場合は単に無視されるだけです。

function Foo() {}
Foo.prototype = 1; // 効果無し

オブジェクトの代入は上記の例のように動作し、動的にプロトタイプチェーンを作る事ができます。

パフォーマンス

プロトタイプチェーンの上位にあるプロパティを探索する時間はコードの実行パフォーマンスに重大な悪影響を与えます。特に存在しないプロパティにアクセスしようとすると、プロトタイプチェーンの全てのプロパティを探索してしまいます。

また、オブジェクトのプロパティに対して反復処理をすると、プロトタイプチェーン上の全てのプロパティを列挙してしまいます。

既存のプロトタイプの拡張

元々組み込まれてるプロトタイプやObject.prototypeを拡張するのは、良くありがちなイケていない実装方法になります。

このテクニックはmonkey patchingと呼ばれるものでカプセル化を壊してしまいます。このテクニックはPrototypeのようなフレームワークにより広まりましたが、非標準の機能を持っている組み込み型のオブジェクトの乱立という点でも推奨されません。

唯一組み込みのプロトタイプを拡張しても良い理由としては、JavaScriptエンジンに将来実装されるであろう機能の移植だけです。 例えばArray.forEachなどが、それに当たります。

終わりに

ここまでがプロトタイプベース継承モデルを使って複雑なコードを書く前に必ず理解すべき事です。また、プロパティチェーンの長さを観察して、もしパフォーマンスに悪影響を及ぼすのを防ぐ為ならば、これを分割をしなければなりません。さらに組み込みのプロトタイプは新しいJavaScriptの機能と互換性が無い限りは絶対に拡張してはいけません。

hasOwnProperty

オブジェクトは自分自身自分以外のどちらで定義されたプロパティかをprototype chainのどこかでチェックしなくてはなりません。これはObject.prototypeから継承される全てのオブジェクトのhasOwnPropertyメソッドを使う必要があります。

hasOwnPropertyはJavaScriptで唯一プロトタイプチェーン内を遡らずにプロパティを扱う事が出来ます。

// Object.prototype汚染
Object.prototype.bar = 1; 
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

hasOwnPropertyだけが、正しく期待した結果を出すでしょう。これはあらゆるオブジェクトのプロパティの繰り返し処理をする時必須の事です。オブジェクト自身に定義されておらず、プロトタイプチェーンのどこかには定義されているというプロパティを除外する手段が他にありません

プロパティとしてのhasOwnProperty

JavaScriptはプロパティ名としてhasOwnPropertyを保護していません。;従って、この名前のプロパティを持ったオブジェクトが存在する事がありえます。正しい結果を得る為には外部hasOwnPropertyを使う必要があります。

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 常にfalseを返す

// 他のオブジェクトのhasOwnPropertyを使い、fooの'this'にセットして呼び出す
({}).hasOwnProperty.call(foo, 'bar'); // true

終わりに

オブジェクトのプロパティの存在判定をする時は、hasOwnProperty唯一のメソッドになります。 また、全てfor in ループ内でhasOwnPropertyを使う事を推奨します。 そうする事により組み込みのprototypesの拡張が原因のエラーを避ける事が出来ます。

for inループ

inオペレーターは単に、for inループの中でオブジェクトのプロパティをプロトタイプチェーンの中で繰り返し遡る為にあるものです。

// Object.prototype汚染
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // barとmooが両方とも表示される
}

for inループそれ自体の動作を変更する事は不可能ですが、ループ内にある要らないプロパティをフィルタリングする必要があります。そんな時はObject.prototypehasOwnPropertyメソッドを使うと解決します。

hasOwnPropertyをフィルタリングに使用する

// 継承されているfoo
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

このループの唯一正しい使い方がこの方法です。hasOwnPropertyを使用しているので、 mooのみが表示されるようになります。hasOwnPropertyが省略されている場合は、このコードは 組み込みのプロトタイプが存在する場合に(特にObject.prototypeが拡張されている場合)エラーを発生しやすくなります。

一般に広く使用されているJavaScriptフレームワークとしてPrototypeが挙げられます。このフレームワークには、 for in 内でhasOwnPropertyが使用されプロトタプチェーン内を頭まで遡るのを中断する事が保証されています。

終わりに

常にhasOwnPropertyを使用する事を推奨します。コードの実行環境や、組み込みのプロトタイプが拡張されているかどうかを仮定して書くようなコードを絶対書いてはいけません。

関数

関数の宣言と式

関数はJavaScriptの第一級オブジェクトです。この事は、その他の値と同じように渡す事が出来るという事です。この機能で良く使われる一つとして匿名関数を他のオブジェクトにコールバックとして渡すというものがあり、これで非同期での実装が可能になります。

関数宣言

function foo() {}

上記の関数はプログラムの開始時の前に評価されるように巻き上げられます。従って定義されたスコープ内のどこでも使用する事が可能になります。ソース内での実際の定義が呼ばれる前でもです。

foo(); // このコードが動作する前にfooが作られているので、ちゃんと動作する
function foo() {}

関数

var foo = function() {};

この例では、fooという変数に無名で匿名の関数が割り当てられています。

foo; // 'undefined'
foo(); // これはTypeErrorが起こる
var foo = function() {};

varは宣言である為に、変数名fooがコードが開始される実際の評価時より前のタイミングにまで巻き上げられています。fooは既にスクリプトが評価される時には定義されているのです。

しかし、コードの実行時にのみこの割り当てがされるため、fooという変数は対応するコードが実行される前にデフォルト値であるundefinedが代入されるのです。

名前付き関数式

他に特殊なケースとして、名前付き関数があります。

var foo = function bar() {
    bar(); // 動作する
}
bar(); // ReferenceError

この場合のbarfooに対して関数を割り当てるだけなので、外部スコープでは使用できません。しかし、barは内部では使用できます。これはJavaScriptの名前解決の方法によるもので、関数名はいつも関数自身のローカルスコープ内で有効になっています。

thisはどのように動作するのか

JavaScriptのthisと名付けられた特殊なキーワードは他のプログラム言語と違うコンセプトを持っています。JavaScriptのthisは正確に5個の別々の使い道が存在しています。

グローバルスコープとして

this;

thisをグローバルスコープ内で使用すると、単純にグローバルオブジェクトを参照するようになります。

関数呼び出しとして

foo();

このthisは、再度グローバルオブジェクトを参照しています。

メソッド呼び出しとして

test.foo(); 

この例ではthistestを参照します。

コンストラクター呼び出し

new foo(); 

newキーワードが付いた関数呼び出しはコンストラクターとして機能します。関数内部ではthis新規に作成されたObjectを参照します。

thisの明示的な設定

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // 配列は下記で展開される
foo.call(bar, 1, 2, 3); // 結果はa = 1, b = 2, c = 3

Function.prototypecallapplyメソッドを使用した時には、呼び出された関数の内部でのthisの値は、対応する関数呼び出しの最初の引数に明示的に設定されます。

結果として、上記の例ではメソッドケースが適用されずfooの内部のthisbarに設定されます。

良くある落し穴

これらのケースのほとんどは理にかなったものですが、最初のケースは実際に利用されることが絶対にないので、間違った言語設計だとみなせるでしょう。

Foo.method = function() {
    function test() {
        // このファンクションはグローバルオブジェクトに設定される
    }
    test();
};

良くある誤解としてtestの中のthisFooを参照しているというものがありますが、そのような事実は一切ありません。

testの中のFooにアクセスする為には、Fooを参照するmethodのローカル変数を作る必要があります。

Foo.method = function() {
    var that = this;
    function test() {
        // ここでthisの代わりに使用する
    }
    test();
};

thatは通常の変数名ですが、外部のthisの参照の為に良く使われます。クロージャと組み合わせる事でthisの値を渡す事ができるようになります。

メソッドの割り当て

JavaScriptを使用する上で、もう一つ動かないものが関数のエイリアスです。これは変数へメソッドを割り当てする事です。

var test = someObject.methodTest;
test();

最初のケースのtestは通常の関数呼び出しになる為に、この中のthisは、もはやsomeobjectを参照できなくなってしまいます。

thisの遅延バインディングは最初見た時にはダメなアイデアに見えますが、プロトタイプ継承により、きちんと動作します。

function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method();

methodBarのインスタンスにより呼び出された時に、thisはまさにそのインスタンスを参照するようになります。

クロージャと参照

JavaScriptの一番パワフルな特徴の一つとしてクロージャが使える事が挙げられます。これはスコープが自身の定義されている外側のスコープにいつでもアクセスできるという事です。JavaScriptの唯一のスコープは関数スコープですが、全ての関数は標準でクロージャとして振る舞います。

プライベート変数をエミュレートする

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

ここでCounter2つのクロージャを返します。関数incrementと同じく関数getです。これら両方の関数はCounterのスコープを参照し続けます。その為、そのスコープ内に定義されているcount変数に対していつもアクセスできるようになっています。

なぜプライベート変数が動作するのか?

JavaScriptでは、スコープ自体を参照・代入する事が出来無い為に、外部から変数countにアクセスする手段がありません。唯一の手段は、2つのクロージャを介してアクセスする方法だけです。

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

上記のコードはCounterのスコープ中にある変数countの値を変更する事はありませんfoo.hackそのスコープで定義されていないからです。これは(Counter内の変数countの変更)の代わりにグローバル変数countの作成 -または上書き- する事になります。

ループ中のクロージャ

一つ良くある間違いとして、ループのインデックス変数をコピーしようとしてか、ループの中でクロージャを使用してしまうというものがあります。

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

上記の例では0から9の数値が出力される事はありません。もっと簡単に10という数字が10回出力されるだけです。

匿名関数はiへの参照を維持しており、同時にforループは既にiの値に10をセットし終ったconsole.logが呼ばれてしまいます。

期待した動作をする為には、iの値のコピーを作る必要があります。

参照問題を回避するには

ループのインデックス変数をコピーする為には、匿名ラッパーを使うのがベストです。

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

外部の匿名関数はiを即座に第一引数として呼び出し、引数eiのコピーとして受け取ります。

eを参照しているsetTimeoutを受け取った匿名関数はループによって値が変わる事がありません。

他にこのような事を実現する方法があります。それは匿名ラッパーから関数を返してあげる事です。これは上記のコードと同じ振る舞いをします。

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

オブジェクトのarguments

JavaScriptの全ての関数スコープはargumentsと呼ばれる特別な変数にアクセスできます。この変数は関数が受け取った全ての引数を保持する変数です。

argumentsオブジェクトはArrayではありません。これは配列と同じような -lengthプロパティと名付けられています- 文法を持っていますが、Array.prototypeを継承している訳では無いので、実際Objectになります。

この為、argumentspushpopsliceといった通常の配列メソッドは使用する事が出来ません。プレーンなforループのような繰り返しでは上手く動作しますが、通常のArrayメソッドを使いたい場合は本当のArrayに変換しなければなりません。

配列への変換

下のコードはargumentsオブジェクトの全ての要素を含んだ新しいArrayを返します。

Array.prototype.slice.call(arguments);

この変換は遅いです。コードのパフォーマンスに関わる重要な部分での使用は推奨しません

引き数の受け渡し

下記の例はある関数から別の関数に引数を引き渡す際に推奨される方法です。

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // do stuff here
}

他のテクニックとして、高速で非結合のラッパーとしてcallapply両方を一緒に使用するという物があります。

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// "メソッド"の非結合バージョンを作成する
// このメソッドはthis, arg1, arg2...argNのパラメーターを持っている
Foo.method = function() {

    // 結果: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

仮パラメーターと引数のインデックス

argumentsオブジェクトはゲッターセッター機能を自身のプロパティと同様に関数の仮パラメーターとして作成します。

結果として、仮パラメーターを変更するとargumentsの対応する値も変更されますし、逆もしかりです。

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

パフォーマンスの神話と真実

argumentsオブジェクトは、関数の内部の名前宣言と仮パラメーターという2つの例外を常に持ちながら生成されます。これは、使用されているかどうかは関係がありません。

ゲッターセッターは両方とも常に生成されます。その為これを使用してもパフォーマンスに影響は全くといって言い程ありません。argumentsオブジェクトのパラメーターに単純にアクセスしているような、実際のコードであれば尚更です。

しかし、一つだけモダンJavaScriptエンジンにおいて劇的にパフォーマンスが低下するケースがあります。そのケースとはarguments.calleeを使用した場合です。

function foo() {
    arguments.callee; // この関数オブジェクトで何かする
    arguments.callee.caller; // そして関数オブジェクトを呼び出す
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // 通常はインライン展開する
    }
}

上記のコードでは、fooは自身と自身の呼び出し元の両方を知らないとインライン展開の対象になる事が出来ません。この事は、インライン展開によるパフォーマンスの向上の機会を失くす事になり、また、特定のコンテクストの呼び出しに依存する関数のせいで、カプセル化が解除されてしまいます。

この為にarguments.calleeを使用または、そのプロパティを決して使用しない事を強く推奨します。

コンストラクタ

JavaScriptのコンストラクタは色々ある他のプログラム言語とは一味違います。newキーワードが付いているどんな関数呼び出しも、コンストラクタとして機能します。

コンストラクタ内部では -呼び出された関数の事です- thisの値は新規に生成されたObjectを参照しています。この新規のオブジェクトのprototypeは、コンストラクタとして起動した関数オブジェクトのprototypeに設定されています。

もし呼び出された関数が、returnステートメントを明示していない場合は、暗黙の了解でthisの値を -新規のオブジェクトとして- 返します。

function Foo() {
    this.bla = 1;
}

Foo.prototype.test = function() {
    console.log(this.bla);
};

var test = new Foo();

上記は、Fooをコンストラクタとして呼び出し、新規に生成されたオブジェクトのprototypeFoo.prototypeに設定しています。

明示的にreturnステートメントがある場合、関数は返り値がObjectである場合に限りステートメントで明示した値を返します。

function Bar() {
    return 2;
}
new Bar(); // 新しいオブジェクト

function Test() {
    this.value = 2;

    return {
        foo: 1
    };
}
new Test(); // 返ってきたオブジェクト

newキーワードが省略されている場合は、関数は新しいオブジェクトを返す事はありません

function Foo() {
    this.bla = 1; // グローバルオブジェクトに設定される
}
Foo(); // undefinedが返る

JavaScriptのthisの働きのせいで、上記の例ではいくつかのケースでは動作するように見える場合がありますが、それはグローバルオブジェクトthisの値として使用されるからです。

ファクトリー

newキーワードを省略するためには、コンストラクタ関数が明示的に値を返す必要があります。

function Bar() {
    var value = 1;
    return {
        method: function() {
            return value;
        }
    }
}
Bar.prototype = {
    foo: function() {}
};

new Bar();
Bar();

Barで呼び出されたものは両方とも全く同じものものになります。これには、methodと呼ばれるプロパティを持ったオブジェクトが新しく生成されますが、これはクロージャです。

また、注意する点として呼び出されたnew Bar()は返ってきたオブジェクトのプロトタイプに影響しません。プロトタイプは新しく生成されたオブジェクトにセットされはしますが、Barは絶対にその新しいオブジェクトを返さないのです。

上記の例では、newキーワードの使用の有無は機能的に違いがありません。

ファクトリーとして新しくオブジェクトを作成する

多くの場合に推奨される事として、newの付け忘れによるバグを引き起こしやすいので、newを使用しないようにするという事があります。

新しいオブジェクトを作成するためにファクトリーを使用して、そのファクトリー内部に新しいオブジェクトを作成すべきだという事です。

function Foo() {
    var obj = {};
    obj.value = 'blub';

    var private = 2;
    obj.someMethod = function(value) {
        this.value = value;
    }

    obj.getPrivate = function() {
        return private;
    }
    return obj;
}

上記の例ではnewキーワードが無いため堅牢になりますし、確実にプライベート変数を使用するのが簡単になりますが、いくつかの欠点があります。

  1. 作られたオブジェクトがプロトタイプ上のメソッドを共有しないために、よりメモリーを消費してしまいます。
  2. ファクトリーを継承するために、他のオブジェクトの全てのメソッドをコピーする必要があるか、新しいオブジェクトのプロトタイプ上にそのオブジェクトを設置する必要があります。
  3. newキーワードが無いという理由だけで、プロトタイプチェーンから外れてしまうのは、どことなく言語の精神に反します。

終わりに

newキーワードが省略される事によりバグの可能性がもたらされますが、それによりプロトタイプを全く使わないという確かな理由にはなりません。最終的には、アプリケーションの必要性により、どちらの解決法がより良いかが決まってきます。特に大切なのは、オブジェクトの作成に特定のスタイルを選ぶ事、またそのスタイルに固執する事です。

スコープと名前空間

JavaScriptはブロックに2つのペアの中括弧を使うのが素晴しいですが、これはブロックスコープをサポートしていません。その為、この言語に残されているのは関数スコープだけです。

function test() { // スコープ
    for(var i = 0; i < 10; i++) { // スコープではない
        // 数える
    }
    console.log(i); // 10
}

JavaScriptはまた明確な名前空間を持ちません。この事は全て一つのグローバルで共有された名前空間で定義されるという事です。

変数が参照されるまでの間、JavaScriptはスコープ全てを遡って参照を探索します。グローバルスコープまで遡っても要求した名前が無いとReferenceErrorが発生します。

グローバル変数の致命傷

// スクリプト A
foo = '42';

// スクリプト B
var foo = '42'

上記の2つのスクリプトは同じ効果を持っていません。スクリプト Aはfooと呼ばれる変数を、グローバルスコープに定義しており、スクリプト Bはfoo現在のスコープで定義ています。

繰り返しますが、この2つのスクリプトは同じ影響を全く持っていないスクリプトになります。varを使用しない事は重大な意味を持ちます。

// グローバルスコープ
var foo = 42;
function test() {
    // ローカルスコープ
    foo = 21;
}
test();
foo; // 21

test関数の中のvarステートメントを省略するとfooの値をオーバーライドします。最初の内は大した事ではないように思いますが、JavaScriptが何千行規模になると、varを使っていない事でバグの追跡が酷く困難になります。

// グローバルスコープ
var items = [/* 何かのリスト */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // サブループのスコープ
    for(i = 0; i < 10; i++) { // varステートメントが無くなった
        // 素敵な実装を!
    }
}

外側のループはsubloopが最初に呼ばれた後に終了します。なぜなら、subloopがグローバル変数iの値で上書きされているからです。2番目のforループにvarを使用する事によって簡単にこのエラーを回避する事ができます。目的とする効果を外側のスコープに与えようとしない限り、絶対varステートメントは省略してはいけません。

ローカル変数

JavaScriptのローカル変数の為の唯一の作成方法はfunctionパラメーターとvarステートメントによって宣言された変数になります。

// グローバルスコープ
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // 関数testのローカル変数
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

fooiは、関数testのスコープ内のローカル変数ですが、barの代入は同じ名前でグローバル変数で上書きしてしまいます。

巻き上げ

JavaScriptは宣言を巻き上げます。これはvarステートメントとfunction宣言が、それらを含むスコープの一番先頭に移動するという事を意味します。

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

上記のコードは、実行を開始する前に変換されてしまいます。JavaScriptはvarステートメントと同じように、直近で囲んでいるfunction宣言を先頭に移動させます。

// varステートメントはここに移動する
var bar, someValue; // 'undefined'がデフォルト

// function宣言もここに移動する
function test(data) {
    var goo, i, e; // 無くなったブロックスコープはこちらに移動する
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // barが'undefined'のままなので、Typeerrorで呼び出し失敗
someValue = 42; // 割り当てすると巻き上げの影響を受けない
bar = function() {};

test();

ブロックスコープの欠落はvarステートメントをループやボディの外に移動するだけでなく、ifの構成を直感的ではないものにしてしまいます。

元のコードの中のifステートメントはグローバル変数であるgooも変更しているように見えますが、実際には -巻き上げが適用された後に- ローカル変数を変更しています。

巻き上げについての知識がないと、下に挙げたコードはReferenceErrorになるように見えます。

// SomeImportantThingが初期化されているかチェックする
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

しかし、勿論上記の動きはvarステートメントがグローバルスコープの上に移動しているという事実に基づいています。

var SomeImportantThing;

// 他のコードがSomeImportantThingをここで初期化するかもしれないし、しないかもしれない

// SomeImportantThingがある事を確認してください
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

名前解決の順序

JavaScriptのグローバルスコープを含む、全てのスコープは、現在のオブジェクトを参照している特殊な名前thisを持っています。

関数スコープはまた、argumentsという名前も持っています。それは関数スコープの中で定義され、関数に渡された引数を含んでいます。

例として、関数の中でfooと命名された変数にアクセスしようとする場合を考えましょう。JavaScriptは以下の順番で、その名前を探索しようとします。

  1. var fooステートメントが現在のスコープで使われている場合
  2. fooという名前の関数パラメーターが存在するかどうか
  3. 関数それ自体がfooとして呼ばれているかどうか
  4. 一つ外のスコープに行き、再度#1から始める

名前空間

一つしかグローバルの名前空間を持たない事による良くある問題は変数名の衝突による問題の起きる可能性です。JavaScriptでは、この問題を匿名関数ラッパーの助けで簡単に回避できます。

(function() {
    // "名前空間"に自分を含む

    window.foo = function() {
        // 露出したクロージャ
    };

})(); // 即座に関数を実行する

無名関数はexpressionsとみなされ、呼び出し可能になり最初に評価されます。

( // カッコ内の関数が評価される
function() {}
) // 関数オブジェクトが返される
() // 評価の結果が呼び出される

関数式を評価し、呼び出す別の方法として構文は違いますが、同様の動作をするのが下記です。

// 2つの別の方法
+function(){}();
(function(){}());

終わりに

自身の名前空間にカプセル化する為に常に匿名関数ラッパーを使用する事を推奨します。これは、コードを名前衝突から守る為だけでなく、プログラムのより良いモジュール化の為でもあります。

さらに、グローバル変数の使用は悪い習慣と考えられています。一回でもグローバル変数を使用するとエラーが発生しやすく、メンテナンスがしにくいコードになってしまいます。

配列

配列の繰り返しとプロパティ

JavaScriptの配列もまたオブジェクトですが、for in ループを配列の繰り返し処理で使用することの良い理由は1つもありません。実際、配列にfor inを使用しない為の正当な理由はたくさんあります。

for inループはプロトタイプチェーン上の全てのプロパティを列挙するため、hasOwnPropertyをそれらのプロパティの存在判定に使います。この為、通常のforループよりも20倍遅くなります。

繰り返し

配列の要素を繰り返すとのに、最高のパフォーマンスを出したければ昔ながらのforループを使うのが一番です。

var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
    console.log(list[i]);
}

上記の例では1つ追加の仕掛けがありますが、それはl = list.lengthによって配列の長さをキャッシュする部分です。

lengthプロパティは配列自身に定義されてはいますが、ループ中の繰り返しで毎回これを参照してしまうと、やはりオーバーヘッドが存在してしまいます。最近のJavaScriptエンジンはこのような場合に最適化するはずですが、コードが新しいエンジンで実行されるかどうか、知る方法はありません。

実際には、キャッシュを抜きにするとループの結果はキャッシュされたものに比べてたった半分の速度にしかなりません。

lengthプロパティ

lengthプロパティのゲッターは単に配列に含まれる要素の数を返すだけにも関わらず、セッターは配列をトランケートする為にも使用できます。

var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]

foo.length = 6;
foo; // [1, 2, 3]

より小さいlengthを割り当てると配列をトランケートしますが、lengthが大きくなっても配列には何も影響しません。

終わりに

最高のパフォーマンスの為には、常にforループを使用し、lengthプロパティをキャッシュする事をお勧めします。for inループを配列で使用するのは、バグや最低のパフォーマンスの傾向があるコードを書く前兆になります。

Arrayコンストラクター

Arrayコンストラクターはそのパラメーターの扱い方が曖昧なので、新しい配列を作る時には、常に配列リテラル - []記法 - を使用する事を強くお勧めします。

[1, 2, 3]; // 結果: [1, 2, 3]
new Array(1, 2, 3); // 結果: [1, 2, 3]

[3]; // Result: [3]
new Array(3); // 結果: []
new Array('3') // 結果: ['3']

このケースの場合、Arrayコンストラクターに渡される引数は一つだけですが、その引数はNumberになります。コンストラクターは、引数に値がセットされたlengthプロパティを伴った新しい配列を返します。特筆すべきなのは、新しい配列のlengthプロパティのみが、このようにセットされるという事です。実際の配列のインデックスは初期化されません。

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, インデックスがセットされていない

配列の長さが先行してセットされるという振舞いは、いくつかの場合に便利です。例えば、文字の繰り返しや、for loopを使用したコードの回避などの場合です。

new Array(count + 1).join(stringToRepeat);

終わりに

Arrayコンストラクターの使用は出来る限り避けてください。リテラルが当然望ましい形です。それらは、短かく明快な文法をもっている為に、コードの可読性を高めてくれます。

等価と比較

JavaScriptはオブジェクトの値の等価の比較方法を2種類持っています。

等価演算子

等価演算子は2つのイコール記号: ==から成っています。

JavaScriptは弱い型付けを特徴としています。これは等価演算子が比較をする際に型付けを強制するという意味です。

""           ==   "0"           // false
0            ==   ""            // true
0            ==   "0"           // true
false        ==   "false"       // false
false        ==   "0"           // true
false        ==   undefined     // false
false        ==   null          // false
null         ==   undefined     // true
" \t\r\n"    ==   0             // true

上記の表では型強制の結果が表示されています。==の使用が一般に悪い習慣とみなされる大きな理由として、変換ルールが複雑な為、バグの追跡が困難になる事が挙げられます。

加えて、型強制が行なわれるとパフォーマンスにも影響してしまいます。例えば、文字列は他の数字と比較する前に数値に変換されなければなりません。

厳密等価演算子

厳密等価演算子は3つのイコール記号:===で成っています。

これはオペランドの間で強制的な型変換が実行されない事を除けば、通常の等価演算子と同じように正確に動作します。

""           ===   "0"           // false
0            ===   ""            // false
0            ===   "0"           // false
false        ===   "false"       // false
false        ===   "0"           // false
false        ===   undefined     // false
false        ===   null          // false
null         ===   undefined     // false
" \t\r\n"    ===   0             // false

上記の結果は、より明確でコードの早期破損を可能にします。これはある程度までコードを硬化させて、オペランドが別の型の場合にパフォーマンスが向上します。

オブジェクトの比較

=====は両方とも等価演算子とされていますが、そのオペランドの少なくとも一つがObjectの場合は、両者は異なる動きをします。

{} === {};                   // false
new String('foo') === 'foo'; // false
new Number(10) === 10;       // false
var foo = {};
foo === foo;                 // true

これら2つの演算子は同一性を比較していているのであって、等価を比較しているわけではありません。これは、これらの演算子はPythonのis演算子やCのポインター比較と同じように、同じオブジェクトのインスタンスを比較するという事になります。

終わりに

厳密等価演算子だけを使用することを特に推奨します。型を強制的に型変換する場合はexplicitlyであるべきで、言語自体の複雑な変換ルールが残っているべきではありません。

typeof演算子

typeof演算子(instanceofも同様です)は恐らくJavaScriptの最大の設計ミスです。完全に壊れている存在に近いものです。

instanceofはまだ限られた用途で使用できますが、typeofは本当に使用できる実用的なケースはオブジェクトの型を調べるという起こらないケース一つしかありません。

JavaScript の型テーブル

Value               Class      Type
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (Nitro/V8ではfunction)
new RegExp("meow")  RegExp     object (Nitro/V8ではfunction)
{}                  Object     object
new Object()        Object     object

上記のテーブルにおいてTypetypeof演算子が返す値を参照しています。はっきりと分かるように、この値はどれでも一貫しています。

Classはオブジェクト内部の[[Class]]プロパティの値を参照しています。

[[Class]]の値を取得する為に、Object.prototypeメソッドのtoStringを使う事があります。

オブジェクトのクラス

仕様では[[Class]]の値にアクセスするためにはObject.prototype.toStringを使用した厳密な一つの方法が与えられています。

function is(type, obj) {
    var clas = Object.prototype.toString.call(obj).slice(8, -1);
    return obj !== undefined && obj !== null && clas === type;
}

is('String', 'test'); // true
is('String', new String('test')); // true

上記の例ではthisの値と共にObject.prototype.toStringが呼び出され[[Class]]の取得されている値がオブジェクトとして設定されます。

未定義変数のテスト

typeof foo !== 'undefined'

上記ではfooが実際に宣言されたかどうかをReferenceErrorの結果を参照してチェックします。これはtypeofが唯一実際に役に立つ場合です。

終わりに

オブジェクトの型をチェックする為には、Object.prototype.toStringを使用する事を強くお勧めします。これが唯一信頼できる方法だからです。上述の型テーブルでも分かるように、typeofの戻り値は仕様で定義されていないものを返します。よって、実装によって別の結果になる事があります。

変数が定義されているかチェックしない限りは、typeofどんな事をしても避けるべきです。

instanceofオペレーター

instanceofオペレーターは2つのオペランドのコンストラクタを比較します。これはカスタムで作ったオブジェクトを比較する時にのみ有用です。組み込みの型に使用するのはtypeof operatorを使用するのと同じくらい意味がありません。

カスタムオブジェクトの比較

function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true

// これは単に関数オブジェクトFooにBar.prototypeをセットしただけです。
// しかし、実際のFooのインスタンスではありません。
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

ネイティブ型でinstanceofを使用する

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

ここで1つ重要な事は、異なるJavaScriptのコンテキスト(例えば、ブラウザの異なるウィンドウ)を元としたオブジェクトでは、コンストラクタが厳密に同じものでは無い為にinstanceofは上手く動作しません。

終わりに

instanceofオペレーターは同じJavaScriptのコンテキストが起源になっているカスタムメイドのオブジェクトを扱う場合のみ使うべきです。ちょうどtypeofオペレーターのように、その他での使用は避けるべきです。

型変換

JavaScriptは弱い型付けの言語なので、可能な限り型強制が適用されます。

// これらはtrueです。
new Number(10) == 10; // Number.toString()が変換される
                      // numberに戻る

10 == '10';           // StringsがNumberに変換される
10 == '+10 ';         // バカみたいに文字列を追加
10 == '010';          // もっともっと
isNaN(null) == false; // nullが0に変換される
                      // もちろんNaNではないです

// これらはfalseです
10 == 010;
10 == '-10';

上記の自体を避ける為に、厳密等価演算子を使用する事を強く推奨します。また、これはたくさんある落し穴を避けますが、それでもまだJavaScriptの弱い型付けシステムから発生する色々な課題が残っています。

組み込み型のコンストラクタ

NumberStringのような組み込み型のコンストラクタは、newキーワードの有無で振る舞いが違ってきます。

new Number(10) === 10;     // False, ObjectとNumber
Number(10) === 10;         // True, NumberとNumber
new Number(10) + 0 === 10; // True, 暗黙の型変換によります

Numberのような組み込み型をコンストラクタとして使うと、新しいNumberオブジェクトが作られますが、newキーワードを除外するとNumber関数がコンバーターのように振る舞います。

加えて、リテラルかオブジェトではない値を持っていると、さらに型強制が多くなります。

最良のオプションは以下の3つの方法の内、1つで型を明示してキャストする事になります。

Stringでキャストする

'' + 10 === '10'; // true

空の文字列の付加により値を簡単に文字列にキャストできます。

Numberでキャストする

+'10' === 10; // true

単項プラスオペレーターを使うと数字にキャストする事が可能です。

Booleanでキャストする

notオペレーターを2回使うと、値はブーリアンに変換できます。

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

コア

なぜ、evalを使ってはいけないのか

eval関数はローカルスコープ中のJavaScriptコードの文字列を実行します。

var foo = 1;
function test() {
    var foo = 2;
    eval('foo = 3');
    return foo;
}
test(); // 3
foo; // 1

しかし、eval直接ローカルスコープから呼ばれて、かつ呼んだ関数の名前が実際のevalでないと実行しません。

var foo = 1;
function test() {
    var foo = 2;
    var bar = eval;
    bar('foo = 3');
    return foo;
}
test(); // 2
foo; // 3

evalの使用は全てのコストを払ってでも回避するべきです。その「使用法」の99.9%で、これ無しでも実装できます。

偽装されたeval

timeout functionsであるsetTimeoutsetIntervalはどちらも最初の引数として文字列を取る事ができます。この文字列はevalがこの場合直接呼ばれていないので、常にグローバルスコープで実行されてしまいます。

セキュリティの問題

evalはまたセキュリティの問題もあります。なぜなら、どんなコードを与えられても実行してしまうからで、絶対に不明または信頼できない発行元の文字列は使ってはいけません。

終わりに

evalは絶対に使用しないでください。これを使用しているどんなコードも、その働き、パフォーマンスやセキュリティについて問われてしまいます。evalが必要な場合でも、最初の段階で使用しないでください。より良いデザインを使用するべきで、それにはevalを使う必要性はありません。

undefinednull

JavaScriptはnothingを表す2つの別個の値を持っています。これら2つの内でundefinedはより便利な存在です。

undefinedの値

undefinedはただ1つの値undefinedを持つ型です。

この言語はまた、undefinedの値を持つグローバル変数を定義しています。この値もまたundefinedと呼ばれています。しかし、この変数は どちらも 言語のキーワードではなく、定数です。この事はこのは簡単に上書きされてしまうという事になります。

undefinedが返される時の例をいくつか挙げます。

  • (未定義の)グローバル変数undefinedにアクセスした時
  • return文が無い為に、暗黙のうちに関数が返された時
  • 何も返されないreturnがある時
  • 存在しないプロパティを探索する時
  • 関数のパラメーターで明示的な値が何も無い時
  • undefinedが設定された全ての値

undefinedの値に変更する処理

グローバル変数undefinedのみが実際のundefinedのコピーを保持するので、これに新しい値を代入してもundefined の値が変更される事はありません

まだ、undefinedの値に対して何かしらの比較をしないといけない場合は、最初にundefinedの値を取得する必要があります。

コードのundefinedの変数の上書きを可能な限りしないよう保護する為には、一般的なテクニックとしてanonymous wrapperの引数にパラメーターを追加するというものがあります。

var undefined = 123;
(function(something, foo, undefined) {
    // ローカルスコープではundefined。
    // ここで値に対して参照がされる

})('Hello World', 42);

同じ効果を得る事ができる別の方法として、ラッパーの内部での宣言を使うものがあります。

var undefined = 123;
(function(something, foo) {
    var undefined;
    ...

})('Hello World', 42);

これらの唯一の違いは、こちらのバージョンの方が4バイト余計に短縮できるという物です。また、他にvarステートメントは匿名ラッパーの中にはありません。

nullの使用

JavaScriptというプログラム言語のコンテキストの中では、undefinedは主に伝統的な意味でのnullの意味で使用される事が多いです。実際のnull(リテラルも型も両方)は多かれ少なかれ、単なるデータ型です。

それはJavaScriptの内部でいくつか使われています(プロトタイプチェーンの終わりにFoo.prototype = nullという宣言をするようなもの)が、ほとんど全てのケースで、undefinedに置き替える事が可能です。

セミコロン自動挿入

JavaScriptはC言語スタイルのシンタックスを持っていますが、これはソースコードの中でセミコロンの使用を強制している事にはならないので、これらを省略する事も可能です。

JavaScriptはセミコロン無しの言語ではありません。実際に、ソースコードを理解する為にもセミコロンは必要になります。ですので、JavaScriptのパーサーはセミコロンが無い事によるパースエラーを検出する度に、自動的にセミコロンを挿入します。

var foo = function() {
} // セミコロンが入っている事が期待されるので、パースエラーになる
test()

挿入が起こると、パーサーはもう一度パースします。

var foo = function() {
}; // エラーが無いので、パーサーは次の解析をする
test()

セミコロンの自動挿入は、コードの振る舞いを変えられる為に、言語の最大の欠陥の内の一つと考えられています。

どのように動くか

以下のコードはセミコロンが無いので、パーサーはどこにセミコロンを挿入するか決めなくてはなりません。

(function(window, undefined) {
    function test(options) {
        log('testing!')

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}

})(window)

下記がパーサーの「推測」ゲームの結果になります。

(function(window, undefined) {
    function test(options) {

        // 行がマージされて、挿入されない
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- 挿入

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        ); // <- 挿入

        return; // <- inserted, breaks the return statement
        { // ブロックとして扱われる

            // a label and a single expression statement
            foo: function() {} 
        }; // <- 挿入
    }
    window.test = test; // <- 挿入

// 再度行がマージされる
})(window)(function(window) {
    window.someLibrary = {}; // <- 挿入

})(window); //<- 挿入

パーサーは上記のコードの振舞いを劇的に変化させます。あるケースにおいては、間違っている事にもなってしまいます。

先頭の括弧

先頭に括弧がある場合、パーサーはセミコロンを挿入しません

log('testing!')
(options.list || []).forEach(function(i) {})

このコードは1つの行に変形します。

log('testing!')(options.list || []).forEach(function(i) {})

logが関数を返さない確率はとても高いです。しかし、上記ではundefined is not a functionというTypeErrorが繰り返されます。

終わりに

セミコロンを省略するのは絶対にお勧めしません。括弧を対応する文と同じ行に記述すること、および一行のif / else文に対して括弧を省略しないことが推奨されています。これら両方の処理がコードの整合性を高めてくれるだけでなく、JavaScriptパーサーの振舞いを変えてしまうのを防いでくれるでしょう。

delete演算子

端的に言って、JavaScriptの関数やその他の要素はDontDelete属性が設定されているので、グローバル変数を消去する事は不可能です。

グローバルコードと関数コード

変数や、関数がグローバルまたは関数スコープで定義された時は、そのプロパティは有効なオブジェクトかグローバルオブジェクトになります。このようなプロパティは属性のセットを持っていますが、それらの内の1つがDontDeleteになります。変数や関数がグローバルや関数コードで宣言されると、常にDontDelete属性を作るために、消去できません。

// グローバル変数:
var a = 1; // DontDelete属性が設定される
delete a; // false
a; // 1

// 通常関数:
function f() {} // DontDelete属性が設定される
delete f; // false
typeof f; // "function"

// 再代入も役に立たない:
f = 1;
delete f; // false
f; // 1

明示的なプロパティ

明示的にプロパティを設定することが、通常通りの消去を可能にします。

// プロパティを明示的に設定する
var obj = {x: 1};
obj.y = 2;
delete obj.x; // true
delete obj.y; // true
obj.x; // undefined
obj.y; // undefined

上記の例の中で、obj.xobj.yはそれぞれDontDelete属性が無い為に消去できます。これが下記の例でも動作する理由です。

// IE以外では、これも動作する
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // true - ただのグローバルのvar
delete GLOBAL_OBJECT.a; // true
GLOBAL_OBJECT.a; // undefined

ここではa. thisを消す為にグローバルオブジェクトと明示的に宣言したaをそのプロパティとして参照させて、消去する事を許可するトリックを使います。

IE(最低でも6-8で)は多少のバグがある為に、上記のコードは動作しません。

関数の引数と組み込み引数

関数の通常の引数である、arguments objectsと組み込みのプロパティもまた、DontDeleteが設定されています。

// 関数の引数とプロパティ:
(function (x) {

  delete arguments; // false
  typeof arguments; // "object"

  delete x; // false
  x; // 1

  function f(){}
  delete f.length; // false
  typeof f.length; // "number"

})(1);

ホストオブジェクト

delete演算子の挙動はホストオブジェクトにとって予測不可能になりかねません。仕様によりホストオブジェクトは、あらゆる挙動の実行が許可されている為です。

終わりに

delete演算子は、しばしば予期せぬ挙動をします。唯一安全な使用方法は通常のオブジェクトに明示的に設定されたプロパティを扱う場合だけです。

その他

setTimeoutsetInterval

JavaScriptは非同期なので、setTimeoutsetInterval関数を使ってある関数の実行のスケジュールを決める事が可能です。

function foo() {}
var id = setTimeout(foo, 1000); // Number > 0を返す

setTimeoutが呼ばれた時に、タイムアウトのIDを返し、この先おおよそ1000ms以内に実行するfooをスケジュールします。fooは正確に1度だけ実行されます。

これは、setTimeout関数の呼び出しで指定した遅延時間を正確に間違いなく得られるという事では決してありません。コードが実行されているJavaScriptエンジンのタイマー分解能によって決まります。この事実はJavaScriptがシングルスレッドなので、他のスレッドでの実行を妨害してしまう事があるかもしれません。

第一パラメーターを渡された関数はグローバルオブジェクトによって呼び出されます。これは呼び出された関数の内部でthisがまさにこのオブジェクトを参照しているという事になります。

function Foo() {
    this.value = 42;
    this.method = function() {
        // これはグローバルオブジェクトを参照しています
        console.log(this.value); // undefinedを記録するはずです
    };
    setTimeout(this.method, 500);
}
new Foo();

setIntervalでスタッキングコール

setTimeoutは関数を一度だけ実行します。setInterval - 名前が示すように - 毎回Xミリ秒毎に関数を実行しますが、この使用は推奨されていません。

コードがタイムアウト呼び出しブロックで実行される時に、setIntervalは指定された関数を呼び出します。これは、特に小さい間隔で、関数の結果をスタックに積む事ができます。

function foo(){
    // 1秒おきにブロックの何かを実行
}
setInterval(foo, 100);

上記のコードでは、fooが1回呼び出されて、1秒ブロックされます。

fooがコードをブロックしている間、setIntervalは呼び出される予定を確保しています。fooが完了した瞬間に、実行を待っている呼び出しが10回以上存在しているでしょう。

ブロッキング可能なコードの取り扱い

簡単かつ、一番コントロール可能な解決法として、関数自体の中でsetTimeoutを使うという方法があります。

function foo(){
    // 1秒ブロックする何か
    setTimeout(foo, 100);
}
foo();

このカプセル化はsetTimeoutの呼び出しだけでなく、呼び出しのスタッキングを防止してより詳細なコントロールが出来ます。fooそれ自身が今や、再度実行するかしないかを決める事が出来るのです。

手動でタイムアウトをクリアする

タイムアウトとインターバルのクリアは、clearTimeoutclearIntervalに個別のIDを渡せば出来ます。最初にset関数を使った場所に依存します。

var id = setTimeout(foo, 1000);
clearTimeout(id);

全てのタイムアウトをクリアする

全てのタイムアウトや、インターバルをクリアする組み込みメソッドが無い為、機能的にクリアする為には暴力的な手段を使う必要があります。

// "全ての"タイムアウトをクリアする
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

ここまでもまだ、任意の数字を与えられた為に影響を受けないタイムアウトがあるかもしれません。そのため、代わりに全てのタイムアウトのIDを追跡する事が推奨されます。それで個別にクリアされます。

隠されたevalの使用

setTimeoutsetInterval は、第一引数に文字列を取る事が可能です。この仕様は内部でevalを使用する為に、絶対に使うべきではありません。

function foo() {
    // この先呼ばれる
}

function bar() {
    function foo() {
        // 絶対に呼ばれない
    }
    setTimeout('foo()', 1000);
}
bar();

この場合、eval直接呼ばれないので、文字列が渡されたsetTimeoutglobal scopeで実行されます。よって、barのスコープからfooのローカル変数は使われないのです。

いずれかのタイムアウト関数によって呼び出される関数に引数を渡すために文字列を使わないという事は、さらに推奨されています。

function foo(a, b, c) {}

// 絶対にこのように使わない
setTimeout('foo(1,2, 3)', 1000)

// 匿名関数を代わりに使用する
setTimeout(function() {
    foo(1, 2, 3);
}, 1000)

終りに

setTimeoutsetIntervalのパラメーターに文字列を用いてはいけません。引数が関数に呼び出される必要がある場合本当に悪いコードの明確なサインになります。実際の呼び出しには匿名関数を渡すべきです。

さらに、setIntervalの使用はスケジューラーがJavaScriptの実行によってブロックされないので、避けるべきでしょう。