JavaScript 실행 메커니즘 - 변수 승격(자세한 예)

WBOY
풀어 주다: 2022-03-11 10:58:14
앞으로
1899명이 탐색했습니다.

이 글은 javascript변수 프로모션에 대한 관련 지식을 제공합니다. 변수 프로모션은 무엇이며 왜 존재합니까? 모두에게 도움이 되기를 바랍니다.

JavaScript 실행 메커니즘 - 변수 승격(자세한 예)

관련 권장사항: javascript 학습 튜토리얼

먼저 코드를 살펴보겠습니다. 다음 코드의 출력은 무엇이라고 생각하시나요?

showName()
console.log(myname)
var myname = '极客时间'
function showName() {
    console.log('函数showName被执行');
}
로그인 후 복사
로그인 후 복사

JavaScript 개발을 사용해본 프로그래머라면 JavaScript가 순서대로 실행된다는 점을 알아야 합니다. 이 논리에 따라 이해하면 다음과 같습니다.

  • 첫 번째 줄을 실행할 때 showName 함수가 아직 정의되지 않았기 때문에 실행 시 오류가 보고되어야 합니다.
  • 두 번째 줄도 실행될 때 변수가 myname은 아직 정의되지 않았으므로 오류도 보고됩니다.

그러나 실제 실행 결과는 아래와 같이 그렇지 않습니다.
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
1번째 줄은 "function showName이 실행되었습니다"를 출력하고, 2번째 줄은 "undefine"을 출력하는데, 이는 앞서 상상했던 순차 실행과는 조금 다릅니다!

위의 실행 결과를 통해 함수나 변수를 정의하기 전에 사용할 수 있다는 것을 이미 알고 있어야 합니다. 그러면 정의되지 않은 변수나 함수를 사용하면 JavaScript 코드가 계속 실행될 수 있습니까? 이를 확인하기 위해 아래와 같이 라인 3에서 myname 변수의 정의를 삭제할 수 있습니다.

showName()
console.log(myname)
function showName() {
    console.log('函数showName被执行');
}
로그인 후 복사

그런 다음 이 코드가 다시 실행되면 JavaScript 엔진이 오류를 보고하고 결과는 다음과 같습니다.
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
위의 두 코드 실행 결과에서 다음 세 가지 결론을 도출할 수 있습니다.

  • 실행 프로세스 중에 선언되지 않은 변수를 사용하면 JavaScript 실행에서 오류가 보고됩니다.
  • 변수를 정의하기 전에 사용해도 오류는 발생하지 않지만, 정의 당시의 값이 아닌 변수의 값이 정의되지 않은 상태가 됩니다.
  • 함수 정의 전에 사용하면 오류가 없으며 함수가 올바르게 실행됩니다.

첫 번째 결론은 이해하기 쉽습니다. 변수가 정의되어 있지 않기 때문에 JavaScript 코드가 실행될 때 변수를 찾을 수 없으므로 JavaScript에서 오류가 발생합니다.

그러나 두 번째와 세 번째 결론은 매우 혼란스럽습니다.

  • 변수와 함수를 정의하기 전에 사용할 수 있는 이유는 무엇입니까? 이는 JavaScript 코드가 한 줄씩 실행되지 않음을 나타내는 것 같습니다.
  • 이렇게 변수와 함수의 처리 결과가 다른 이유는 무엇인가요? 예를 들어 위의 실행 결과에서 미리 사용한 showName 함수를 사용하면 완전한 결과를 출력할 수 있지만, 정의에 사용된 "geek time" 값이 아니라 미리 사용된 myname 변수의 값이 정의되지 않았습니다.

변수 호이스팅

이 두 가지 문제를 설명하려면 먼저 변수 호이스팅이 무엇인지 이해해야 합니다.

하지만 변수 승격을 소개하기 전에 먼저 다음 코드를 통해 JavaScript의 선언과 할당이 무엇인지 살펴보겠습니다.

