공부 정리/You Don't Know Js

[YDKJ] Scope & Closures - Limiting Scope Exposure

경적필패. 2023. 5. 27. 17:50
반응형

요약

이번 장에서는, 변수를 조직하기 위해 왜 다양한 스코프를 이용해야 되는지 알아본다.

 

Least Exposure

POLE(Principle of Least Privilege) => 최소권한 원칙

시스템의 구성요소가 최소한의 권한, 최소한의 접근권한, 최소한의 노출로 동작되도록 설계되어야 함을 나타냄.

그럴수록 하나의 구성요소의 결함 또는 실패가 전체 시스템에 미치는 영향이 적어짐

=> 그래서 블록을 사용하여 스코프를 써야 한다!!

 

왜 전역에 모든 변수를 배치하면 안 좋을까?

 

1. 이름충돌

프로그램의 다른 부분에서 이름이 충돌남!

 

2. 예상치 못한 작동

private한 함수를 사용하여 개인정보가 노출될 수 있음

 

3. 의도하지 않은 의존성

어떤 코드가 숫자배열에 의존하고 있고, 나중에 이 숫자배열을 손봐야 할 때 모든 코드를 수정해야 할 수 있음.

 

이러한 문제를 예방하기 위해 스코프 안에 변수를 사용하는 게 좋다 (let을 써서)

Scoping with Blocks

  • 모든 블록이 스코프가 되는 것은 아님

1. 객체 리터럴에서 중괄호는 스코프가 아님

2. 클래스에서 body 중괄호도 스코프가 아님

3. 함수의 중괄호는 스코프임

4. switch문의 스코프는 스코프가 아님

if (somethingHappened) {
    // this is a block, but not a scope

    {
        // this is both a block and an
        // explicit scope
        let msg = somethingHappened.message();
        notifyOthers(msg);
    }

    // ..

    recoverFromSomething();
}

=> 대부분의 개발자는 msg를 if블록으로 스코핑 하고 넘어갈 것임. 그러나 msg가 다른 데서 안 쓰인다면 이처럼 블록스코프 처리해 주는 게 POLE를 따르는 것이라 할 수 있음.

 

function getNextMonthStart(dateStr) {
    var nextMonth, year;

    {
        let curMonth;
        [ , year, curMonth ] = dateStr.match(
                /(\d{4})-(\d{2})-\d{2}/
            ) || [];
        nextMonth = (Number(curMonth) % 12) + 1;
    }

    if (nextMonth == 1) {
        year++;
    }

    return `${ year }-${
            String(nextMonth).padStart(2,"0")
        }-01`;
}
getNextMonthStart("2019-12-25");   // 2020-01-01

이처럼 curMonth가 쓰이는 곳에 한해서 스코프를 지정하는 것.

 

var and let

var은 js초기부터 함수전체에 속하는 변수를 나타내는 신호였음.

전역범위를 나타내고 싶을 때는 var을 사용하고, 블록범위를 나타내고 싶을 때 let을 사용하면 가독성이 좋음.

=> var을 지양하고 let을 사용하는 게 일반적이지만, 저자는 var 또한 여전히 사용가치가 있다고 주장함.

 

Where To let?

let을 어디다가 써야 할까?

선언이 블록범위라면 let을 쓰고, 함수 범위라면 var를 써라(저자의견)

 

function diff(x,y) {
    if (x > y) {
        // `tmp` is still function-scoped, but
        // the placement here semantically
        // signals block-scoping
        var tmp = x;
        x = y;
        y = tmp;
    }

    return y - x;
}

es6이전에는 let이 없었기 때문에 배치를 통해 블록범위를 나타냈음

 

What's the Catch?

try-catch는 es3에서 추가된 문법임.

try {
    doesntExist();
}
catch (err) {
    console.log(err);
    // ReferenceError: 'doesntExist' is not defined
    // ^^^^ message printed from the caught exception

    let onlyHere = true;
    var outerVariable = true;
}

console.log(outerVariable);     // true

console.log(err);
// ReferenceError: 'err' is not defined
// ^^^^ this is another thrown (uncaught) exception

여기서 catch안의 err는 블록스코프를 가지며, var 또한 유효함.

 

Function Declarations in Blocks (FiB)

블록 안의 함수는 어떠한 스코프를 가질까?

if (false) {
    function ask() {
        console.log("Does this run?");
    }
}
ask();

이 결과는 js환경에 따라 다르다....

대부분의 브라우저에서는 ask() 호출이 TypeError와 함께 실패한다.

왜냐하면 if문이 실행되지 않기 때문에 ask가 undefined 상태임.

 

 

if (true) {
    function ask() {
        console.log("Am I called?");
    }
}

if (true) {
    function ask() {
        console.log("Or what about me?");
    }
}

for (let i = 0; i < 5; i++) {
    function ask() {
        console.log("Or is it one of these?");
    }
}

ask();

function ask() {
    console.log("Wait, maybe, it's this one?");
}

 

 이 경우 함수들이 호이스팅 돼서 Wait,~~이 출력될 것 같지만, 아니다...(실제는 OR IS IT ONE~~ 출력)

이처럼 FIB는 이상한 에지 케이스가 많다.

즉, FIB의 모호성을 피하는 가장 좋은 방법은  FIB를 피하는 것임.

=> 항상 함수의 최상위 범위 OR 전역범위에 선언하라!!!!

 

이러한 예제들에서 주목할 것은, 함수 표현식이 아니라 함수선언을 IF문 내에 배치한 것.

즉 블록 안에서 function 선언을 피해라

프로그램이 올바르게 작동하고 있더라도 다른 js환경에서 문제가 발생할 수 있다.

FIB는 피하자

 

 

 

 

느낀 점

이 챕터를 읽으며 let과 var을 함께 쓰라는 저자의 말이 좀 참신했다. 그동안 정보를 그냥 받아들이고 있지는 않았는지 생각해 보게 되는 계기였음....

그리고 스코프와 블록의 미묘한 차이에 대해 배울 수 있었고

FIB는 생각도 못하고 있었는데 이 챕터에서 인사이트를 얻은 것 같다.

 

 

 

반응형