Skip to content

자바스크립트의 스코프 이해하기1

chchoing88 edited this page Apr 27, 2019 · 1 revision

자바스크립트의 스코프 이해하기

번역 : https://www.telerik.com/blogs/understanding-scope-in-javascript

스코프는 매우 중요합니다. 하지만 모호한 컨셉입니다. 올바르게 사용하면 좋은 디자인 패턴을 활용하고 나쁜 부작용을 피할 수 있습니다. 이 글에서는 자바 스크립트에서 다양한 유형의 범위를 분석하고 더 나은 코드를 작성하는 방법에 대해 자세히 설명합니다.

스코프의 간단한 정의는 컴파일러가 필요한 경우 변수와 함수를 찾는 위치입니다. 너무 쉽게 들리는가? 이 모든 것이 무엇인지 보자.

자바스크립트 인터프리터

스코프를 설명하기 전에 JavaScript 인터프리터와 다른 인터프리터에 미치는 영향에 대해 설명해야합니다. JavaScript 코드를 실행하면 인터프리터는 코드를 두 번 통과합니다.

첫 번째 코드 실행 (컴파일 실행이라고도 함)은 스코프에 가장 영향을 미칩니다. 인터프리터는 변수 및 함수 선언을 찾아 코드를 현재 스코프의 맨 위로 이동합니다. 오직 이동된 선언과 두번째 실행을 위해 할당은 남겨둔다는 것이 중요합니다.

이를 더 잘 이해하기 위해 간단한 코드 스 니펫을 사용 해보자.

'use strict'

var foo = 'foo';
var wow = 'wow';

function bar (wow) {
  var pow = 'pow';
  console.log(foo); // 'foo'
  console.log(wow); // 'zoom'
}

bar('zoom');
console.log(pow); // ReferenceError: pow is not defined

위 코드를 컴파일이 동작하고 나면 이렇게 바뀔 것이다.

'use strict'
// Variables are hoisted at the top of the current scope
var foo;
var wow;

// Function declarations are hoisted as-is at the top of the current scope
function bar (wow) {
  var pow;
  pow = 'pow';
  console.log(foo);
  console.log(wow);
}

foo = 'foo';
wow = 'wow';

bar('zoom');
console.log(pow); // ReferenceError: pow is not defined

여기서 이해하는데 중요한 것은 선언들이 현재 스코프의 가장 위로 끌어 올려졌다는 것이다. 이것이 스코프를 이해하는 결정적인 것이다. 이제 설명해보자.

예를 들어, 변수 pow는 부모 범위에서 선언되는 대신 범위가되기 때문에 함수 bar에서 선언되었습니다.

함수 bar의 매개 변수 wow또한 함수 범위에서 선언됩니다. 사실, 모든 함수 매개 변수는 함수 범위 내에서 암시 적으로 선언되므로, 9 행의 console.log (wow)는 왜 와우 대신 zoom을 출력합니다.

Lexical Scope

이제 JavaScript 인터프리터가 작동하는 방법을 살펴보고 호이스트에 대해 간략하게 소개 했으므로 범위를 더 자세히 파헤 칠 수 있습니다. 컴파일 시간 범위를 의미하는 어휘 Scope부터 시작해 보겠습니다. 즉, 범위(Scope)에 대한 결정은 컴파일 시간에 실제로 이루어진 것입니다. 이 글에서는이 규칙에 대한 예외 사항을 무시할 것입니다.이 규칙은 eval 또는 with를 사용하는 경우에만 발생합니다. 어떤 경우에도 사용해서는 안되기 때문입니다.

인터프리터의 두 번째 실행은 변수가 지정되고 함수가 실행되는 곳입니다. 위의 샘플 코드에서 bar ()가 12 행에서 실행됩니다. 인터프리터는 bar를 실행하기 전에 bar 선언을 찾아야하며, 현재 범위를 처음 살펴 봅니다. 이 시점에서 현재 범위는 전역 범위입니다. 첫 번째 실행 덕분에 파일의 맨 위에 bar가 선언되어 있으므로 통역사가 찾아서 실행할 수 있습니다.

