JavaScriptは開発者を混乱させたり、一貫性が不完全なために白熱したりすることがあります。 JavaScriptには、混乱させたり混乱させたりすることがいくつかあります。 これらの最も有名なものは、with演算子 、 暗黙的なグローバル変数、および比較演算における奇妙な動作です。
おそらく、プログラミングの歴史の中で最も論争が起こったのはJavaScriptをめぐって燃え上がった。 欠点(新しいECMAScript仕様で部分的に説明されています)に加えて、ほとんどのプログラマーは次の点に不満を感じています。
- 多くの人がJavaScript言語自体に相当すると誤って考えているDOMには、非常に失敗したAPIがあります。
- C言語とJava言語からJavaScriptに切り替えると、構文のtrapに陥ります。構文のtrapは、命令型言語と同じようには配置されません。 これは非常に頻繁にバグにつながり、非常に迷惑です。
その結果、JavaScriptの評判はかなり悪くなりましたが、これは一般的にはふさわしくないものです。 ほとんどの場合、これは多くの開発者がJavaまたはC / C ++での経験をJavaScriptに移行するためです。 JavaとJavaScriptのアプローチの違いを示す3つの最も難しいケースを以下に示します。
範囲
ほとんどの開発者は、必要性のためにJavaScriptに切り替えます。 そして、ほとんどすべての人が1つの間違いを繰り返します-彼らは最初に言語の機能を学ぶことなくコードを書き始めます。 非常に多くの人が少なくとも一度は視界の問題を抱えています。
JavaScript構文はCファミリで使用されるものと非常に似ており、関数の構成要素(
if
および
for
中括弧で区切ります。 したがって、多くの開発者は、ブロックレベルのスコープを同様の原則で整理することを提案しています。 残念ながら、そうではありません。
まず、変数のスコープは角括弧ではなく関数によって決定されます。 つまり、新しいスコープを作成し、
for
作成しない
if
、実際には、その構築で宣言された変数は「上昇」します。 つまり、宣言されている最初の関数の先頭、つまりグローバルスコープで作成されます。
第二に、
with
ステートメントの存在はJavaScriptのスコープを動的にし、プログラムが開始する前にそれを決定することはできません。 JavaScript
with
完全に使用
with
ないでください。JavaScriptを使用
with
ないと、字句スコープを使用する言語になります。 つまり、コードを読んですべてのスコープを自分で理解するだけで十分です。
正式には、JavaScriptにはスコープに識別子を含める4つの方法があります。
- 言語標準によると、デフォルトでは、すべての領域に識別子thisと引数が含まれます。
- 仮パラメータに基づく :仮関数パラメータの範囲は、関数の本体によって制限されます。
- 関数宣言を使用します 。
- 変数を宣言することにより。
ただし、留意することが1つあります。varを使用せずに(暗黙の)変数を宣言すると、グローバルスコープの暗黙的な定義になります。 明示的なバインドなしで関数が呼び出された場合、
this
ポインターにも同じことが当てはまります。
詳細を説明する前に、strictモード(
'use strict';
)を
'use strict';
、すべての変数と関数の宣言を各関数の先頭に配置することをお勧めします。
for
ブロックと
if
ブロック内
for
変数と関数を宣言しないでください。
育てる
この用語は、広告が実際に実装される方法の説明を簡略化するために使用されます。 発生した変数は、それらを含む関数の最初で宣言され、
undefined
として初期化されます。 割り当ては、アナウンスが発生する行で直接実行されます。
例を考えてみましょう:
function myFunction() { console.log(i); var i = 0; console.log(i); if (true) { var i = 5; console.log(i); } console.log(i); }
どのような値が画面に表示されると思いますか?
undefined 0 5 5
var
ステートメントは、
if
ブロック内で変数
i
ローカルコピーを宣言しません。 代わりに、以前に発表された内容を上書きします。 最初の
console.log
ステートメントは、未定義として初期化された変数
i
実際の値を出力することに注意してください。 そして、あなたが厳格モードに入ったら? 厳格モードでは、変数を使用する前に宣言する必要がありますが、JavaScriptエンジンはこれを必要としません。 ところで、
var
を再宣言する必要はないことに注意してください。 そのようなバグをキャッチする必要がある場合は、 JSHintやJSLintなどのツールを使用してください 。
エラーにつながる可能性のある変数を宣言する別の方法を示す例を見てみましょう。
var notNull = 1; function test() { if (!notNull) { console.log("Null-ish, so far", notNull); for(var notNull = 10; notNull <= 0; notNull++){ //.. } console.log("Now it's not null", notNull); } console.log(notNull); }
この例では、
notNull
変数のローカルコピーが
test()
関数内
notNull
宣言され、 上げられているため、
if
ブロックが実行されます。 ここでは、型キャスト操作も役割を果たします。
関数式と関数宣言
引き上げは、変数だけでなく、実際に変数である関数式、および関数宣言にも適用できます。 ここでは、この機能について簡単に説明します。 要するに、関数宣言は一般に関数式として動作しますが、宣言はスコープの先頭に配置されます。
次に関数宣言の例を示します。
function foo() { // A function declaration function bar() { return 3; } return bar(); // This function declaration will be hoisted and overwrite the previous one function bar() { return 8; } }
次に、関数式の例と比較します。
function foo() { // A function expression var bar = function() { return 3; }; return bar(); // The variable bar already exists, and this code will never be reached var bar = function() { return 8; }; }
問題のより深い理解については、投稿の最後に示されている出版物を参照してください。
と
この例は、実行時にのみスコープを決定できる状況を反映しています。
function foo(y) { var x = 123; with(y) { return x; } }
y
にフィールド
x
がある場合、関数
foo()
は
yx
を返し、そうでない場合は
123
を返します。 この方法ではランタイムエラーが発生する可能性があるため、
with
ステートメントの使用を避けることをお勧めします。
未来を見据える:ECMAScript 6
ECMAScript 6仕様では、ブロックレベルの可視性を定義する5番目の方法である
let
ステートメントが有効になります。
function myFunction() { console.log(i); var i = 0; console.log(i); if (false) { let i = 5; console.log(i); } console.log(i); }
ECMAScript 6では、letを使用して
if
内で
i
を宣言すると、
if
ブロックに新しいローカル変数が作成されます。 非標準の代替として、
let
ブロックを宣言できます:
var i = 6; let (i = 0, j = 2) { /* Other code here */ } // prints 6 console.log(i);
この例では、変数
i
と
j
はブロック内にのみ存在します。 執筆時点では、Chromeは
let
の使用のみをサポートしています。
他の言語で
以下は、異なる言語でのスコープの実装の機能の比較表です。
物件 | Java | Python | Javascript | ご注意 |
---|---|---|---|---|
範囲 | 字句(ブロック) | レキシカル(関数、クラス、またはモジュール) | はい | JavaやCのようにはまったく機能しません。 |
ブロックスコープ | はい | いや | let
(ES6)と組み合わせて | Javaのようにはまったく機能しません。 |
育てる | いや | いや | はい | 変数、関数、および関数式を宣言するため。 |
機能
関数はJavaScriptの別の障害でもあります。 その理由は、Javaのような命令型言語はまったく異なる概念を使用しているためです。 JavaScriptは関数型プログラミング言語を指します。 確かに、それは純粋に機能的ではありませんが、それでも命令型が明確にトレースされており、可変性が推奨されています。 ただし、JavaScriptは関数呼び出しに外部から影響を与えることなく、関数型言語としてのみ使用できます。
JavaScriptでは、関数は
String
や
Number
などの他のデータ型と同様に処理できます。 それらは変数と配列に保存され、引数として他の関数に渡され、他の関数によって返されます。 プロパティを持つことができ、動的に変更することができます。これはすべてオブジェクトのおかげです。
多くのJavaScript初心者にとって驚くべきことは、ここでの関数がオブジェクトであることです。
Function
コンストラクターは
Function
オブジェクトを作成します。
var func = new Function(['a', 'b', 'c'], '');
これはほとんど同じです:
function func(a, b, c) { }
ほとんど-コンストラクターの使用は効率が悪いためです。 無名関数を生成し、そのコンテキストのクロージャーを作成しません。
Function
オブジェクトは常にグローバルスコープで作成されます。
Function
一種である
Function
、
Object
基づいています。 宣言した関数を解析すると、これがはっきりとわかります。
function test() {} // prints "object" console.log(typeof test.prototype); // prints function Function() { [native code] } console.log(test.constructor);
これは、関数にプロパティがあることを意味します。 それらのいくつかは、作成時に割り当てられます。 たとえば、
name
または
length
は、それぞれ関数定義の引数の名前と数を返します。
function func(a, b, c) { } // prints "func" console.log(func.name); // prints 3 console.log(func.length);
任意の関数を設定し、他のプロパティを自由に設定できます:
function test() { console.log(test.custom); } test.custom = 123; // prints 123 test();
他の言語で
異なる言語での関数実装の比較表:
物件 | Java | Python | Javascript | ご注意 |
---|---|---|---|---|
組み込み型としての機能 | ラムダ、Java 8 | はい | はい | |
コールバック/チームテンプレート | オブジェクト(またはJava 8のラムダ) | はい | はい | 関数(コールバック) |
動的作成 | いや | いや | eval
( Function
オブジェクト) | eval
はセキュリティ上の懸念を引き起こします; Function
オブジェクトは予期せず実行できます |
プロパティ | いや | いや | プロパティがある場合があります | 関数プロパティへのアクセスを制限できます |
短絡
JavaScriptは、クロージャーを導入した最初の主要なプログラミング言語です。 おそらくご存知のように、JavaとPythonにはクロージャーの単純化されたバージョンがありますが、それは外側のスコープから一部の値しか読み取ることができませんでした。 Javaでは、匿名のネストされたクラスはクロージャーに似た機能を提供します(いくつかの制限があります)。 たとえば、スコープでは、最終的なローカル変数のみを使用できます。 より正確には、それらの値を読み取ることができます。
JavaScriptは、外部変数と外部スコープ関数への完全なアクセス権を持っています。 ローカル定義を使用して、読み取り、書き込み、必要に応じて非表示にすることもできます。 この例は、最初の章で繰り返し提示されています。
さらに興味深いのは、クロージャーで作成された関数が、それが作成された環境を「記憶」していることです。 クロージャーと関数のネストを組み合わせて、外部関数が実行せずに内部関数を返すようにすることができます。 さらに、外部関数のローカル変数は、最後に宣言された変数の実行後、長時間、内部関数のクロージャーに保存できます。 これは非常に強力なツールですが、欠点が1つあります。JavaScriptアプリケーションの一般的なメモリリークの問題です。
上記の理解を深めるために、いくつかの例を見てみましょう。
function makeCounter () { var i = 0; return function displayCounter () { console.log(++i); }; } var counter = makeCounter(); // prints 1 counter(); // prints 2 counter();
makeCounter()
関数は、親環境との通信を維持する別の関数を作成して返します。
makeCounter()
実行は変数counterの割り当てで終了しましたが、ローカル変数iは
displayCounter
のクロージャーに格納されており、その内部でアクセスできます。
makeCounter()
再度実行すると、異なる初期値
i
新しいクロージャーが作成されます。
var counterBis = makeCounter(); // prints 1 counterBis(); // prints 3 counter(); // prints 2 counterBis();
makeCounter()
が引数を受け入れるようにすることもできます:
function makeCounter(i) { return function displayCounter () { console.log(++i); }; } var counter = makeCounter(10); // prints 11 counter(); // prints 12 counter();
外部関数の引数もクロージャーに保存されるため、ローカル変数を宣言する必要はありません。
makeCounter()
が呼び出される
makeCounter()
、私たちが設定した初期値が記憶され、そこから
makeCounter()
が取得されます。
クロージャは、名前空間、モジュール、プライベート変数、メモ化など、JavaScriptの多くの基本的なものにとって非常に重要です。 たとえば、これはオブジェクトのプライベート変数をモデル化する方法です。
function Person(name) { return { setName: function(newName) { if (typeof newName === 'string' && newName.length > 0) { name = newName; } else { throw new TypeError("Not a valid name"); } }, getName: function () { return name; } }; } var p = Person("Marcello"); // prints "Marcello" a.getName(); // Uncaught TypeError: Not a valid name a.setName(); // Uncaught TypeError: Not a valid name a.setName(2); a.setName("2"); // prints "2" a.getName();
このようにして、独自のセッターとゲッターを使用してプロパティ名のラッパーを作成できます。 ES 5では、プロパティのセッター/ゲッターを使用してオブジェクトを作成し、これらのプロパティへのアクセスを微調整できるため、これははるかに簡単になりました。
他の言語で
異なる言語でのクロージャー実装の比較表:
物件 | Java | Python | Javascript | ご注意 |
---|---|---|---|---|
短絡 | 匿名のネストされたクラスでは無効、読み取り専用 | ネストされた定義で無効、読み取り専用 | はい | メモリリーク |
メモ化テンプレート | 共有オブジェクトを使用する必要があります | おそらくリストまたは辞書を使用する | はい | より良い使用遅延コンピューティング |
名前空間/モジュールテンプレート | 必要なし | 必要なし | はい | |
プライベート属性テンプレート | 必要なし | 不可能 | はい | 誤解を招く可能性があります |
おわりに
そのため、この記事では、以前に他のプログラミング言語、特にJavaとCで働いていた開発者にとって混乱を招くことが多いJavaScriptの3つの機能について説明します。
• JavaScriptのスコープ
• 関数宣言と関数式
• letステートメントとletブロック