자바스크립트에서 함수, 블록 스코프란

Jin·2022년 3월 1일
0

Javascript

목록 보기
7/22

스코프는 컨테이너 또는 바구니 역할을 하는 일종의 '버블'이며 변수나 함수가 그 안에서 선언됩니다. 이 컨테이너는 경계가 분명하게 중첩되고, 그 경계는 개발자가 코드를 작성할 때 결정됩니다.

함수 기반 스코프

스코프 안에 있는 모든 변수와 함수는 그 스코프에 속합니다.

function foo(a) {
	let b = 2;
    
	function bar() {
		...
	}

	let c = 3;
}

foo()의 스코프 버블은 변수 a, b, c와 함수 bar를 포함합니다. 즉, 스코프 안에 있는 모든 변수와 함수는 그 스코프 버블에 속합니다.

함수 스코프는 모든 변수가 함수에 속하고 함수 전체에 걸쳐 사용되며 재사용됩니다.

일반 스코프에 숨기

스코프를 이용해 변수와 함수를 숨기는 방식을 사용하는 이유는 소프트웨어 디자인 원칙인 '최소 권한 노출의 원칙'과 관련이 있습니다.

이 원칙은 모듈/객체의 API와 같은 소프트웨어를 설계할 때 필요한 것만 최소한으로 남기고 나머지는 숨겨야 한다는 원칙입니다.

function doSomething(a) {
  function doSmethingElse(a) {
    return a - 1;
  }

  let b;
  b = a + doSmethingElse(a * 2);
  console.log(b * 3);
}

doSomething(2); // 15

변수 b와 함수 doSomethingElse는 외부에서 접근할 수 없어서 더는 바깥의 영향을 받지 않고, 오직 doSomething 함수만이 이들을 통제합니다. 이런 디자인은 비공개로 해야 할 내용을 확실하게 비공개로 두기 때문에 좋은 코드입니다.

변수와 함수를 스코프 안에 숨기는 것의 또 다른 장점은 같은 이름을 가졌지만 다른 용도를 가진 두 변수나 함수가 충돌하는 것을 피할 수 있다는 것입니다.

function foo() {
  function bar(a) {
    i = 3;
    console.log(a + i);
  }

  for (var i = 0; i < 10; i++) {
    bar(i * 2);
  }
}

foo();

위의 코드에서 i = 3은 예기치 않게 for 반복문을 위해 선언된 변수 i의 값을 변경합니다. 따라서, 이 코드는 무한 루프에 빠집니다. var i = 3; 으로 변경하면 이 문제를 해결할 수 있습니다.

스코프를 이용해서 내부에 선언문을 숨기는 것은 가장 좋은, 그리고 유일한 선택지입니다.

스코프 역할을 하는 함수

var a = 2;

function foo() {
  var a = 3;
  console.log(a); // 3
}
foo();

console.log(a); // 2

이 방식은 작동하기는 하지만 결코 이상적인 방식은 아닙니다. foo()를 선언해야만 합니다. 이는 foo라는 이름으로 둘러싸인 스코프 (여기서는 글로벌 스코프)를 오염시킨다는 의미입니다.

함수를 이름 없이 선언하고 자동으로 실행하게 한다면 더 이상적입니다.

var a = 2;

(function foo() {
  var a = 3;
  console.log(a); // 3
})();

console.log(a);

여기서 가장 큰 차이점은 함수를 ()로 감싸고 그것 뒤에 다시 ()을 했다는 것입니다. 이렇게 되면 이 코드에서 함수는 보통의 선언문이 아니라 함수 표현식으로 취급되어 즉시 자동으로 호출됩니다. 즉, 함수 안의 코드들이 함수를 호출해야만 실행되는 것이 아닌, 바로 실행되는 코드가 되는 것입니다.

이제 앞선 두 코드를 비교해봅시다.

첫번째 코드는 함수 이름 foo가 함수를 둘러싼 스코프에 묶이고, foo()롤 직접 호출하였습니다.

두번째 코드는 함수 이름 foo가 함수를 둘러싼 스코프에 묶이는 대신 함수 자신의 내부 스코프에 묶였습니다. 즉, 여기서 foo는 바깥 스코프에서 발견되지 않는 것입니다.

이처럼 함수 이름 foo를 자기 내부에 숨기면 함수를 둘러싼 스코프를 불필요하게 오염시키지 않을 수 있습니다.

스코프 역할을 하는 블록

for (var i = 0; i < 10; i++) {
  console.log(i);
}

변수 i는 for 반복문만을 위해 선언되고 사용될 목적으로 선언되었습니다. (var 특성상 바깥 스코프에서도 사용이 가능하지만) 블록 스코프의 목적이 바로 이것입니다. 변수를 최대한 사용처 가까이에서 최대한 작은 유효 범위를 갖도록 선언하는 것입니다.

블록 스코프는 앞서 언급한 '최소 권한 노출의 원칙'을 확장하여 정보를 함수 안에 숨기고, 나아가 정보를 코드 블록 안에 숨기기 위한 도구입니다.

try/catch

try/catch 문 중 catch 부분에서 선언된 변수는 catch 블록 스코프에 속합니다.

가장 유명한 변수가 err입니다. 이 변수는 오직 catch 문 안에만 존재하므로 다른 곳에서 참조하면 오류가 발생합니다.

let

let은 var같이 변수를 선언하는 다른 방식입니다.

키워드 let은 선언된 변수를 둘러싼 가장 가까운 블록의 스코프에 붙입니다. 즉, 명시적이진 않지만 let은 선언한 변수를 위해 해당 블록 스코프를 사용합니다.

여기서 잠깐 let에 대해 추가적으로 설명하자면,

호이스팅은 선언문이 어디에서 선언됐든 속하는 스코프 전체에서 존재하는 것처럼 취급되는 작용을 말합니다.

그러나, let을 사용한 선언문은 속하는 스코프에서 호이스팅 효과를 받지 않습니다.

const

let과 마찬가지로 선언 즉시 둘러싸고 있는 가장 가까운 스코프에 붙게 되지만 상수값으로써 값을 변경할 수 없습니다.

가비지 콜렉션

블록 스코프가 유용한 또 다른 이유는 메모리를 회수하기 위한 클로저 그리고 가비지 콜렉션과 관련이 있습니다.

function doSomething(data) {
  // do something
}

{
  let something = {...};
  process(something);
}

var btn = document.getElementById("myButton");
btn.addEventListener("click", () => {
  console.log("clicked!");
})

이런 식으로 something을 선언하고 process 함수를 호출하는 부분을 블록으로 만들어서 스코프를 명확히 해주면 자바스크립트 엔진에게 something이라는 변수가 더는 필요없다는 사실을 더 명료하게 알려줍니다.

명시적으로 블록을 선언하여 변수의 영역을 한정하는 것은 효과적인 코딩 방식이므로 익혀두면 좋습니다.

블록 스코프는 함수 스코프를 완전히 대체할 수 없습니다. 두 기능은 공존하며 개발자들은 함수 스코프와 블록 스코프 기술을 같이 사용할 수 있어야 하고 그래야 합니다. 상황에 따라 더 읽기 쉽고 유지 보수가 쉬운 코드를 작성하기 위해 두 기술을 적절한 곳에 사용하는 것이 좋은 코드를 만들 수 있습니다.

profile
배워서 공유하기

0개의 댓글