우리가 8 행 console.log (foo)를 본다면, 인터프리터는이 줄을 실행하기 전에 foo의 선언을 찾아야합니다. 첫 번째로 다시 한 번, 전역 범위가 아닌 현재 scope의 function bar 범위를 살펴 보겠습니다. foo가 이 함수의 scope에서 선언 되었습니까? 아니다. 그러면 부모 범위까지 올라가서 거기에있는 선언을 찾아 보겠습니다. 함수의 상위 범위는 전역 범위입니다. foo는 전역인 범위에서 선언 되었습니까? 그렇습니다. 따라서 인터프리터가 그것을 실행할 수 있습니다.

요약하면, 어휘 범위는 첫 번째 실행 후에 범위가 결정되었음을 의미하며, 해석기가 변수 또는 함수 선언을 찾아야하는 경우 먼저 현재 범위를 살펴보고 상위 범위까지 길게 유지합니다 그것이 필요한 선언을 찾지 못했기 때문입니다. 갈 수있는 가장 높은 수준은 글로벌 범위입니다.

전역 범위에서 선언을 찾지 못하면 ReferenceError 오류가 발생합니다.

또한 인터프리터는 부모 범위를 살펴보기 전에 항상 현재 범위에서 선언을 찾고 있기 때문에 어휘 범위에서는 JavaScript에서 가변 쉐도잉 개념을 도입합니다. 즉, 현재 함수 범위에서 선언 된 변수 foo는 부모 범위에서 같은 이름의 변수를 가릴 (또는 숨길) 것입니다. 그림자가 무엇인지 더 잘 이해하기 위해 다음 코드를 살펴 보겠습니다.

'use strict'

var foo = 'foo';

function bar () {
  var foo = 'bar';
  console.log(foo);
}

bar();

위 코드의 출력은 foo 대신 bar입니다. 왜냐하면 6 행의 foo 변수 선언은 3 행의 foo 변수 선언을 가리기 때문입니다.

쉐도잉은 특정 변수를 마스크하여 특정 범위에서 액세스하지 못하게하려는 경우 유용 할 수있는 디자인 패턴입니다. 즉, 동일한 변수 이름을 사용하면 팀간에 더 많은 혼란을 야기 할 수 있으며 때때로 개발자가 변수가 실제로 가지고있는 것보다 다른 가치를 가졌다 고 생각할 수 있기 때문에 필자는 개인적으로 사용하지 않는 경향이 있습니다.

Function Scope

어휘 범위에서 보았듯이 인터프리터는 현재 범위에서 변수를 선언합니다. 즉, 함수에서 선언 된 변수는 함수 범위에서 선언됩니다. 이 범위는 함수 자체 및 해당 함수 내에서 선언 된 다른 함수로 제한됩니다.

함수 범위에서 선언 된 변수는 외부에서 액세스 할 수 없습니다. 이는 다음 코드에서 볼 수 있듯이 전용 속성을 만들고 함수 범위 내에서만 액세스 할 수있는 경우 매우 유용한 패턴입니다.

'use strict'

function convert (amount) {
   var _conversionRate = 2; // Only accessible in this function scope
   return amount * _conversionRate;
}

console.log(convert(5));
console.log(_conversionRate); // ReferenceError: _conversionRate is not defined

Block Scope

block scopefunction scope 와 유사하지만 함수대신에 _block_로 제한됩니다.

ES3에서 try / catch 문에있는 catch 절에는 block scope 가 있습니다. 즉, 자체 범위가 있음을 의미합니다. try 절에는 block scope 가 없으므로 catch 절만 사용한다는 점에 유의해야합니다. 코드 스니펫을 자세히 살펴 보도록하겠습니다.

'use strict'

try {
  var foo = 'foo';
  console.log(bar);
}
catch (err) {
  console.log('In catch block');
  console.log(err);
}

console.log(foo);
console.log(err);

앞의 코드는 bar에 접근하려고 할 때 5 번째 줄에 에러를 던질 것입니다. 그러면 인터프리터가 catch 절에 들어갑니다. 이것은 외부에서 접근 할 수없는err 변수를 그 범위에 선언 할 것입니다. 사실, 마지막 줄에 err의 값을 기록하려고하면 에러가 발생합니다 :console.log (err);. 이 코드의 정확한 출력은 다음과 같습니다.

In catch block
ReferenceError: bar is not defined
    (...Error stack here...)
foo
ReferenceError: err is not defined
    (...Error stack here...)

footry / catch 밖에서 어떻게 접근 할 수 있는지 주목하십시오. 그러나err은 그렇지 않습니다.

