序章

このチュートリアルでは、有名な巻き上げメカニズムが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のライフサイクルであり、変数の宣言と初期化が行われる順序を示しています。

variable-hoisting

ただし、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の制限付きバリアントを選択します。

厳密モードでコードを実行する:

  1. インタプリタによって吐き出される明示的なスローエラーに変更することで、いくつかのサイレントJavaScriptエラーを排除します。
  2. JavaScriptエンジンが最適化を実行するのを困難にする間違いを修正します。
  3. JavaScriptの将来のバージョンで定義される可能性のあるいくつかの構文を禁止します。

ファイルまたは関数の前に次のように付けることで、厳密モードを有効にします

'use strict';

// OR
"use strict";

それをテストしてみましょう。

'use strict';

console.log(hoist); // Output: ReferenceError: hoist is not defined
hoist = 'Hoisted'; 

変数の宣言を見逃したと想定する代わりに、use strictReference 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関数は、大まかに次のように分類できます。

  1. 関数宣言
  2. 関数式

巻き上げが両方の関数タイプによってどのように影響を受けるかを調査します。

関数宣言

これらは次のような形で、完全に上に持ち上げられています。 これで、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の関数と変数を宣言するときは、いくつかの点に注意することが重要です。

  1. 変数の割り当ては関数宣言よりも優先されます
  2. 関数宣言は変数宣言よりも優先されます

関数宣言は変数宣言の上に上げられますが、変数割り当ての上には上げられません。

この動作がどのような影響を与えるかを見てみましょう。

関数宣言に対する変数の割り当て

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クラスも、大まかに次のいずれかに分類できます。

  1. クラス宣言
  2. クラス式

クラス宣言

対応する関数と同じように、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変数、およびクラスが実際にホイストされているか、大まかにホイストされているか、またはホイストされていないかについては、少し議論があります。 実際には吊り上げられているが初期化されていないと主張する人もいれば、まったく吊り上げられていないと主張する人もいます。

結論

これまでに学んだことを要約しましょう。

  1. es5 var を使用しているときに、宣言されていない変数を使用しようとすると、巻き上げ時に変数にundefinedの値が割り当てられます。
  2. es6 letおよびconstを使用している場合、宣言されていない変数を使用すると、実行時に変数が初期化されないままになるため、参照エラーが発生します。

したがって、

  1. 使用する前にJavaScript変数を宣言して初期化することを習慣にする必要があります。
  2. JavaScript es5でstrictモードを使用すると、宣言されていない変数を公開するのに役立ちます。

この記事がJavaScriptでの巻き上げの概念の良い入門書となり、JavaScript言語の微妙さに関する興味を刺激することを願っています。