ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ES6 + 기초 2
    코-드 스피츠/es6 2019. 1. 22. 16:44



    https://www.youtube.com/watch?v=q9j6XLOQYeA&list=PLBA53uNlbf-tKPtxR85LmXFYk0pNF8-Og&index=6 에서 발췌




    흐름제어 flow control


    바르게 좋은 제어문을 짜는 것은 매우 어렵다.

    제어문을 잘못짜는 이유 중 하나는 제어문이 뭔지 잘 모르기 때문이다.


    기본적인 flow control


    코드는 보통 위에서 아래로 흐른다. 그런 흐름 속에서 Jump가 가능하다. goto문이 있다. 고전적으로는 line number로 이동시켰으나 현재는 lable을 이용한다. goto대신에 lable의 이름으로 jump를 시킨다. lable identifier는 변수의 식별자 규칙과 같은 이름 규칙을 갖는다. 숫자로만 시작할 수 없고 _, $, 또는 알파벳으로 시작해서 뒤에는 숫자를 붙일 수 있다. 이 식별자 뒤에 colon만 붙이면 코드의 어떤 위치라도 lable이 된다. 


    break문은 현재의 lable의 컨텍스트에서 탈출할 때 쓴다. 따라서 break뒤에 어디로 탈출할 것인지 lable이름을 써준다. colon을 쓰지 않는다.


    continue문 역시 동일한 방식으로 탈출한다. colon을 쓰지 않는다.


    label identifier:

    break label; 

    continue label; // break label과 함께 lable을 생략하면 undefined (named?) lable 이라고 한다. 


    ---------------------------------------------------------------------------------------------------


    const con = document.getElementById('log');

    const log = (...arg) => con.innerHTML += '<br>' + args.join(' ');


    레이블은 함수 스코프를 탄다.

    () => {

    ABC:

    } // ok


    {

    ABC:

    } // x


    레이블이 가리킬 수 있는 영역을 lable range라고 한다. lable range에 의해 해당 레이블로 이동하고 어디로 갈 지 알게 된다.


    ABC:

    for (const i of [1,2,3,4]) {

    if (i === 3) break ABC;

    }


    for (const i of [1,2,3,4]) {

    if (i === 3) break;

    } // 자동 레이블이 생성되어져서 break문에 lable이름이 없으면 자동레이블이 있는 경우에만 작동. 자동레이블은 iteration set이라는 상황(for문같은)과 lable set이라는 상황에서 발생한다. 


    abc: {

    log('a');

    break;

    log('b');

    }

    log('c'); // 오류를 뿜는다. 왜냐면 iteration set이 아니기 때문. iteration set이 아니기 때문에 자동으로 되어 있는 break;를 쓸 수 없다. auto lable은 iteration set에서만 발동된다.



    abc: 
    {

    log('a');

    break abc;

    log('b');

    }

    log('c'); // 제대로 작동. abc 아래의 {}가 lable set. lable이 가리키고 있는 영역이다. 


    현재 문법은 아래로만 점프할 수 있다.

    예외 - 레이블 점프만이 서브루틴에서 제어를 위가 아닌 아래로 보낸다.

    레이블 블록에서는 continue를 쓸 수 없다. continue는 iteration set에서만 쓸 수 있다.


    abc: 
    {

    log('a'); // mandatory

    if (location.href.includes('.com')) break abc; // 함수의 return과도 같은 효과.

    log('b'); // optional

    } // 특히 es6에서는 블록안에 별도의 지역변수 공간을 만들 수 있다. 

      // 따라서 흐름제어를 위해서(return을 쓰기위해) lable 대신 함수를 쓸 필요가 없게 되었다. 

    log('c');


    자스 엔진은 자스 파일의 텍스트 코드를 제일 먼저 받아들인다. 이 파일을 파싱한다. 파싱하고 메모리에 적재하고 실행을 해간다. script 엔진은 파일 자체가 실행하는 게 아니라 파싱한 결과물을 갖고 실행한다. 이러한 실행하는 형태로 바꿀 때 대부분의 statement들은 객체화되어 record(고유명사)로 바뀐다(마치 html이 파싱되어서 하나의 엘레멘트가 DOM 엘레멘트 객체로 바뀌는 것처럼) 이 record를 실행해가며 처리한다. 그 때 lable, break, continue처럼 분기를 일으키며 흐름제어에 관여하는 것들을 completion record라고 부른다. 



    <Function Scope>

    lable의 scope는 function scope에 되어 있다. function scope내에서는 같은 이름을 쓸 수 없다. 중첩된 블록을 사용해도 쓸 수 없다. 같은 이름의 lable을 쓰려면 함수로 분기해야만 가능하다. 


    변수는 두가지 특성이 있다.


    첫번째, 변수는 특정 시점에 태어났다가 사라진다(라이프사이클을 가진다)

    두번째, 변수는 접근제어라는 특성이 있다. 어떤 변수에는 접근할 수 없지만 다른 변수에는 접근할 수 있다던가 하는 메모리에 격벽을 세워서 보호해주는 기능이 있다. 이것을 scope라고 부른다. 이때의 scope는 접근 권한이라는 뜻이다. 

    따라서 변수를 만들면 scope와 lifecycle을 갖게 된다.

    이것이 언어 일반론이다.


    자스에서는 scope가 변수의 lifecycle을 의미한다. lifecycle이라는 말 대신에 scope라는 말을 쓴다는 것이다. 따라서 어떤 변수가 태어나서 죽는 때까지, 살아있는 범위를 나타낼 때 scope라는 용어를 쓴다. 그러면 변수의 scope에 따라서 변수가 얼만큼 살아 있는지 누가 접근할 수 있는지가 결정되는데, 자스의 scope는 라이프사이클 뿐만이 아닌 접근권한까지를 포함하는 개념이다. 


    대표적인 변수


    함수의 인자

    함수의 지역변수 

    블록스코프 변수

    멤버변수 - 클래스의 인스턴스의 속성(구조체가 깨지거나 인스턴스가 깨질 때까지(객체가 살아있을 때까지) 보존된다)

    자유변수 - 함수나 인스턴스나 자기를 기준으로 자기가 원래 알 수 있는 권한이 없는 변수들. 함수로 예를 들면 자기가 갖고 있는 인자와 지역변수 외에는 모두 자유변수 이다. 클래스의 메소드라면 this로부터 시작하는 멤버변수, 함수의 인자, 지역변수외에는 모두 자유변수이다. 

    이러한 내가 쓸 수 있는 자유변수들이 실제로 사용될 수 있는 공간(블록)을 클로저라고 한다. 


    let a = 3;

    const f = () => {

    log(a);

    } // a는 f에 대한 자유변수 이다. f의 함수 바디{}가 a라는 자유변수의 클로저가 된다. 클로저는 일반명사가 아니라 이런식으로 '~에 대한 클로저' 라는 식으로 쓰인다. 


    let a = 3;

    const f = () => {

    let a = 2; 

    log(a);    // 쉐도잉으로 인해 2가 찍힌다. 이것은 lable에도 마찬가지로 적용되어서

    }                  // 바깥쪽의 abc lable이 있고 {} 안에 abc lable이 있으면 {}쪽이 적용된다.



    modern language에서는 함수 바깥 레이블로는 점프할 수 없다.


    k:{

    let a = 3;

    const f = () => {

    let a = 2; 

    k: {

    break k;

    log(37); // 내부의 레이블 k가 인식되어 찍히지 않는다.

    }

    log(a);

    }

    }


    k1:{

    let a = 3;

    const f = () => {

    let a = 2; 

    k: {

    break k1; // 외부의 레이블 k1이 f의 스코프 안에서 인식되지 않아서 syntax error가 발생.

    log(37);   // 런타임 에러가 발생하지 않고 그전에 죽는다. 따라서 상대적으로 안전.

    }                   // 현대 언어는 함수 스코프 안의 레이블을 바깥쪽 레이블로 못넘어가게 격벽을 세워준다. 

    log(a);          // 이것을 레이블 스코프라고 한다.

    }

    }



    lable은 함수 스코프이다. lable의 이름이 같은 함수 스코프 내에서는 같은 이름을 가질 수 없다.



    Block Range



    lable 뒤에 블록을 주면 블록 레인지 전체가 레이블의 타겟이 되어버린다. 블록 레인지가 끝나는 점으로 이동시키기 때문에 중요하다. 


    GOTO LAST 마지막 위치로 이동하는데 이것은 매우 예외적인 것이다. 함수, 서브루틴 모두 위로 가는데 점프로 아래로 가는 것은 유일하다. 


    Iteration set

    Lable set 


    break와 continue가 작동할 때 lable set에서 작동하는 경우와 iteration set에서 작동하는 경우의 두가지가 있는 것이다. iteration set 한정으로 익명 가능. break의 익명은 흉내낼 수 있지만 continue의 익명은 흉내낼 수 없다. break는 for문 앞으로 돌아가는 것이 아니라 for문의 block scope로 돌아가기 때문(37:30)



    Switch문


    순서: 중괄호, 케이스식, 콜론


    switch (3) {                // switch문의 중괄호는 중문x. switch lable block. 문법적인 형식. 특수 레이블 블록.

    case 3: break;       // 이 특수 레이블 블록 안에서 특수한 레이블을 만들 수 있는 권한을 준다. 

    }                                 // 다른 제어문의 경우 뒤에 문만 오면 되기 때문에 중괄호가 오지 않아도 된다.


    case lable = case + 식 + : -> 하나의 점프 레이블.

    위에가 통과되면 아래로 쭉 흐른다. fall through라고 한다. case 레이블은 레이블 효과만 있기 때문에 당연하다.


    switch (3) {

    case 3:

    case 4: break; // switch lable block바깥으로 빠져나간다. 익명 break가능.

    case 5:

    case 6:

    }



    a:
    switch (3) {

    case 3:

    b: {}  // 허용된다. 

    case 4: break a; // switch의 break문은 iteration set이나 lable set과 다른 의미에서 익명 lable을 만든다.

    case 5:

    case 6:

    default: 

    }


    lable set 안에서 점프를 시킬 때 충돌하는 경우가 생긴다.

    switch문에 대해 알아야 할 것은 레이블을 어떻게 만들어 내는지. 특수 레이블에 대한 기본적인 이해이다. 두번째는 switch문은 다른언어에 비해 자스에서 독특하게 작동한다. 다른 언어에서는 스위치 블록 안을 컴파일 타임에 맵으로 만든다. 그 안에 값이 오자마자 바로 연결되는 값이 있는지 조회한 후 없으면 default 후 빠져나온다. 자스는 런타임에 작동하는데 값이 들어오면 위에서 아래로 계산하기 시작한다. 


    let a = 3, b = 0;    // a가 조건, case를 값으로,

    switch (a) {

    case b++: log('a', b); break;

    case b++: log('b', b); break;

    case b++: log('c', b); break;

    case b++: log('d', b); break; // 위에서 아래로 내려와서 'd 4' 가 찍힌다. 각 식이 매번 평가된다는 말.

    }


    switch문의 원리는 가장 처음에 주어진 식(위에선 a)과 각각 케이스에서의 식이 맞는지를 위에서부터 아래로 계속 뒤져가며 확인하는 과정이다.


    값으로 확정지을 수 없고 조건으로 확정되는 케이스를 만들고 싶다면? 

    (a)를 값으로 확정을 짓고 case 레이블에 조건을 기술하면 된다(아래와 같은 경우) 

    (a)를 조건으로 하고 case를 값으로 적으려면 조건이 한정적이고 값이 다양할 때 한정이다(바로 위와같은 경우)


    let a = 3, b = 0;    // a를 값으로, case가 조건

    switch (true) {

    case a > 5: log('a', a); break;

    case a > 4: log('b', a); break;

    case a > 3: log('c', a); break;

    case a > 2: log('d', a); break;   // 'd 3'

    }   // 이 경우 순서관계가 생겨버린다. 

        // 독립적인 조건을 기술할 땐 괜찮지만 관련된 조건으로 기술할 때는 로직적인 함정에 빠지기 쉽다.



    continue와 break는 반드시 블록 안에서만 쓰일 수 있다. continue와 break이 쓰이는 경우는 iteration set, lable set, switch set의 블록 안에 들어가 있을 때 뿐이다. switch set은 블록이 아니라 switch section이다. 


    a: const c = 3;    // a lable의 블록이 없으므로 continue와 break를 쓸 방법이 없다. 

              // 이런 레이블은 주석 대신 쓸 수 있다.


    레이블은 흐름제어의 기본이다.


    if문과 loop문은 goto문을 현대에 완전히 추상화 시켰다. 이것들을 통해서 명시적인 break나 continue같은 goto문을 제거하는데 성공했다. 


    conditional statement 조건문

    if문은 optional하다. 

    if else문은 mandatory하다.


    if (c === 1) {


    } else if (c === 2) {      // 제어문은 그자체로 하나가 단문이다. else 이후에 if문이 들어 온 것이다. 


    } else if (c === 3) {


    } else {


    }


    제어문엔 사실상 if문과 if else문만 있다. 


    if (c === 1) {


    } else {  if (c === 2) {


    } else { if (c === 3) {


    } else {


    }

    }   // 위 코드는 이 코드와 일치한다. 



    if (c > 5) {


    } else switch (c) {


    }



    if (c > 5) {


    } else for (;;) {


    }


    if else문은 반복되는 경우 else가 후방 결합하는 점이 주의할 점이다. 


    if (c === 1) {


    } else if (c === 2) {


    } else {


    }


    if문의 경우, if문이 속해있는 모든 문을 분석해서 후방에 있는 else가 앞에 있는 if를 따라가도록 뒤에서부터 앞으로 해석을 하게 되어 있다. 이러한 현상을 방지하려면 중괄호등으로 묶어서 분리해주지 않으면 항상 후방 결합을 해버린다. else가 후방 결합한다는 사실이 매우 중요하다. 


    if else문의 중첩, 복잡성은 else의 후방결합을 완전하게 이해하고 통제하지 못하면 대부분 버그로 이어진다. 

    따라서, else if는 앞으로 사용하지 않는다. 무조건 중괄호를 사용한다. 연산자 우선순위도 쓰지 말고 무조건 소괄호로 묶어야 하는 것과 같다. 복잡성을 줄이는 것이다.


    복잡성은 습관으로 줄일 수 있다. 


    if else문의 특이한 점은 문을 두개 소유한다는 점이다. 다른 제어문은 문을 하나만 소유한다. 따라서 문이 두개 이기 때문에 후방결합이라는 개념이 생긴다. 


    이것이 병렬적인 조건을 선택할 때 절대로 else if로 써서는 안되는 이유다.

    else if는 매우 위험한 코드이며 다른 사람이 해석할 때 매우 난감하며 else 후방조건이 제대로 붙어 있는지 검사하기 매우 까다롭다. 컴파일 에러가 안나며 런타임도 그냥 통과하기 때문이다. 로직이 잘못된 경우가 많다. 


    그렇다면 if else는 솔직히 언제만 쓰는 것일까? 병렬조건이 아닌 1차 조건이 분기한 이후에, 나머지 남은 부분집합을 분기할 때 쓰는 것이다. 병렬조건이 아니라, else이후는 안에 포함nested되어 있는 sub 집합인 것이다. 따라서 else if는 병렬조건일 때 쓰면 안된다. 착시효과에 의해 병렬조건에 쓰고 있는 것이다. 따라서 병렬조건(내가 평가해야할 식들이 동등할 경우)일 때는 무조건 switch만 쓴다. 


    표현을 좀 더 날카롭게 다듬어야만 한다. 왜 그래야만 하는가? 나중에 수정할 수 있으니까. 이런 구별이 없어지면 나중에 다른 사람들이 그 코드를 보고 의도를 찾을 수 없게 된다. 그 의도를 어떻게 만드는가? 필요한 위치에 필요한 문법으로 필요한 단어로 기술했는가에 달려 있다. 제어문이 여러 개인 것은 각각 필요한 것을 기술하기 위해서다. 이러한 각 제어문만의 의도는 절대로 서로 교환되지 않는다. 


    if (c == 3) {


    } else {

    if (c === 2) {


    }    // 만약 이곳이 if else문이 아닌 if문이라면?

    }         // 이런 것을 우리는 논리적인 부수효과 라고 부른다. 

    대부분의 코드가 이런 곳에서 망가진다. 

    if else문은 mandatory를 의미한다. 절대로 빠져나갈 수 없는 logic이다. 그 else안에 if문이 들어가는 바람에 optional조건이 들어가게 된다. 이것을 무엇이라고 말하기 어렵다. 다른 코드를 다 보기 전까지는 이 else 블록에 대해 판단할 수가 없다. 반드시 실행되는지 안되는지 알 수가 없다. else 구문 안이 반드시 처리된다는 확신이 없다. 


    따라서 mandatory로 갔다면 계속해서 mandatory로 갈 것을 추천한다. 따라서 위의 else문 안의 if문은 if else문으로 바꿔야 한다. 기계는 항상 괜찮다. 항상 사람이 이해하기 어려운 코드가 있을 뿐이다. 


    코드의 수정을 할 수 있다는 것은 내 코드의 의도를 정확히 알기 때문에 어디를 바꿀지 알고, 그 여파가 다른 곳에 퍼지지 않는다는 것을 말한다. 프로그램은 변한다.


    switch문 역시 섬세하고 어렵다. 병행조건을 다루기 때문이다. 병행조건을 기술하면 병행조건에 포함되지 않는 예외의 여집합이 반드시 생긴다. 따라서 우리는 switch문을 쓸 때 반드시 default를 쓰기로 한다. 



    for문


     for (ex; ex; ex) {  // 포문의 첫번째 인자는 선언문이나 공문, 식이 들어온다.

       // 포문의 두번째 인자가 truthy인 동안 실행한다. 

      // '', 0, false, null, undefined, NaN 가 falsy값. 그외에는 모두 truthy

      // 포문의 세번째 인자는 식문으로 중괄호 문 마지막에 실행해준다. 공문이 될 수도 있다.

      // 포문의 가운데 인자로 공문, 공식이 오는 경우 truthy로 평가되어 무한루프.

    }


    공문의 값은 empty라는 자스 엔진 내의 값이다. empty는 원래 falsy로 평가되지만 예외적으로 포문의 가운데 인자가 공문이면 truthy로 평가된다. 


    for ( ; ; ) {

    // 인자가 true로 평가되어 무한루프

    }


    while (true) {

    // 인자로 true를 넣어야만 무한루프

    }



    do {

    // 한 번 실행 후 while 실행

    } while (truthy) 


    var a = -1;

    while (a > 2) {

    a++;             // a의 상태가 변함으로써 상태를 빠져나옴 

    }


    while문과 do while문에서는 평가식에 관여되있는 상태값이 바뀌지 않으면 무한루프에 빠지는 경우가 굉장히 다양하다. 따라서 평가식에 관여되는 코드가 한번도 안나왔다면 무한루프 당첨이다. 

    while문과 do while문에서 가장 중요한 건 조건식에 나오는 내용이 body(중괄호 안)에 등장 하나 안하나가 가장 중요한 관건이다. 




    var a = act.method().c;

    while (a) {

    other.action()

    a = act.method().c;   // 동일한 식이 바디에도 나온다.

    if (r === 'abc') a = false // test code 작성이 매우 편하다.

    }


    따라서 위의 a의 식 코드가 while 바디에 반드시 나오게 해야만 한다. 무한루프에 빠질 수 있기 때문에.

    while문 바디는 중문으로 짜는 것이 권장사항이다. 


    do (문) while (식)


    do a++; while (a); // 마지막에 ;를 붙여야 한다.


    enhanced for => for in문, for of문




    '코-드 스피츠 > es6' 카테고리의 다른 글

    ES6 + 기초 3  (0) 2019.01.24
    ES6 + 기초 1  (0) 2019.01.21
Designed by Tistory.