티스토리 뷰

책이름: You don't know JS : 타입과 문법, 스코프와 클로저
주관적으로 머리에 남기고 싶은 부분들만 정리하였습니다. 자세한 내용은 책을 참고하시는게 좋습니다. 

5.4.1 너무 이른 변수 사용

ES6는 임시 데드 존 TDZ(Temporal Dead Zone)이라는 새로운 개념을 도입 

TDZ는 아직 초기화를 하지 않아 변수를 참조할 수 없는 코드 영역

  • ES6의 let 블록 스코핑이 대표적인 예시
{
a = 2; //ReferenceError
let a;
}

-> a가 let a 선언에 의해 초기화되기 전 a = 2 할당문이 블록스코프에 있는 변수 a에 접근하였으나 a는 아직 TDZ 내부에 있으므로 에러를 발생시킨다.

재밌는 사실🙄 typeof 연산자는 선언되지 않은 변수 앞에 붙여도 오류는 나지 않는데 TDZ 참조 시에는 이러한 안전장치가 없다(TDZ 내에 위치하여 접근할 수 없으니 typeof 적용조차 불허되는 것입니다.). 

5.5 함수인자

TDZ 관련 에러는 ES6 디폴트 인자 값에서도 적용된다.

  • 함수의 기본 인자값으로 쳐지는 ES6 디폴트 인자 값은 해당 함수를 호출할 때 인자를 넘기지않거나 undefined를 전달했을 때 적용됩니다. 
var b = 3;
function foo(a = 42, b = a + b + 5){
// ~ 
}

두 번째 할당문에서 좌변 b는 아직 TDZ에 남아있는 b를 참조하려고 하기 때문에(더 바깥쪽에서 끌어오지 못하고)에러를 던집니다. 그러나 이 시점에서 인자 a는 TDZ를 밟고 간 이후여서 문제가 없습니다. 

Q. b와 a는 동일 선상에 있는데 a가 왜 TDZ를 거쳤나? 함수 인자의 디폴트 값은 마치 하나씩 좌 -> 우측 순서로 let 선언을 한 것과 동일하게 처리됩니다. 그래서 일단 무조건 TDZ에 속하게 된다. 
function foo(a = 42, b = a + 1){
console.log(a,b);
}

foo(); 42 43
foo(undefined); // 42, 43
foo(5); // 5 6
foo(void 0, 7); // 42 7
foo(null); // null 1 -> 추가 설명: a + 1에서 null은 0으로 강제 형변환된 것

아래는 arguments 배열(ES6 이후부터 비권장 요소)에 대한 추가적인 설명이다. 

  • arguments 배열이라는 것을 사용해본 적이 없어서 잘 몰랐으나 ES6이전까지 유용하게 쓰인 방법으로 알아두면 좋을 거 같다. 
//ES6 디폴트 인자 값
//아무런 인자도 넘기지 않았을 때 디폴트 인자 값이 a, b에 각각 적용되었지만 args배열에는 원소가 하나도 없다.
//그런데 undefined 인자를 명시적으로 넘기면 args배열에도 값이 undefined인 원소가 추가되는데 여기에 해당하는 디폴트 인자 값과는 다르다.

function foo(a = 42, b = a + 1) {
  console.log(arguments.length, a, b, arguments[0], arguments[1]);
}
foo();
foo(10);
foo(10, undefined);
foo(10, null);

// 0 42 43 undefined undefined
// 1 10 11 10 undefined
// 2 10 11 10 undefined
// 2 10 null 10 null

//ES6 디폴트 인자 값이 args배열 슬롯과 이에 대응하는 인자 값 간의 불일치를 초래하는 것은 사실이지만 이전 버전
//ES5에서도 똑같은 불일치는 교묘하게 발생한다.
{
  function foo(a) {
    a = 42;
    console.log(arguments[0]);
  }
  foo(2); //42 연결된다
  foo(); // undefined 연결되지 않는다
}
// arguments 배열은 비 권장 요소( ES6 부터는 ...레스트 인자를 권장)
// arguments 배열은 ES6 이전까지 함수에 전달된 인자들을 배열 형태로 추출할 수 있는 유일한 방법이었다.
// 주의사항인 인자와 이 인자에 해당하는 argument 슬롯을 동시에 참조하지 말 것 만 준수하면 안전하다.
{
  function foo(a) {
    console.log(a + arguments[1]); //안전
  }
  foo(10, 32); //42
}

