JavaScript秘密ガーデン-scope,namespace,constructor,equality and comparisons
70947 ワード
役割ドメインとネーミングスペース
JavaScriptはカッコで作成されたコードセグメントのペアをサポートしていますが、ブロックレベルの役割ドメインはサポートされていません.関数の役割ドメインのみをサポートします.
function test() { //
for(var i = 0; i < 10; i++) { //
// count
}
console.log(i); // 10
}
注:
{...}
は、付与文ではなくreturn式または関数パラメータの場合、オブジェクトとしての字面構文解析ではなく、コードセグメント解析として機能します.自動セミコロン挿入を考慮すると、これはいくつかの気づきにくいエラーを引き起こす可能性があります.訳者注:returnオブジェクトの左かっことreturnが1行にないとエラーが発生します.
// : undefined
function add(a, b) {
return
a + b;
}
console.log(add(1, 2));
JavaScriptには明示的なネーミングスペース定義がありません.これは、すべてのオブジェクトがグローバル共有ネーミングスペースの下に定義されていることを意味します.
1つの変数を参照するたびに、JavaScriptはこの変数が見つかるまで役割ドメイン全体を上に移動します.グローバル役割ドメインに到達してもこの変数が見つからない場合、
ReferenceError
異常が放出されます.暗黙的グローバル変数(The bane of global variables)
// script A
foo = '42';
// script B
var foo = '42'
上の2つのスクリプトの効果は異なります.スクリプトAは、グローバル役割ドメイン内で変数
foo
を定義し、スクリプトBは、現在の役割ドメイン内で変数foo
を定義する.さらに、上記の効果はまったく異なり、
var
を使用して変数を宣言しないと、暗黙的なグローバル変数が生成されることを強調します.//
var foo = 42;
function test() {
//
foo = 21;
}
test();
foo; // 21
関数
test
内でvar
キーワード宣言foo
変数を使用しないと、外部の同名変数が上書きされます.最初は大きな問題ではないように見えますが、数千行のコードがある場合、var
を使用して変数を宣言しないと、追跡しにくいBUGをもたらします.//
var items = [/* some list */];
for(var i = 0; i < 10; i++) {
subLoop();
}
function subLoop() {
// subLoop
for(i = 0; i < 10; i++) { // var
// do amazing stuff!
}
}
外部ループは、
subLoop
がグローバル変数subLoop
をカバーするため、i
が最初に呼び出された後に終了する.2番目のfor
サイクルでvar
宣言変数を使用すると、このようなエラーを回避できます.変数を宣言するときは、外部の役割ドメインに影響を及ぼすことが望ましい場合を除き、var
のキーワードを絶対に漏らさないでください.ローカル変数(Local variables)
JavaScriptのローカル変数は、2つの方法でのみ宣言できます.1つは関数#カンスウ#パラメータとして、もう1つは
var
キーワードで宣言されます.//
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// test
i = 5;
var foo = 3;
bar = 4;
}
test(10);
foo
およびi
は、関数test
内のローカル変数であり、bar
に対する付与値は、グローバル役割ドメイン内の同名変数を上書きする.変数宣言の移動(Hosting)
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 test(data) {
var goo, i, e; // ,
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // :TypeError, bar 'undefined'
someValue = 42; // (hoisting)
bar = function() {};
test();
ブロックレベルの役割ドメインがないと、
var
式がサイクル内から外部に移動するだけでなく、if
式の一部がより理解しにくくなる.従来のコードでは、
if
式はすべての変数goo
を修正したように見えますが、実際にはリフトルールが適用された後、ローカル変数を修正しています.アップグレードルールの知識がなければ、次のコードは異常
ReferenceError
を投げ出すように見えます.// SomeImportantThing
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
実際には、
var
式がグローバル役割ドメインの上部に昇格するため、上記のコードは正常に動作します.var SomeImportantThing;
// , SomeImportantThing,
//
if (!SomeImportantThing) {
SomeImportantThing = {};
}
Nettuts+のウェブサイトにはhoistingを紹介する文章があり、そのコードは啓発的です.
// : Nettuts+ , JavaScript
var myvar = 'my value';
(function() {
alert(myvar); // undefined
var myvar = 'local value';
})();
名前解析順序(Name resolution order)
JavaScriptのすべての役割ドメイン(グローバル役割ドメインを含む)には、現在のオブジェクトを指す特別な名前`this`があります.
関数の役割ドメインには、関数に渡されるパラメータを含むデフォルトの変数`arguments`もあります.
たとえば、関数内の
foo
変数にアクセスすると、JavaScriptは次の順序で検索されます.var foo
の定義があるかどうか.foo
名が使用されているかどうか.foo
と呼ぶかどうか.注:カスタム
arguments
パラメータは、オリジナルのarguments
オブジェクトの作成を阻止します.ネームスペース(Namespaces)
グローバルな役割ドメインが1つしかないため、一般的なエラーはネーミング競合です.JavaScriptでは、匿名パッケージで簡単に解決できます.
(function() {
// ( : )
window.foo = function() {
// ,
};
})(); //
匿名関数は式#シキ#と考えられる.したがって、呼び出し性のために、まず実行されます(evaluated).
( //
function() {}
) //
() // ,
次の2つの方法で文法が異なりますが、効果は同じです.
//
+function(){}();
(function(){}());
結論(In conclusion)
匿名パッケージ(自己実行匿名関数)を使用して、ネーミングスペースを作成することをお勧めします.これにより,ネーミング競合を防止するだけでなく,プログラムのモジュール化にも有利である.
また,グローバル変数の使用は悪い習慣とされている.このようなコードは、エラーが発生し、高いメンテナンスコストをもたらす傾向があります.
コンストラクタ
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
をコンストラクション関数として呼び出し、新しく作成されたオブジェクトのprototype
をFoo.prototype
に設定します.明示的な
return
式は、返される結果に影響しますが、返されるオブジェクトに限定されます.function Bar() {
return 2;
}
new Bar(); //
// :new Bar() , 2。
// new Bar().constructor === Bar
// ,
// function Bar() {
// return new Number(2);
// }
// new Bar().constructor === Number
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); //
// : , new
// (new Test()).value undefined, (new Test()).foo === 1。
new
が欠落している場合、関数は新しく作成されたオブジェクトを返しません.function Foo() {
this.bla = 1; //
}
Foo(); // undefined
上記の例では、場合によっては正常に動作することもありますが、JavaScriptの`this`の動作原理のため、ここでの
this
はグローバルオブジェクトを指します.ファクトリモード(Factories)
new
キーワードを使用しないためには、コンストラクション関数は明示的に値を返さなければなりません.function Bar() {
var value = 1;
return {
method: function() {
return value;
}
}
}
Bar.prototype = {
foo: function() {}
};
new Bar();
Bar();
上記の2つの
Bar
関数の呼び出しに対して返される値は完全に同じであり、新しく作成されたmethod
属性を持つオブジェクトが返され、実際にはここでクローズドパッケージが作成されます.また、
new Bar()
は、戻りオブジェクトのプロトタイプを変更することはありません(すなわち、戻りオブジェクトのプロトタイプはBar.prototypeを指しません).コンストラクション関数のプロトタイプは作成したばかりの新しいオブジェクトに指し示されるため、ここのBar
はこの新しいオブジェクトを返さなかった(訳者注:method
のプロパティを含むカスタムオブジェクトを返した).上記の例では、
new
キーワードを使用するか使用しないかには機能的な違いはない.// : Bar
var bar1 = new Bar();
typeof(bar1.method); // "function"
typeof(bar1.foo); // "undefined"
var bar2 = Bar();
typeof(bar2.method); // "function"
typeof(bar2.foo); // "undefined"
ファクトリモードによる新規オブジェクトの作成(Creating new objects via factories)
私たちがよく耳にする忠告は、
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
の呼び出し方式よりもエラーが発生しにくく、プライベート変数による利便性を十分に利用できるが、それに伴ういくつかの悪い点である.new
の漏れを防ぐためだけで、言語そのものの思想に反しているようだ.まとめ(In conclusion)
new
のキーワードを漏らすと問題になる可能性がありますが、プロトタイプチェーンの使用を放棄する言い訳ではありません.最終的にどの方法を使用するかは、アプリケーションのニーズに応じて、コードの書くスタイルを選択し、堅持することが最も重要です.同等と比較
JavaScriptでは、2つの値が等しいかどうかを判断する方法が2つあります.
イコールオペレータ
イコールオペレータは2つの等号で構成されています:
==
JavaScriptは弱い言語です.これは、オペレータが2つの値を比較するために強制的なタイプ変換を行うことを意味します."" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r
" == 0 // true
上記の表は強いタイプの変換を示しており、
==
を用いることがプログラミング習慣が悪い主な原因として広く考えられているが、その複雑な変換規則のため、追跡しにくい問題を招く.また、強制タイプ変換は、文字列が配列と比較するために事前に数値に強制的に変換されるなど、パフォーマンスの消費をもたらします.
厳密にはオペレータ(The strict equals operator)に等しい
厳密なイコールオペレータは、
===
という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
" === 0 // false
上記の結果はより明確で、コードの分析に有利である.2つのオペランドのタイプが異なる場合、等しくないことは間違いなく、パフォーマンスの向上にも役立ちます.
オブジェクトの比較(Comparing objects)
==
と===
のオペレータはすべてオペレータに等しいが、そのうちの1つのオペランドがオブジェクトである場合、動作は異なる.{} === {}; // false
new String('foo') === 'foo'; // false
new Number(10) === 10; // false
var foo = {};
foo === foo; // true
ここで、オペレータが比較するのは、値が等しいかどうかではなく、同じアイデンティティに属しているかどうかです.すなわち,オブジェクトの同じインスタンスのみが等しいと考えられる.これはPythonの
is
とCのポインタの比較に似ています.結論(In conclusion)
厳密なイコールオペレータの使用を強くお勧めします.タイプが変換する必要がある場合は、言語自体の複雑な強制変換ルールではなく、比較前の明示的の変換を使用する必要があります.
http://www.cnblogs.com/sanshi/archive/2011/03/22/1991987.html