var myname = '极客时间'
로그인 후 복사

이 코드는 두 줄의 코드로 구성되어 있다고 생각하시면 됩니다.

var myname    //声明部分
myname = '极客时间'  //赋值部分
로그인 후 복사

아래 그림과 같습니다.
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
위는 변수의 선언과 할당입니다. 그러면 선언과 할당을 살펴보겠습니다.

function foo(){
  console.log('foo')
}

var bar = function(){
  console.log('bar')
}
로그인 후 복사

첫 번째 함수 foo는 완전한 함수 선언입니다. 즉, 두 번째 함수가 먼저 변수 bar를 선언한 다음 function(){console.log를 선언합니다. ('bar')}는 bar에 할당됩니다. 직관적인 이해를 위해 다음 그림을 참고하시면 됩니다.
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
이제 선언과 할당 연산을 이해했으니 변수 승격이 무엇인지 이야기해 보겠습니다.

변수 승격이란 JavaScript 엔진이 JavaScript 코드 실행 중에 변수 선언 부분과 함수 선언 부분을 코드 시작 부분으로 승격시키는 "동작"을 말합니다. 변수가 승격된 후에는 변수에 대한 기본값이 설정됩니다. 이 기본값은 익숙한 undefed입니다.

구현을 시뮬레이션해 보겠습니다.

/*
* 变量提升部分
*/// 把变量 myname提升到开头,// 同时给myname赋值为undefinedvar myname = undefined// 把函数showName提升到开头function showName() {
    console.log('showName被调用');}/*
 * 可执行代码部分
*/showName()console.log(myname)// 去掉var声明部分,保留赋值语句myname = '极客时间'
로그인 후 복사

변수 승격 효과를 시뮬레이션하기 위해 아래와 같이 코드를 다음과 같이 조정했습니다.
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
그림에서 볼 수 있듯이 두 가지 주요 변경 사항이 있습니다. 원래 코드로 수정:

  • 第一处是把声明的部分都提升到了代码开头,如变量 myname 和函数 showName,并给变量设置默认值 undefined;
  • 第二处是移除原本声明的变量和函数,如var myname = '极客时间’的语句,移除了 var 声明,整个移除 showName 的函数声明。

通过这两步,就可以实现变量提升的效果。你也可以执行这段模拟变量提升的代码,其输出结果和第一段代码应该是完全一样的。

通过这段模拟的变量提升代码,相信你已经明白了可以在定义之前使用变量或者函数的原因——函数和变量在执行之前都提升到了代码开头

JavaScript 代码的执行流程

从概念的字面意义上来看,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,正如我们所模拟的那样。但,这并不准确。实际上变量和函数声明在代码里的位置是不会改变的,而且是在编译阶段被 JavaScript 引擎放入内存中。对,你没听错,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进入执行阶段。大致流程你可以参考下图:
JavaScript 실행 메커니즘 - 변수 승격(자세한 예)

1. 编译阶段

那么编译阶段和变量提升存在什么关系呢?

为了搞清楚这个问题,我们还是回过头来看上面那段模拟变量提升的代码,为了方便介绍,可以把这段代码分成两部分。

第一部分:变量提升部分的代码。

var myname = undefined
function showName() {
    console.log('函数showName被执行');
}
로그인 후 복사

第二部分:执行部分的代码。

showName()
console.log(myname)
myname = '极客时间'
로그인 후 복사
로그인 후 복사

下面我们就可以把 JavaScript 的执行流程细化,如下图所示:

JavaScript 실행 메커니즘 - 변수 승격(자세한 예)
从上图可以看出,输入一段代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。

执行上下文是 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。

关于执行上下文的细节,我会在下一篇文章《08 | 调用栈:为什么 JavaScript 代码会出现栈溢出?》做详细介绍,现在你只需要知道,在执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容,比如上面代码中的变量 myname 和函数 showName,都保存在该对象中。

