Skip to content

Files

133 lines (117 loc) · 8.85 KB

Chapter3.md

File metadata and controls

133 lines (117 loc) · 8.85 KB

함수 vs 블록 스코프

키워드: 스코프 IIFE let const


  • 스코프는 버블이다. 무슨 말이냐면,
    • 스코프 검색은 항상 실행 시점에서 가장 안쪽 스코프에서 시작하여 최초 목표와 일치하는 대상을 찾으면 멈추고, 그 전까지는 바깥/위로 올라가면서 수행한다.
  • 함수만 버블을 만들까? 자바스크립트의 다른 자료 구조는 스코프 버블을 생성하지 못할까?
    • 결론부터 이야기하면, 함수는 결코 유일한 스코프 단위가 아니다.

함수 기반 스코프

일반 스코프에 숨기

  • 함수에 대한 전통적인 개념은 다음과 같다:
    1. 함수를 선언하고 그 안에 코드를 넣는다.
    2. 작성한 코드에서 임의 부분을 함수 선언문으로 감싼다. 이는 해당 코드를 숨기는 효과를 낸다.
  • 스코프를 이용해 숨기는 방식을 사용하는 이유는,
    • 소프트웨어 디자인 원칙인 '최소 권한의 원칙'과 관련이 있다. 이 원칙은 모듈/객체의 API와 같은 소프트웨어를 설계할 때 필요한 것만 최소한으로 남기고 나머지는 숨겨야 한다는 것이다.
    • 만일 모든 변수와 함수가 글로벌 스코프에 존재한다면 어느 중첩된 하위 스코프에서도 이들에 접근할 수 있다. 이는 최소의 원칙을 어기는 것이고, 코드를 적절하게 사용했을 때 접근할 필요가 없어 비공개로 남겨둬야 할 많은 변수나 함수를 노출시키게 된다.

충돌 회피

  • 변수와 함수를 스코프 안에 숨기는 것의 또 다른 장점은 같은 이름을 가졌지만 다른 용도를 가진 두 확인자가 충돌하는 것을 피할 수 있다는 점이다. 소프트웨어 설계를 하다 보면 자연스럽게 같은 확인자 이름을 사용하게 되므로 스코프를 이용해서 내부에 선언문을 숨기는 것이 가장 좋은, 그리고 유일한 선택지다.

글로벌 네임스페이스

  • 어떤 라이브러리는 글로벌 스코프에 하나의 고유한 이름을 가지는 객체 선언문을 생성한다. 이후 객체는 해당 라이브러리의 네임스페이스로 이용된다.
  • 네임스페이스를 통해 최상위 스코프의 확인자가 아니라 속성 형태로 라이브러리의 모든 기능이 노출된다.

모듈 관리

  • 좀더 현대적인 충돌 방지 옵션으로는 다양한 의존성 관리자를 이용한 모듈 접근법이 있다.
  • 모듈을 사용하면 어떤 라이브러리도 확인자를 글로벌 스코프에 추가할 필요 없이, 특정 스코프로부터 의존성 관리자를 이용한 다양한 명시적인 방법으로 확인자를 가져와 사용할 수 있다.

스코프 역할을 하는 함수

var a = 2;

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

foo();
console.log(a); // 2
  • 위의 방식은 이상적이지 않으며, 다음의 두 문제를 갖는다:
    1. foo()라는 이름의 함수를 선언해야 한다. 그저 또다른 값을 할당한 변수 a를 숨기고 호출하기 위해?!
    2. 또한 foo 함수를 직접 호출해야만 실제 감싼 코드를 실행할 수 있다.
  • 함수를 이름없이 선언하고 자동으로 실행할 수 있다면? 다행히도 그런 방법이 있다:
var a = 2;

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

console.log(a); // 2
  • 위에서 foo 함수는 함수 선언문이 아니라 표현식이다.
    • 선언문과 표현식의 중요한 차이는 함수 이름이 어디의 확인자로 묶이느냐에 있다. 첫번째에서는 함수 이름 foo가 함수를 둘러싼 스코프에 묶여있다. 반면 두번째에서 함수 이름 foo는 함수 자신의 내부 스코프에 묶였다.

익명 vs 기명

  • 일단 함수 선언문에서는 이름이 빠져서는 안된다.
  • 함수 표현식은 익명으로 만들 수 있다.
    • 익명 함수 표현식은 빠르고 쉽게 입력할 수 있어 많이 권장된다. 그러나 기억해야 할 몇 가지 단점도 있다 (이름이 없다는 것이 주는 단점):
      1. 익명 함수는 스택 추적시 표시할 이름이 없어서 디버깅이 더 어려울 수 있다.
      2. 이름 없이 함수 스스로 재귀 호출을 하려면 불행히도 폐기 예정인 arguments.callee 참조가 필요하다.
      3. 이름은 보통 쉽게 이해하고 읽을 수 있는 코드 작성에 도움이 되는데, 익명 함수는 이런 이름을 생략한다. 기능을 잘 나타내는 이름은 해당 코드를 그 자체로 설명하는데 도움이 된다.
    • 그러니까 결론적으로, 함수 표현식을 사용할 땐 이름을 항상 쓰는 것이 가장 좋다

