[YOU DON'T KNOW JS] 8. 함수 vs블록 스코프

함수 vs블록 스코프

함수 기반 스코프

function foo(a){
    var b= 2;
    //some code
    function bar(){
        // ...
    }
    //more code
    var c = 3;
}

만약 console.log(a,b,c) 찍으면 모두 ReferenceError 발생할것

함수 스코프는 모든 변수가 함수에 속하고 함수 전체(심지어 중첩된 스코프에서도)에 걸쳐 사용되며 재사용된다는 개념을 확고하게 한다. 이런 디자인 접근법은 상당히 유용하고 자바스크립트 변수의 ‘동적 특성을 완전히 살려 다른 타입의 값을 필요에 따라 가져올 수 있지만, 스코프 전체에서 변수가 살아있다는 점이 예상치 못한 문제를 일으킬 수 있다.

일반 스코프에 숨기

function doSomething(a){
    b = a + doSomethingElse( a * 2 );
    console.log(b * 3);
}

function doSomethingElse(a){
    return a- 1;
}

var b;

do Somethong(2); // 15

위 코드 보다는

function doSomething(a){
    function doSomethingElse(a){
        return a- 1;
    }
    var b;
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}
doSomething(2); 

이게 좋다! 안에다가 같이 하지만 ,이건 어디까지나 자바스크립트이기 떄문에 가능한 이야기!!

충동 회피

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

function foo(){
    function bar(a){
        i = 3;
        console.log(a + i);
    }
    
    for ( var i = 3; i<10; i++){
        bar ( i * 2);
    }
}

foo();

bar() 내부의 대입문 “i = 3”은 예기치 않게 foo()에서 for반복문을 위해 선언된 변수 i의 값을 변경한다. 그 결과, 이코드는 무한 반복에 빠진다.

3으로 고정되서!!!

글로벌 ‘네임 스페이스’

var MyReallyCoolLibrary = {
    awesome: "stuff",
    doSomething: function(){
        //...
    },
    doAnotherThing: function(){
        // ...
    }
};

글로벌 스코프에서 변수 충돌이 특히 일어나기 쉬운 경우에 대해 알아보자. 내부/비공개 함수와 변수가 적절하게 숨겨져 있지 않은 여러 라이브러리를 한 프로그램에서 불러오면 라이브러리들은 서로 쉽게 충돌한다.

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

모듈 관리

스코프 역할을 하는 함수

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

먼저, 코드를 감싼 함수는 ‘function …‘이 아니라 (… 시작함)

위 코드의 함수는 보통의 선언문이 아니라 함수 표현식으로 취급된다.

선언문과 표현식을 구분하는 가장 쉬운 방법은 ‘function’이라는 단어가 구문에서 어디에 위치하는가를 살펴보면 된다.(한줄에서가 아니라 하나의 독립 구문에서 봐야 한다). ‘function’이 구문의 시작 위치에 있다면 함수 선언문이고, 다른 경우는 함수 표현식이다.

여기서 볼 수 있는 함수 선언문의 함수 표현식의 중요한 차이는 함수 이름이 어디의 확인자로 묶이느냐에 관련이 있다.

익명 vs 기명

익명 함수 표현식

setTimeout( function(){
    console.log("I waited 1 second!");
}, 1000);

익명 함수 표현식의 몃가지 기억할 단점이 있다.

  1. 익명 함수는 스택 추적시 표시할 이름이 없어서 디버깅이 더 어려울 수 있다.
  2. 이름 없이 함수 스스로 재귀 호출을 하려면 불행히도 폐기 예정인 arguments.callee 참조가 필요하다. 자기 참조가 필요한 또 다른 예로는 한 번 실행하면 해제되는 이벤트 처리 함수가 있다.
  3. 이름은 보통 쉽게 이해하고 읽을 수 있는 코드 작성에 도움이 되는데, 익명 함수는 이런 이름을 생략한다. 기능을 잘 나타내는 이름은 해당 코드를 그 자체로 설명하는 데 도움이 된다.

함수 표현식 즉시 호출

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

( )로 함수를 감싸면 함수는 표현식으로 바꾸는데, “(function foo( ) { }) ()”처럼 마지막에 또 다른 ( )를 붙이면 함수를 실행할 수 있다. 함수를 둘러싼 첫 번째 ( )는 함수를 표현식으로 바꾸고, 두 번쨰 ( )는 함수를 실행 시킨다.

실행 시키면

3

2

바로 동작함!

이런 패턴은 굉장히 흔해서 즉시, 호출, 함수, 표현식 즉시 호출 함수 표현식(IIFE) 이라는 용어를 정했다.

var a = 2;
(function IIFE(def){
    def(window);
})(function def(global){
    var a =3;
    console.log(a);
    console.log(global.a);
})

함수 표현식 def는 코드 후반부에 정의되어 코드 전반부에 정의된 IIFE함수에 (def라는 이름의)인자로 넘겨진다. 결국, 인자 함수 def가 호출되고 window가 global인자로 넘겨진다.

스코프 역할을 하는 블록

let

const

Wrap up

자바스크립트에서 함수는 스코프를 이루는 가장 흔한 단위다. 다른 함수 안에서 선언된 변수와 함수는 본질적으로 다른 ‘스코프’로부터 ‘숨겨진’것이다. 이는 좋은 소프트웨어를 위해 적용해야할 디자인 원칙이다.

그러나 함수는 결코 유일한 스코프 단위가 아니다. 블록 스코프는 함수만이 아니라 임의의 코드 블록에 변수와 함수가 속하는 개념이다.

ES3부터 시작해서 try/catch 구조의 catch부분은 블록 스코프를 가진다. ES6에서는 키워드 let이 추가되어 임의의 코드 블록 안에 변수를 선언할 수 있게 되었다.

“if( ) { let a = 2; }”에서 변수 a는 if문의 { } 블록 스코프에 자신을 붙인다.

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