5.6 try ... finally

finally 절의 코드는 어떤 일이 있어도 반드시 실행되고 다른 코드로 넘어가기 전에 try 이후부터(catch가 있으면 catch 다음부터) 항상 실행된다. finally 절은 다른 블록 코드에 상관없이 무조건 실행돼야하는 콜백함수 같다. 

 

만약 try 절에 return 문이 있으면 어떻게 될까? 그 값을 반환받은 호출부 코드는 finally 이전에 실행될까 아니면 이후에 실행될까? 

{
  function foo() {
    try {
      return 42;
    } finally {
      console.log('hello iam finally');
    }
    console.log('실행될 리 없어');
  }
  console.log(foo());
}
//결과는 아래와 같다.
//hello iam finally 
//42

-> foo함수의 완료값은 42로 세팅되고, try 절의 실행이 종료되면서 곧바로 finally 절로 넘어간다.

 

try 안에 throw가 있어도 비슷하다.

{
  function foo() {
    try {
      throw 42;
    } finally {
      console.log('안녕하세요? 파이널리 입니다.');
    }
    console.log('여긴 출력되지않아여');
  }
  console.log(foo());
}
// 안녕하세요? 파이널리입니다.
// Uncaught 42
{
    function foo() {
      try {
        throw 42;
      }catch(e){
        console.error(e);
      } finally {
        console.log('안녕하세요? 파이널리 입니다.');
      }
      console.log('하이하이');
    }
    foo();
  }
  // 42 
  // 안녕하세요? 파이널리입니다. 
  // 하이하이

만약 finally 절에서 예외가 던져지면, 이전의 실행 결과는 모두 무시한다.

즉, 이전에 try 블록에서 생성한 완료값이 있어도 완전히 사라진다. 

 

continue나 break같은 비선형(Nonlinear)제어문 역시 return과 throw와 비슷하게 작동한다. 

{
  for (let i = 0; i < 10; i++) {
    try {
      continue;
    } finally {
      console.log(i);
    }
  }
}
//0 1 2 3 4 5 6 7 8 9

->continue문 때문에 console.log(i)문은 루프 순회 끝 부분에서 실행된다. 그러나 i ++로 인덱스가 수정되기 직전까지 꾸준히 실행되므로 1 ...10이 아니라 0 ...9까지가 콘솔에 표시된다. 

{
  for (let i = 0; i < 10; i++) {
    try {
      console.log(`컨티뉴문 전에 ${i}`);
      continue;
    } finally {
      console.log(i);
    }
  }
}
//0 1 2 3 4 5 6 7 8 9
finally 절의 return 은 그 이전에 실행된 try나 catch절의 return을 덮어쓰는 능력을 가지고 있다. 단 반드시 명시적으로 return 문을 써야 한다.!

보통 함수에서는 return을 생략해도 return; 또는 return undefined; 한 것으로 치지만 finally 안에서 return 을 빼면 이전의 return 을 무시하지 않고 존중한다. 

😓사실상 return 을 취소해버리는 fianlly + 레이블 break 조합의 코드는 피하자. (어려움)

5.7 Switch

if...else if..else 문의 사슬을 짧게해주는 switch 문

: switch 표현식의 평가 결과를 각 case 표현식의 값들(단순값일 수도 있고 표현식일수도 있음) 과 매치한다. 

쭉 내려가다 매치된 case 절의 코드를 실행하기 시작하여 break문을 만나기 전이나 switch 블록의 끝까지 계속 실행한다. 

주의사항: 

  • ==(느슨한 비교) 를 써도 switch문은 엄격하게 매치한다. 그래서 case 표현식의 평가 결과가 truthy일지라도 엄격히 true가 아닐 경우(강제 변환) 매치는 실패한다. e.g. 표현식에 &&이나 || 같은 "논리 연산자"를 사용할 때 문제가 된다. 
  • default 절은 옵션이지만 맨 끝에 쓰지 않을 경우(관습적으로 우리는 맨 끝에 default절을 썼습니다.), defalut 에도 break가 없을 때 이후 코드가 계속 실행된다.

 

댓글