함수 표현식 즉시 호출하기

var a = 2;

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

console.log(a); // 2
  • 위의 예시 foo 함수에서 (1) 함수를 둘러싼 첫번째 ()는 함수를 표현식으로 바꾸고, (2) 두번째 ()는 함수를 실행시킨다.
  • 이런 패턴은 굉장히 흔해서 개발자 커뮤니티에서는 몇년 전 이것을 부르는 즉시 호출 함수 표현식 IIFE라는 용어를 정하기도 했다.
    • Immediately Invoked Function Expression

스코프 역할을 하는 블록

  • 이미 가장 앞에서 이야기했지만, 함수가 유일한 스코프 단위가 아니다: 블록 스코프가 있다.
    • 블록 스코프의 목적은, 변수를 최대한 사용처 가까이에서 최대한 작은 유효 범위를 갖도록 선언하는 것이다.
    • 앞서 말한 '최소 권한 노출의 원칙'을 확장하여 정보를 함수 안에 숨기고, 나아가 정보를 코드 블록 안에 숨기기 위한 도구다.
  • with 문 안에 생성된 객체는 바깥 스코프에 영향을 주지 않고 존재하는데 - 지양해야 할 구조다.
  • try/catch에서 catch 부분에서 선언된 변수는 catch 블록 스코프에만 속한다.

let

  • ES6에서 let 키워드가 도입됐다.
  • let 키워드는 선언된 변수를 둘러싼 아무 블록의 스코프에 붙인다.
  • let을 이용해 변수를 현재 블록에 붙이는 것은 약간 비명시적이다.
  • let을 사용한 선언문은 속하는 스코프에서 호이스팅 효과를 받지 않는다. 호이스팅이 뭐냐면,
    • 호이스팅은 선언문이 어디에서 선언됐든 속하는 스코프 전체에서 존재하는 것처럼 취급되는 작용을 말한다.

가비지 콜렉션 Garbage Collection

  • 블록 스코프는 엔진에게 어떤 변수가 더는 필요 없다는 사실을 더 명료하게 알려준다. 예를 들어,
var bigData = { ... }; // 아주 큰 데이터라고 치자
process(bigData);

var btn = document.getElementById("my_btn");
btn.addEventListener("click", function click(evt) {
  console.log("clicked!");
});
  • 위의 예시에서 보면, process 함수가 실행된 이후, 아주 큰 bigData 변수는 더 이상 쓸모가 없다. 버튼의 클릭 이벤트 핸들러에서도 사용되지 않는다.
    • 그러므로 아래와 같이 블록 스코프를 이용하면, 엔진에게 bigData가 더는 필요없다는 걸 알려줄 수 있다.
      {
        let bigData = { ... };
        process(bigData);
      }
      
      var btn = document.getElementById("my_btn");
      btn.addEventListener("click", function click(evt) {
        console.log("clicked!");
      });
    • 위와 같이 명시적으로 블록을 선언하여 변수의 영역을 한정하는 것은 효과적인 코딩 방식이다.

const

  • ES6에서는 let과 함께 const 키워드도 추가됐다.
  • 이 역시 블록 스코프를 생성하지만, 다른 점이라면 선언한 값이 고정되는 상수라는 점이다.
    • 따라서 선언 후 그 값을 변경하려고 하면 에러가 발생한다.

정리

  1. 함수는 스코프를 이루는 가장 흔한 단위이다.
  2. 함수 안에 선언된 변수, 함수 등은 다른 스코프로부터 숨겨진 것이다.
  3. 함수가 유일한 스코프 단위가 아니다. 블록 스코프가 있다.
  4. try ... catch 문에서 catch문은 블록 스코프를 갖는다.
  5. let으로 임의의 코드 블록 안에 변수를 선언할 수 있게 됐다.
    • 그러나 많이들 착각하는 것과 달리 블록 스코프는 var 함수 스코프를 완전히 대체할 수 없다. 호이스팅이 되는지 안되는지 여부에 차이가 있다.

느낀점

  • 1권이 거의 끝나간다. 이 책 내용이 최신의 내용으로 업그레이드 됐다면 특히 이번 단원의 내용이 많이 달라졌지 않을까 생각이 들었다.
  • 한번 읽으면서 눈에 들어오는 부분에 밑줄을 치고, 이후에 그 내용 위주로 마크다운에 정리를 했더니 짧게 두번 반복해서 그런지 내용이 머리에 더 잘 들어왔다.
  • 입춘이다! 벌써 봄. 봄이 한창일 때 이 스터디가 끝날 텐데 기대가 된다.