你可以简单地把变量环境对象看成是如下结构:

VariableEnvironment:
     myname -> undefined, 
     showName ->function : {console.log(myname)
로그인 후 복사

了解完变量环境对象的结构后,接下来,我们再结合下面这段代码来分析下是如何生成变量环境对象的。

showName()
console.log(myname)
var myname = '极客时间'
function showName() {
    console.log('函数showName被执行');
}
로그인 후 복사
로그인 후 복사

我们可以一行一行来分析上述代码:

  • 第 1 行和第 2 行,由于这两行代码不是声明操作,所以 JavaScript 引擎不会做任何处理;
  • 第 3 行,由于这行是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myname 的属性,并使用 undefined 对其初始化;
  • 第 4 行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储到堆 (HEAP)中,并在环境对象中创建一个 showName 的属性,然后将该属性值指向堆中函数的位置(不了解堆也没关系,JavaScript 的执行堆和执行栈我会在后续文章中介绍)。

这样就生成了变量环境对象。接下来 JavaScript 引擎会把声明以外的代码编译为字节码,至于字节码的细节,我也会在后面文章中做详细介绍,你可以类比如下的模拟代码:

showName()
console.log(myname)
myname = '极客时间'
로그인 후 복사
로그인 후 복사

好了,现在有了执行上下文和可执行代码了,那么接下来就到了执行阶段了。

2. 执行阶段

JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行地执行。下面我们就来一行一行分析下这个执行过程:

  • 当执行到 showName 函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以 JavaScript 引擎便开始执行该函数,并输出“函数 showName 被执行”结果。
  • 接下来打印“myname”信息,JavaScript 引擎继续在变量环境对象中查找该对象,由于变量环境存在 myname 变量,并且其值为 undefined,所以这时候就输出 undefined。
  • 接下来执行第 3 行,把“极客时间”赋给 myname 变量,赋值后变量环境中的 myname 属性值改变为“极客时间”,变量环境如下所示:
VariableEnvironment:
     myname -> "极客时间", 
     showName ->function : {console.log(myname)
로그인 후 복사

好了,以上就是一段代码的编译和执行流程 。

代码中出现相同的变量或者函数怎么办?

现在你已经知道了,在执行一段 JavaScript 代码之前,会编译代码,并将代码中的函数和变量保存到执行上下文的变量环境中,那么如果代码中出现了重名的函数或者变量,JavaScript 引擎会如何处理?

我们先看下面这样一段代码:

function showName() {
    console.log('极客邦');
}
showName();
function showName() {
    console.log('极客时间');
}
showName();
로그인 후 복사

在上面代码中,我们先定义了一个 showName 的函数,该函数打印出来“极客邦”;然后调用 showName,并定义了一个 showName 函数,这个 showName 函数打印出来的是“极客时间”;最后接着继续调用 showName。那么你能分析出来这两次调用打印出来的值是什么吗?

我们来分析下其完整执行流程:

  • 首先是编译阶段。遇到了第一个 showName 函数,会将该函数体存放到变量环境中。接下来是第二个 showName 函数,继续存放至变量环境中,但是变量环境中已经存在一个 showName 函数了,此时,第二个 showName 函数会将第一个 showName 函数覆盖掉。这样变量环境中就只存在第二个 showName 函数了。
  • 接下来是执行阶段。先执行第一个 showName 函数,但由于是从变量环境中查找 showName 函数,而变量环境中只保存了第二个 showName 函数,所以最终调用的是第二个函数,打印的内容是“极客时间”。第二次执行 showName 函数也是走同样的流程,所以输出的结果也是“极客时间”。

综上所述,一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。

总结

好了,今天就到这里,下面我来简单总结下今天的主要内容:

  • JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
  • 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。
  • 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。

相关推荐:javascript学习教程

위 내용은 JavaScript 실행 메커니즘 - 변수 승격(자세한 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:csdn.net
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