>웹 프론트엔드 >JS 튜토리얼 >JavaScript 범위 지정 및 호이스팅 이해(2)_javascript 기술

JavaScript 범위 지정 및 호이스팅 이해(2)_javascript 기술

WBOY
WBOY원래의
2016-05-16 15:31:171236검색

범위 지정 및 호이스팅

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 정의"라는 두 명령문 중 어느 것이 올바른지 알고 계십니까?
•다음 예제는 "변수 선언"이라고 합니다:

var a;

•다음 예를 "변수 정의"라고 합니다.

var a = 1;

•선언: 변수나 함수 등의 존재를 주장하지만 그것이 무엇인지 설명하지 않고 해석자에게 그러한 것이 존재한다고만 말합니다.

•정의: 변수의 값이 무엇인지, 함수의 본문이 무엇인지 등의 구체적인 구현을 지정하고, 그러한 것의 의미를 정확하게 표현한다는 의미입니다.

요약:

var a; // 문장입니다

a = 1; // 정의(할당)입니다
var a = 1; // 두 개를 하나로 결합: 변수의 존재를 선언하고 값을 할당합니다.

핵심 사항은 다음과 같습니다. 한 가지 작업(var a = 1)만 수행했다고 생각할 때 인터프리터는 실제로 이 작업을 두 단계로 분해합니다. 하나는 선언(var a)이고 다른 하나는 정의( a = 1).

이것이 호이스팅과 어떤 관련이 있나요?

처음의 혼란스러운 예로 돌아가서 인터프리터가 코드를 분석하는 방법을 알려드리겠습니다.

var a;
a = 1;

function foo() {
  var a;    // 关键在这里
  if (!a) {
    a = 2;
  }
  alert(a);   // 此时的 a 并非函数体外的那个全局变量
}
코드에서 볼 수 있듯이 인터프리터는 함수 본문을 입력한 후 새 변수 a를 선언하고 if 문의 조건과 관계없이 새 변수 a에 값 2가 할당됩니다. 믿을 수 없다면 함수 본문 외부에서 경고(a)를 한 다음 foo()를 실행하여 결과를 비교할 수 있습니다.

범위 지정

누군가는 "왜 if 문 내에서 변수 a를 선언하지 않습니까?"라고 물을 수 있습니다.

자바스크립트에는 블록 스코프(Block Scoping)가 없고 함수 스코프(Function Scoping)만 있기 때문에 중괄호({}) 쌍이 보인다면 C와는 다른 새로운 스코프가 생성되었다는 의미입니다. !

파서가 if 문을 읽을 때 변수 선언과 할당이 있음을 확인하므로 파서는 해당 선언을 현재 범위의 맨 위로 올립니다(이는 기본 동작이며 변경할 수 없습니다). 행동을 호이스팅(Hoisting)이라고 합니다.

좋아요, 다들 이해하고 계시죠...

이해한다고 해서 이를 사용한다는 의미는 아닙니다. 초기 예를 들어 보겠습니다. 단지 경고(a)가 1을 생성하도록 하려면 어떻게 해야 합니까?

새 범위 만들기

alert(a)가 실행되면 변수 a의 위치를 ​​검색합니다. 현재 범위에서 최상위 범위까지 검색합니다. 이를 찾을 수 없으면 정의되지 않음이 보고됩니다. .

alert(a)의 형제 범위에서 지역 변수 a를 다시 선언했기 때문에 2를 보고하므로 지역 변수 a의 선언을 아래쪽(또는 안쪽)으로 이동할 수 있습니다. 찾을 수 없습니다.

기억하세요: JavaScript에는 함수 범위만 있습니다!


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

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.