Home >Web Front-end >JS Tutorial >Understanding JavaScript Scoping & Hoisting (2)_javascript skills

Understanding JavaScript Scoping & Hoisting (2)_javascript skills

WBOY
WBOYOriginal
2016-05-16 15:31:171231browse

Scoping & Hoisting

var a = 1;

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

foo();

What results will the above code produce when run?

Although this is just a piece of cake for experienced programmers, I still follow the common ideas of beginners and describe it:

1. Created global variable a and defined its value as 1
2. Created function foo
3. In the function body of foo, the if statement will not be executed because !a will convert the variable a into a Boolean false value, that is, false
4. Skip the conditional branch, alert variable a, the final result should be output 1

Well, it seems like an impeccable reasoning, but what’s surprising is: the answer is actually 2! Why?

Don’t worry, I’ll explain it to you. First let me tell you that this is not a bug, but an (unofficial) feature of the JavaScript language interpreter. Someone (Ben Cherry) called this feature: Hoisting (There is no standard translation yet, the more common one is Promote).

Declarations and Definitions

In order to understand Hoisting, let’s first look at a simple situation:

var a = 1;

Have you ever thought about what exactly happens when the above code is run?
Do you know, as far as this code is concerned, which of the two statements "declare variable a" or "define variable a" is correct?
•The following example is called “declaring variables”:

var a;

•The following example is called "defining variables":

var a = 1;

•Declaration: It means that you claim the existence of something, such as a variable or a function; but you do not explain what such a thing is, you just tell the interpreter that such a thing exists;
•Definition: It means that you specify the specific implementation of something, such as what the value of a variable is, what the function body of a function is, and exactly express the meaning of such a thing.

To summarize:

var a; // This is a statement
a = 1; // This is the definition (assignment)
var a = 1; // Combine two into one: declare the existence of a variable and assign it a value

Here’s the key point: when you think you only did one thing (var a = 1), the interpreter actually decomposed this thing into two steps, one is the declaration (var a), and the other One is the definition (a = 1).

What does this have to do with Hoisting?

Going back to the confusing example at the beginning, let me tell you how the interpreter analyzes your code:

var a;
a = 1;

function foo() {
  var a;    // 关键在这里
  if (!a) {
    a = 2;
  }
  alert(a);   // 此时的 a 并非函数体外的那个全局变量
}

As shown in the code, the interpreter declares a new variable a after entering the function body, and regardless of the condition of the if statement, the new variable a will be assigned a value of 2. If you don't believe it, you can alert(a) outside the function body and then execute foo() to compare the results.

Scoping

Someone may ask: "Why not declare variable a within the if statement?"

Because JavaScript does not have block scope (Block Scoping), only function scope (Function Scoping), so if you see a pair of curly brackets {}, it means that a new scope has been generated, which is different from C!

When the parser reads the if statement, it sees that there is a variable declaration and assignment, so the parser will raise its declaration to the top of the current scope (this is the default behavior and cannot be changed), this The behavior is called Hoisting.

OK, everyone understands, do you understand...

Understanding does not mean that you will use it. Take the initial example, if I just want alert(a) to produce the 1, what should I do?

Create new scope

When

alert(a) is executed, it will search for the location of variable a. It will search upward (or outward) from the current scope to the top-level scope. If it cannot find it, it will report undefined.

Because in the sibling scope of alert(a), we declared the local variable a again, so it reports 2; so we can move the declaration of the local variable a downward (or inward), so that alert (a) It cannot be found.

Remember: JavaScript only has function scope!

var a = 1;

function foo() {
  if (!a) {
    (function() {    // 这是上一篇说到过的 IIFE,它会创建一个新的函数作用域
      var a = 2;    // 并且该作用域在 foo() 的内部,所以 alert 访问不到
    }());        // 不过这个作用域可以访问上层作用域哦,这就叫:“闭包”
  };
  alert(a);
};

foo();

你或许在无数的 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 的原文解释的更加详细,只不过是英文而已。我这篇是借花献佛,主要是更浅显的解释给初学者听,若要看更多的示例,请移步原作,谢谢。

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn