ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript のスコープとホイスティングを理解する (2)_JavaScript スキル

JavaScript のスコープとホイスティングを理解する (2)_JavaScript スキル

WBOY
WBOYオリジナル
2016-05-16 15:31:171231ブラウズ

スコープとホイスト

var a = 1;

function foo() {
  if (!a) {
    var a = 2;
  }
  alert(a);
};

foo();

上記のコードを実行するとどのような結果が生成されますか?

これは経験豊富なプログラマーにとっては朝飯前ですが、それでも初心者の一般的な考えに従って説明します。

1. グローバル変数 a を作成し、その値を 1 として定義します
2. 関数 foo
を作成しました 3. foo の関数本体では、!a が変数 a をブール値 false、つまり false
に変換するため、if ステートメントは実行されません。 4. 条件分岐をスキップし、変数 a を警告します。最終結果は 1

として出力されます。

これは完璧な推論のように見えますが、驚くべきことに、答えは実際には 2 です。なぜ?

心配しないでください、私が説明します。最初に言っておきますが、これはバグではなく、JavaScript 言語インタープリターの (非公式) 機能であり、誰か (Ben Cherry) はこの機能を ホイスティング (標準的な翻訳はまだありません。より一般的な翻訳は 宣伝 です)。

宣言と定義

ホイスティングを理解するために、まず簡単な状況を見てみましょう:

var a = 1;

上記のコードが実行されると正確に何が起こるか考えたことがありますか?

このコードに関する限り、「変数 a を宣言する」と「変数 a を定義する」という 2 つのステートメントのどちらが正しいかわかりますか?
•次の例は「変数の宣言」と呼ばれます:

変数;

•次の例は「変数の定義」と呼ばれます:

var a = 1;

•宣言: 変数や関数などの何かの存在を主張することを意味しますが、それが何であるかを説明せず、そのようなものが存在することをインタープリターに伝えるだけです。 •定義: 変数の値が何であるか、関数の関数本体が何であるかなど、何かの具体的な実装を指定し、そのようなものの意味を正確に表現することを意味します。


要約すると、

var a; // これはステートメントです

a = 1; // これが定義 (代入)

var a = 1; // 2 つを 1 つに結合します: 変数の存在を宣言し、それに値を割り当てます

ここが重要なポイントです: 1 つのこと (var a = 1) だけを行ったと思った場合、インタプリタは実際にはこれを 2 つのステップに分解します。1 つは宣言 (var a)、もう 1 つは定義 ( a = 1)。

これはホイスティングと何の関係がありますか?

冒頭のわかりにくい例に戻り、インタプリタがコードをどのように分析するかを説明します。

var a;
a = 1;

function foo() {
  var a;    // 关键在这里
  if (!a) {
    a = 2;
  }
  alert(a);   // 此时的 a 并非函数体外的那个全局变量
}
コードに示されているように、インタープリターは関数本体に入った後に新しい変数 a を宣言し、if ステートメントの条件に関係なく、新しい変数 a には値 2 が割り当てられます。信じられない場合は、関数本体の外側でalert(a)を実行し、foo()を実行して結果を比較できます。

スコープ

「if ステートメント内で変数 a を宣言しないのはなぜですか?」と尋ねる人もいるかもしれません。

JavaScript にはブロック スコープ (Block Scoping) がなく、関数スコープ (Function Scoping) しかないため、中括弧 {} のペアが表示された場合は、新しいスコープが生成されたことを意味し、C とは異なります。 !

パーサーは if ステートメントを読み取ると、変数の宣言と代入があることを認識するため、パーサーはその宣言を現在のスコープの先頭に引き上げます (これはデフォルトの動作であり、変更できません)。この動作はホイスティングと呼ばれます。

わかった、みんなわかった、わかった...

理解したからといって、それを使用できるわけではありません。最初の例を見てみましょう。alert(a) で 1 を生成したいだけの場合、どうすればよいでしょうか?

新しいスコープの作成

alert(a) が実行されると、変数 a の場所が検索されます。現在のスコープから最上位のスコープまで上方向 (または外側) に検索され、見つからない場合は、未定義と報告されます。 。

alert(a) の兄弟スコープでローカル変数 a を再度宣言したため、2 が報告され、ローカル変数 a の宣言を下方向 (または内側) に移動できるため、alert (a)見つかりません。

覚えておいてください: JavaScript には関数スコープしかありません。


你或许在无数的 JavaScript 书籍和文章里读到过:“请始终保持作用域内所有变量的声明放置在作用域的顶部”,现在你应该明白为什么有此一说了吧?因为这样可以避免 Hoisting 特性给你带来的困扰(我不是很情愿这么说,因为 Hoisting 本身并没有什么错),也可以很明确的告诉所有阅读代码的人(包括你自己)在当前作用域内有哪些变量可以访问。但是,变量声明的提升并非 Hoisting 的全部。在 JavaScript 中,有四种方式可以让命名进入到作用域中(按优先级):

1.语言定义的命名:比如 this 或者 arguments,它们在所有作用域内都有效且优先级最高,所以在任何地方你都不能把变量命名为 this 之类的,这样是没有意义的
2.形式参数:函数定义时声明的形式参数会作为变量被 hoisting 至该函数的作用域内。所以形式参数是本地的,不是外部的或者全局的。当然你可以在执行函数的时候把外部变量传进来,但是传进来之后就是本地的了
3.函数声明:函数体内部还可以声明函数,不过它们也都是本地的了
4.变量声明:这个优先级其实还是最低的,不过它们也都是最常用的

另外,还记得之前我们讨论过 声明 和 定义 的区别吧?当时我并没有说为什么要理解这个区别,不过现在是时候了,记住:

Hosting 只提升了命名,没有提升定义

这一点和我们接下来要讲到的东西息息相关,请看:

函数声明与函数表达式的差别

先看两个例子:

function test() {
  foo();

  function foo() {
    alert("我是会出现的啦……");
  }
}

test();
function test() {
  foo();

  var foo = function() {
    alert("我不会出现的哦……");
  }
}

test();

同学,在了解了 Scoping & Hoisting 之后,你知道怎么解释这一切了吧?

在第一个例子里,函数 foo 是一个声明,既然是声明就会被提升(我特意包裹了一个外层作用域,因为全局作用域需要你的想象,不是那么直观,但是道理是一样的),所以在执行 foo() 之前,作用域就知道函数 foo 的存在了。这叫做函数声明(Function Declaration),函数声明会连通命名和函数体一起被提升至作用域顶部。

然而在第二个例子里,被提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。

尾记:Ben Cherry 的原文解释的更加详细,只不过是英文而已。我这篇是借花献佛,主要是更浅显的解释给初学者听,若要看更多的示例,请移步原作,谢谢。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。