JavaScriptでの巻き上げを理解する
序章
このチュートリアルでは、有名な巻き上げメカニズムがJavaScriptでどのように発生するかを調査します。 飛び込む前に、巻き上げとは何かを理解しましょう。
巻き上げは、コードを実行する前に変数と関数宣言をスコープの最上位に移動するJavaScriptメカニズムです。
必然的に、これは、関数と変数が宣言されている場所に関係なく、スコープがグローバルかローカルかに関係なく、スコープの最上位に移動されることを意味します。
ただし、巻き上げメカニズムが宣言を移動するだけであるという事実は注目に値します。 割り当てはそのまま残されます。
コードに関数を記述する前に、なぜ関数を呼び出すことができたのか疑問に思ったことがある場合は、読み進めてください。
未定義vsReferenceError
本格的に始める前に、いくつかのことを検討しましょう。
console.log(typeof variable); // Output: undefined
これにより、最初の注意点がわかります。
JavaScriptでは、宣言されていない変数には、実行時にundefinedの値が割り当てられ、タイプもundefinedになります。
2番目のポイントは次のとおりです。
console.log(variable); // Output: ReferenceError: variable is not defined
JavaScriptでは、以前に宣言されていない変数にアクセスしようとすると、ReferenceErrorがスローされます。
変数を処理するときのJavaScriptの動作は、巻き上げのために微妙になります。 これについては、以降のセクションで詳しく説明します。
吊り上げ変数
以下はJavaScriptのライフサイクルであり、変数の宣言と初期化が行われる順序を示しています。
ただし、JavaScriptでは変数の宣言と初期化の両方を同時に行うことができるため、これが最もよく使用されるパターンです。
var a = 100;
ただし、バックグラウンドでは、JavaScriptが変数を宗教的に宣言してから初期化することを覚えておくことが重要です。
前に述べたように、すべての変数と関数の宣言は、それらのスコープのtopに引き上げられます。 また、変数宣言は、コードが実行される前に処理されることを追加する必要があります。
ただし、対照的に、 undeclared 変数は、それらを割り当てるコードが実行されるまで存在しません。 したがって、宣言されていない変数に値を割り当てると、割り当ての実行時にその値がグローバル変数として暗黙的に作成されます。 これは、宣言されていないすべての変数がグローバル変数であることを意味します。
この動作を示すために、以下を見てください。
function hoist() {
a = 20;
var b = 100;
}
hoist();
console.log(a);
/*
Accessible as a global variable outside hoist() function
Output: 20
*/
console.log(b);
/*
Since it was declared, it is confined to the hoist() function scope.
We can't print it out outside the confines of the hoist() function.
Output: ReferenceError: b is not defined
*/
これはJavaScriptが変数を処理する方法の偏心の1つであるため、関数またはグローバルスコープにあるかどうかに関係なく、常に変数を宣言することをお勧めします。 これは、インタプリタが実行時にそれらをどのように処理するかを明確に示しています。
ES5
var
キーワードvar
で宣言された変数のスコープは、その現在の実行コンテキストです。 これは、囲み関数か、関数の外部で宣言された変数の場合はグローバルのいずれかです。 これが何を意味するかを特定するために、いくつかの例を見てみましょう。
グローバル変数
console.log(hoist); // Output: undefined
var hoist = 'The variable has been hoisted.';
ログの結果はReferenceError: hoist is not defined
であると予想しましたが、代わりに、その出力はundefined
です。
なぜこれが起こったのですか?
この発見により、獲物の争いに近づくことができます。
JavaScriptは変数宣言を引き上げました。 上記のコードは、インタプリタにとって次のようになります。
var hoist;
console.log(hoist); // Output: undefined
hoist = 'The variable has been hoisted.';
このため、変数を宣言する前に変数を使用できます。 ただし、巻き上げられた変数は未定義の値で初期化されるため、注意が必要です。 最良のオプションは、使用する前に変数を宣言して初期化することです。
関数スコープ変数
上で見たように、グローバルスコープ内の変数はスコープの一番上に持ち上げられます。 次に、関数スコープの変数がどのように持ち上げられるかを見てみましょう。
function hoist() {
console.log(message);
var message='Hoisting is all the rage!'
}
hoist();
私たちの出力が何であるかについて、知識に基づいた推測をしてください。
ご想像のとおり、undefined
はその通りです。 あなたがそうしなかったとしても、心配しないでください、私たちはすぐにこれの底に到達します。
これは、インタプリタが上記のコードを表示する方法です。
function hoist() {
var message;
console.log(message);
message='Hoisting is all the rage!'
}
hoist(); // Ouput: undefined
スコープが関数hoist()
である変数宣言var message
は、関数の先頭に持ち上げられます。
この落とし穴を回避するには、変数を使用する前に、必ず宣言および初期化します。
function hoist() {
var message='Hoisting is all the rage!'
return (message);
}
hoist(); // Ouput: Hoisting is all the rage!
厳密モード
strict-modeとして知られるJavaScriptのes5バージョンのユーティリティのおかげで、変数の宣言方法にもっと注意を払うことができます。 strictモードを有効にすることで、宣言される前に変数の使用を許容しないJavaScriptの制限付きバリアントを選択します。
厳密モードでコードを実行する:
- インタプリタによって吐き出される明示的なスローエラーに変更することで、いくつかのサイレントJavaScriptエラーを排除します。
- JavaScriptエンジンが最適化を実行するのを困難にする間違いを修正します。
- JavaScriptの将来のバージョンで定義される可能性のあるいくつかの構文を禁止します。
ファイルまたは関数の前に次のように付けることで、厳密モードを有効にします
'use strict';
// OR
"use strict";
それをテストしてみましょう。
'use strict';
console.log(hoist); // Output: ReferenceError: hoist is not defined
hoist = 'Hoisted';
変数の宣言を見逃したと想定する代わりに、use strict
がReference error
を明示的にスローすることで、トラックで停止したことがわかります。 厳密に使用せずに試してみて、何が起こるかを確認してください。
ただし、厳密モードはブラウザによって動作が異なるため、本番環境で使用する前に、機能テストを徹底的に実行することをお勧めします。
ES6
ECMAScript 6 、ES6としても知られるECMAScript 2015は、この記事の執筆 2017年1月として、ECMAScript標準の最新バージョンであり、es5にいくつかの変更が加えられています。
私たちにとって興味深いのは、標準の変更がJavaScript変数の宣言と初期化にどのように影響するかです。
させて
始める前に、キーワードlet
で宣言された変数はブロックスコープであり、関数スコープではないことに注意してください。 これは重要ですが、ここで問題が発生することはありません。 ただし、簡単に言うと、変数のスコープは、宣言されている関数ではなく、宣言されているブロックにバインドされていることを意味します。
let
キーワードの動作を確認することから始めましょう。
console.log(hoist); // Output: ReferenceError: hoist is not defined ...
let hoist = 'The variable has been hoisted.';
以前と同様に、var
キーワードの場合、ログの出力はundefined
であると予想されます。 ただし、es6 let は宣言されていない変数を使用して親切に対応しないため、インタープリターはReference
エラーを明示的に吐き出します。
これにより、常に最初に変数を宣言することが保証されます。
ただし、ここではまだ注意が必要です。 次のような実装では、Reference error
ではなくundefined
の出力になります。
let hoist;
console.log(hoist); // Output: undefined
hoist = 'Hoisted'
したがって、注意を怠るには、変数を使用する前に、変数を宣言してから変数を値に割り当てる必要があります。
const
const
キーワードは、不変変数を許可するためにes6で導入されました。 つまり、一度割り当てられると値を変更できない変数。
const
では、let
と同様に、変数がブロックの先頭に持ち上げられます。
const
変数に付加された値を再割り当てしようとするとどうなるか見てみましょう。
const PI = 3.142;
PI = 22/7; // Let's reassign the value of PI
console.log(PI); // Output: TypeError: Assignment to constant variable.
const
は変数宣言をどのように変更しますか? 見てみましょう。
console.log(hoist); // Output: ReferenceError: hoist is not defined
const hoist = 'The variable has been hoisted.';
let
キーワードと同じように、undefined
で黙って終了する代わりに、インタープリターはReference error
を明示的にスローすることで私たちを救います。
関数内でconst
を使用する場合も同様です。
function getCircumference(radius) {
console.log(circumference)
circumference = PI*radius*2;
const PI = 22/7;
}
getCircumference(2) // ReferenceError: circumference is not defined
const
を使用すると、es6はさらに進んでいきます。 宣言して初期化する前に定数を使用すると、インタープリターはエラーをスローします。
私たちのリンターはまた、この重罪について私たちにすぐに知らせます:
PI was used before it was declared, which is illegal for const variables.
世界的に、
const PI;
console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
PI=3.142;
したがって、定数変数は、使用する前に宣言および初期化する必要があります。
このセクションのプロローグとして、実際、JavaScriptはes6letおよびconstで宣言された変数をホイストすることに注意することが重要です。 この場合の違いは、それらを初期化する方法です。 letおよびconstで宣言された変数は、実行の開始時に uninitialized のままですが、varで宣言された変数はの値で初期化されます]undefined。
巻き上げ機能
JavaScript関数は、大まかに次のように分類できます。
- 関数宣言
- 関数式
巻き上げが両方の関数タイプによってどのように影響を受けるかを調査します。
関数宣言
これらは次のような形で、完全に上に持ち上げられています。 これで、JavaScriptを使用して、関数を宣言する前に一見関数を呼び出すことができる理由を理解できます。
hoisted(); // Output: "This function has been hoisted."
function hoisted() {
console.log('This function has been hoisted.');
};
関数式
ただし、関数式は引き上げられません。
expression(); //Output: "TypeError: expression is not a function
var expression = function() {
console.log('Will this work?');
};
関数宣言と式の組み合わせを試してみましょう。
expression(); // Ouput: TypeError: expression is not a function
var expression = function hoisting() {
console.log('Will this work?');
};
上記のように、変数宣言var expression
は引き上げられていますが、関数への割り当ては引き上げられていません。 したがって、インタープリターはexpression
を関数ではなく変数と見なすため、TypeError
をスローします。
優先順位
JavaScriptの関数と変数を宣言するときは、いくつかの点に注意することが重要です。
- 変数の割り当ては関数宣言よりも優先されます
- 関数宣言は変数宣言よりも優先されます
関数宣言は変数宣言の上に上げられますが、変数割り当ての上には上げられません。
この動作がどのような影響を与えるかを見てみましょう。
関数宣言に対する変数の割り当て
var double = 22;
function double(num) {
return (num*2);
}
console.log(typeof double); // Output: number
変数宣言に対する関数宣言
var double;
function double(num) {
return (num*2);
}
console.log(typeof double); // Output: function
宣言の位置を逆にしても、JavaScriptインタープリターはdouble
を関数と見なします。
巻き上げクラス
JavaScriptクラスも、大まかに次のいずれかに分類できます。
- クラス宣言
- クラス式
クラス宣言
対応する関数と同じように、JavaScriptクラス宣言は引き上げられます。 ただし、評価されるまで初期化されないままです。 これは事実上、クラスを使用する前にクラスを宣言する必要があることを意味します。
var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: ReferenceError: Hobbit is not defined
class Hobbit {
constructor(height, weight) {
this.height = height;
this.weight = weight;
}
}
undefined
を取得する代わりに、Reference error
を取得することに気付いたと思います。 その証拠は、クラス宣言が引き上げられているという私たちの立場を主張するのに役立ちます。
リンターに注意を払っている場合は、便利なヒントが提供されます。
Hobbit was used before it is declared, which is illegal for class variables
したがって、クラス宣言に関する限り、クラス宣言にアクセスするには、最初に宣言する必要があります。
class Hobbit {
constructor(height, weight) {
this.height = height;
this.weight = weight;
}
}
var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: { height: 100, weight: 300 }
クラス式
対応する関数と同じように、クラス式は引き上げられません。
これは、クラス式の名前のない、または匿名のバリアントの例です。
var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor
var Polygon = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
名前付きクラス式の例を次に示します。
var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor
var Polygon = class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
それを行う正しい方法は次のようになります。
var Polygon = class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square);
警告
Javascript es6 let、const変数、およびクラスが実際にホイストされているか、大まかにホイストされているか、またはホイストされていないかについては、少し議論があります。 実際には吊り上げられているが初期化されていないと主張する人もいれば、まったく吊り上げられていないと主張する人もいます。
結論
これまでに学んだことを要約しましょう。
- es5 var を使用しているときに、宣言されていない変数を使用しようとすると、巻き上げ時に変数にundefinedの値が割り当てられます。
- es6 letおよびconstを使用している場合、宣言されていない変数を使用すると、実行時に変数が初期化されないままになるため、参照エラーが発生します。
したがって、
- 使用する前にJavaScript変数を宣言して初期化することを習慣にする必要があります。
- JavaScript es5でstrictモードを使用すると、宣言されていない変数を公開するのに役立ちます。
この記事がJavaScriptでの巻き上げの概念の良い入門書となり、JavaScript言語の微妙さに関する興味を刺激することを願っています。