ES6에서 let 및 const 변수는 함수 범위 대신 현재 블록 범위에 암시 적으로 첨부됩니다. 즉,이 변수는 if 블록인지 블록인지 또는 함수인지에 관계없이 선언 된 블록으로 제한됩니다. 다음은이를보다 잘 보여주는 예제입니다.

'use strict'

let condition = true;

function bar () {
  if (condition) {
    var firstName = 'John'; // Accessible in the whole function
    let lastName = 'Doe'; // Accessible in the `if` only
    const fullName = firstName + ' ' + lastName; // Accessible in the `if` only
  }

  console.log(firstName); // John
  console.log(lastName); // ReferenceError
  console.log(fullName); // ReferenceError
}

bar();

letconst 변수는 우리가 최소 공개 원칙을 사용할 수 있도록 허용합니다. 즉, 가능한 한 가장 작은 범위에서만 변수에 액세스 할 수 있어야합니다. ES6 이전에 개발자들은 IIFE에서 'var'을 선언하여 스타일을 사용하는 경우가 많았지 만 이제는 let 및 const를 통해 ES6에서 기능적으로이를 적용 할 수 있습니다. 이 원칙의 주요 이점 중 하나는 변수에 대한 잘못된 액세스를 피하고 버그 가능성을 줄이는 것이며 가비지 수집기가 block 범위 밖에있을 때 이러한 사용되지 않는 변수를 정리할 수있게하는 것입니다.

IIFE

Immediately Invoked Function Expression (IIFE)은 함수가 새로운 블록 범위를 만들 수있게 해주는 매우 보편적 인 JavaScript 패턴입니다. IIFE는 단순히 인터프리터가 함수를 실행하자마자 호출하는 함수 표현식입니다. 다음은 IIFE의 예입니다.

'use strict'

var foo = 'foo';

(function bar () {
  console.log('in function bar');
})()

console.log(foo);

이 코드는 'bar'를 통해 명시 적으로 호출 할 필요없이 'bar'함수가 즉시 실행되기 때문에foo 전에`in function bar '를 출력합니다. 그 이유는 다음과 같습니다.

  • 여는 괄호 function 키워드 앞의 (는 _function 선언 _ 대신 _function 표현식 _이됩니다.
  • 마지막에 괄호`() '가있어서 _function expression_을 즉시 실행합니다.

이전에 보았 듯이, 이것은 외부 범위에서 변수를 숨기고 액세스를 제한하며 불필요한 변수로 외부 범위를 오염시키지 않도록합니다.

IIFE는 비동기 작업을 실행하고 IIFE의 범위에서 변수 상태를 보존하려는 경우에도 매우 유용합니다. 이것이 의미하는 바를 예를 들면 다음과 같습니다.

'use strict'

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log('index: ' + i);
  }, 1000);
}

이것이 0, 1, 2, 3, 4를 출력한다는 첫 번째 가정에도 불구하고 비동기 연산 (setTimeout)을 실행하는 this for 루프의 실제 결과는 다음과 같습니다.

index: 5
index: 5
index: 5
index: 5
index: 5

그 이유는 1000 밀리 초가 만료 될 때까지 for 루프가 완료되고 i 값이 실제로 5이기 때문입니다.

대신, 0, 1, 2, 3, 4 값을 출력하려면 다음과 같이 IIFE를 사용하여 원하는 범위를 유지해야합니다.

'use strict'

for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

이 샘플에서는 i의 값을 IIFE에 전달합니다. IIFE는 자체 범위를 가지며 더 이상 for 루프의 영향을받지 않습니다. 이 코드의 출력은 다음과 같습니다.

index: 0
index: 1
index: 2
index: 3
index: 4

결론

자바 스크립트에서 스코프에 대해 더 많이 논의 할 수 있지만, 범위가 무엇인지, 스코프의 종류가 다른지, 그리고이를 활용하기 위해 일부 디자인 패턴을 사용하는 방법에 대한 확실한 소개라고 생각합니다.

다음 기사에서는 JavaScript에서의 컨텍스트와 명시적인 바인딩 또는 하드 바인딩, 새로운 키워드의 의미에 대해 설명하고자합니다.

이것이 범위가 무엇인지 명확히하는 데 도움이 되었기를 바랍니다. 질문이나 의견이 있으면 주저하지 말고 답변을하십시오.

Clone this wiki locally