ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바스크립트는 왜 그 모양일까? 16장 ~ 20장
    Books/자바스크립트는 왜 그 모양일까? 2022. 6. 24. 10:45

    { " number " : 16 , " chapter " : " this " }

    프로토타입

    - 객체는 속성만 저장하며, 프로토타입은 그냥 객체일 뿐이다. 그리고 메서드는 객체에 저장된 함수에 불과하다.

    - 프로토타입에도 해당 속성이 없고, 프로토타입이 또 다른 프로토타입을 가지고 있다면 해당 결과는 ' 프로토타입의 프로토타입의 속성 '값이 됩니다.

    - 전에 살펴본 프로토타입 체인으로서 이 과정은 프로토타입이 더 이상 없을 때까지 반복된다.

    - 많은 객체가 같은 프로토타입을 공유할 수 있다.

    - 이런 객체들은 클래스 인스턴스처럼 보일 수 있지만, 실은 프로토타입을 공유하는 개별적인 객체에 불과하다.

    - 프로토타입을 사용하는 가장 큰 이유 중 하나는 메서드를 저장하는 공간으로 쓰기 위함이다.

    - 비슷한 객체들이 전부 비슷한 메서드를 가지는 경우가 많기 때문에, 이런 메서드들을 하나의 공유 프로토타입에만 저장한다면 메모리를 절약할 수 있을 것이다.

    -  프로토타입에 있는 함수가 어떤 객체에서 동작하고 있는지 알아내기 위해 this를 사용

     

    - this는 보안이나 신뢰성 위험을 초래한다.

    - 함수가 배열에 저장되어 나중에 호출된다면, 해당 함수를 호출할 때는 해당 배열에 대한 참조가 this로 제공

     

    this 없이 쓰기

    - 메서드 호출에서는 사용되는 객체가 this로 바인딩되기 때문에 this가 때때로 쓸모가 있다

    - 하지만 동일한 함수 객체가 함수로서 호출되면 this는 전역 객체에 바인딩될 수 있으므로 최악

    - 해법은 this를 금지할 것! → this는 문제가 많고 필요도 없다!

     

    { " number " : 17 , " chapter " : " 클래스 없는 자바스크립트 " }

    - 상속은 아주 강력한 코드 재사용 기법이다

    - 상속은 " 거의 같지만, 예외가 있다 " 라는 생각에 기초를 둔다.

    - 상속은 클래스 사이의 강한 결합도를 유지발한다. 한 클래스에 가해지느 변경은 의존적인 다른 클래스에 문제를 일으킨다.

    - 클래스 방식의 상속은 개발자가 좋은 프로그램을 만든다고 생각하게 하지만, 동시에 더 많은 버그와 더 이상 손대고 싶지 않은 유산을 만들기도 한다.

    - 그리고 타입은 그 비용이 이득보다 크다.

     

    생성자

    - 자바스크립트의 아주 좋은 것 중 하나가 바로 객체 리터럴이다.

    - 여러가지 정보를 묶을 때 쓰기 좋고 표현하기도 좋다.

    - 데이터 객체를 생성하고 소비하는 메서드를 만듦으로써 메서드의 수를 줄일 수 있고, 객체의 완전성을 증가시킬 수 있다.

    - 객체는 두 종류로 볼 수 있다

    1. 메서드만 가지고 있는 단단한 객체 ( hard object )

        ㆍ이런 객체는 클로저 내부에 있는 데이터에 대한 완전성을 보호하며 다형성과 캡슐화를 제공한다

    2. 데이터만 가지고 있는 부드러운 객체 ( soft data object )

        ㆍ정의된 동작이 없으며 함수에 의해 처리될 수 있는 편리한 데이터집합

    - 단단한 객체를 문자열화 해야 한다면 해당 객체에 toJSON메서드를 정의해줘야한다.

    - 그렇지 않고 JSON.stringify를 사용하면, 그냥 빈 객체로 보고 메서드를 무시하고 데이터를 숨겨버릴 것이다.

     

    생성자 매개변수

     

    컴포지션

    - 함수 컴포지션은 ' 거의 다 같지만 약간 다른 ' 것을 만드는 대신, 여기서 조금, 저기서 조금 가져와서 만들 수 있다.

    function my_little_constructor(spec) {
        let {member} = spec;
        const reuse = other_constructor(spec);
        const method = function () {
            // 이 method는 spec, member, reuse, method를 사용할 수 있다
        };
        return Object.freeze({
            method,
            goodness: reuse.goodness
        });
    }

    - 생성자는 원하는 상태 관리나 동작을 쓰기 위해서 원하는 만큼 다른 생성자를 호출해 쓸 수 있다.

    - 동일한 spec 객체를 다른 생성자에 전달할 수 있다

    - spec 매개변수를 만들 때 my_little_constructor가 필요로 하는 속성도 나열하고, 다른 생성자가 필요로 하는 속성들도 나열하면 된다

    - 몇몇의 경우에는 우리가 얻은 메서드들을 동결된 객체 추가하기만 하면 됐고 그 외의 경우에는 획득한 메서드를 호출하는 새로운 메서드를 만든다.

    - 이렇게 코드를 재사용하는 방식은 상속과 비슷하긴 하지만 강한 결합이 없다는 점에서 더 좋다.

     

    크기

    - 객체를 생성하는 방법은 프로토타입을 쓰는 방법보다 메모리를 더 많이 사용한다.

    - 하지만 메모리 사용량에 아주 큰 차이는 없다.

    - 클래스 모델에는 모든 모델에 맞는 한 가지 크기만 있고 모든 객체는 클래스의 인스턴스여야 한다

    - 하지만 자바스크립트에는 이런 제약이 없고 모든 객체가 단단할 필요도 없다.

     

    { " number " : 18 , " chapter " : " 꼬리 호출 " }

    - 최적화의 가장 중요한 점은 새로운 버그를 만들지 않는 것만이 아니라, 좋은 프로그램의 버그를 완전히 제거하여 프로그램의 새로운 패러다임을 가져온다는 것이다.

    - 꼬리 호출

        ㆍ함수가 마지막으로 하는 일이 어떤 함수를 호출해 그 결과를 바로 반환하는 것일 때 일어난다.

        ㆍ함수가 함수 호출 결과를 바로 반환하는 경우

    - 꼬리 호출 최적화를 통해서 재귀 함수 호출을 반복문만큼 빠르게 만들 수 있다.

    // 일반적인 반복문
    while(true) {
        do some stuff
        if (done) {
            break;
        }
        do more stuff
    }
    // 꼬리 재귀 함수와 비슷한 형태
    (function loop() {
        do some stuff
        if (done) {
            return;
        }
        do more stuff
        return loop();		// 꼬리 호출
    }());

     

    - 재귀 함수는 할당 대신 매개변수로 변경된 상태를 전달하고 값을 반환하는 방식으로 더 우아하게 동작한다.

     

    꼬리 위치

    - 함수가 반환하는 값이 바로 반환되는 경우 해당 함수 호출은 꼬리 호출이라고 한다.

    return (
        typeof any === "function"
        ? any()								 // <-- 꼬리 호출
        : undefined
    );

    - 재귀는 일반적으로 꼬리 호출이 아니다.

    function factorial(n, result = 1) {
        if (n<2) {
            return result;
        }
            return factorial(n - 1, n * result);		// <-- 꼬리호출
    }
    return( function () {}());			// <-- 꼬리 호출

     

    예외

    - try블록 내의 꼬리 호출은 최적화되지 않는다.

     

     연속 전달 스타일

    - 연속 전달 스타일 ( continuation passing style )에서 함수는 부수적인 continuation 매개변수를 가지는데, 이는 결과를 전달받는 함수이다.

     

    { " number " : 19 , " chapter " : " 순수함 " }

    - 함수형 프로그래밍 ( functional programming )은 말 그대로 함수로 프로그래밍하는 것을 뜻한다.

    - 특정 도메인의 값을 다른 값과 연관짓는 수학적인 함수로 프로그래밍한다는 뜻일 수도 있고,

       대부분의 프로그래밍 언어가 의미하는 매개변수를 사용하는 소프트웨어 함수로 프로그래밍한다는 뜻일수도 있다.

    - 수학적인 함수는 소프트웨어 함수보다 훨씬 더 순수하다고 여겨진다.

     

    순수함의 축복

    - 순수함은 아주 훌륭한 모듈화를 내포하고 있다.

    - 순수 함수는 매우 높은 응집도를 보인다. 극도로 약한 결합도를 보인다.

    - 어떤 사이드 이펙트도 없고 외부에 대한 의존성도 없으며 영향도 받지 않는다.

    - 순수함수는 스레드에도 영향을 주는 신뢰성과 성능 문제를 해결할 수 있는 훌륭한 방법이다.

     

    - 멀티스레드 시스템에서 두 스레스가 같은 메모리 영역에 동시에 접근하려고 하면, 두 스레드 간 경쟁(race)이 발생하고, 데이터나 결과가 오염되거나 시스템이 멈출수도 있다.

    - 이런 경쟁은 찾아내기도 힘들다

    - 상호 배제 ( mutual exclusion )를 통해서 경쟁을 줄이거나 관리할 수도 있지만 지연이나 데드락 ( deadlock ), 심지어 시스템이 멈추는 상황이 발생할 수도 있다.

     

    - 순수 함수는 스레드 안전하며 효과적이다.

    - 순수 함수는 아무것도 변경하지 않기 떄문에 메모리를 공유한다고 해도 아무 문제가 없다.

     

    순수해지기

    1. var나 let문을 포함한 할당 연산자 대신에 const문을 써라

        ㆍconst 문을 써서 변수를 초기화하고, 변경되는 일을 막을 수 있다.

    2. delete연산자나 Object.assign 메서드와 같이 객체를 수정하는 연산자나 메서드들을 제거해야한다.

    - splice나 sort처럼 배열을 변경하는 메서드들도 제거해야한다.

        ㆍ배열의 sort 메서드는 순수 함수가 될 수 있었음에도 원본 배열 자체를 수정하도록 구현되었으니 제거해야한다.

    3. getter와 setter 도 없애야 한다.

        ㆍsetter는 분명히 변경을 가하는 함수이며, 두 가지 모두 사이드 이펙트를 만들기 위해 존재하는 것이다.

        ㆍ모든 사이드 이펙트는 오염이며, 제거해야 할 대상이다.

    4. 정규표현식의 exec 메서드는 lastIndex 속성을 변경하기 때문에 제거해야 한다.

    5. for 문은 색인 변수를 변경하는 것이 목적이기 때문에  쓰지 않아야 한다.

        ㆍwhile이나 do도 마찬가지로 쓰지 말아야한다

        ㆍ꼬리 재귀( tail recursion )가 반복을 구현할 수 있는 가장 순수한 방법이다

    6. Date 생성자 역시 제거해야한다.

        ㆍ호출할 때마다 다른 값을 가져오기에 순수하지 않은 함수다

        ㆍ Math.random 역시 같은 이유

    7. 사용자도 개입되어서는 안된다

        ㆍ사람과의 상호작용은 다른 결과를 가져올 수 있다.

    8. 마지막으로 네트워크와도 연결되어서는 안된다.

        ㆍ람다계산( Lambda Calculus, 또는 람다 대수 )에서는 한 기계에는 있고

            다른 기계에는 없는 정보를 표현할 수 있는 방법 존재 X

     

    { " number " : 20 , " chapter " : " 비동기 프로그래밍 " }

    - 순차적 언어는 입출력을 블록 ( block ) 방식으로 처리한다.

    - 자바스크립트는 다른 언어보다 순차적 모델에 영향을 덜 받는다.

     

    동시성

    - 동일한 유형(homogeneous)의 동시성 : 비슷한 많은 동작이 같은 시간에 처리될 수 있게 한다

    - 서로 다른 유형(heterogeneous)의 동시성 : 각각의 서로 다른 책임을 지는 특별한 프로세스들의 협업을 가능하게 만든다.

     

    스레드

    - 스레드는 가장 오래된 동시성 기법

    - 스레드는 실제 혹은 가상의 CPU로서 메모리를 공유하며 동시에 실행된다.

    - 스레드로 인해 발생하는 버그는 버그들 중에서도 그 대가가 비싸다

    - 스레드 간 경쟁으로 발생할 수 있는 위험성은 상호 배제(mutual exclusion)로 줄일 수 있다

    - 상호 배제는 메모리의 임계 구역(critical region)을 잠그고, 스레드를 차단하고, 서로 경쟁하는 코드 실행을 막는 것으로 이루어진다.

    - 임계 구역을 잠그는 작업은 그 비용이 아주 비싸다

    - 그리고 실행이 차단된 스레드가 잠금을 해제하지 못하는 경우도 발생하는데, 이를 데드락(deadlock)이라고 한다

    - 운영체제에서의 스레드는 필요악이다. 하지만 애플리케이션에서의 스레드는 그냥 악이다.

     

    비동기 프로그래밍

    - 비동기 함수( eventual function, asynchronous function )는 호출하면 즉각 반환한다.

    - 비동기 방식은 애플리케이션이 스레드를 쓰지 않고도 많은 일을 처리할 수 있게 해준다.

    - 사실 애플리케이션 스레드를 활용해 사용자 인터페이스를 비동기로 처리해 주는 시스템이 많다.

    - 비동기 프로그래밍은 콜백함수와 프로세싱 루프 ( processing loop )에 근간한다.

     

    - 콜백함수는 기대하는 일이 향후에 일어날 때 호출되는 함수이다

        ㆍ메시지가 도착함

        ㆍ어떤 작업이 완료됨

        ㆍ사용자가 프로그래뫄 상호 작용함

        ㆍ센서가 특정 이벤트를 관측함

        ㆍ시간이 흐름

        ㆍ무언가가 잘못됨

    - 콜백 함수는 시작하거나 특정 활동을 지켜보는 함수에 인자로 전달된다. 더 원초적으로 설명하면, 콜백 함수는 어떤 활동을 표현하는 객체 연결된다.

     

    - 프세세싱 루프는 다른 말로 이벤트 루트(event loop) 또는 메시지 루프(message loop)라고도 한다.

    - 이벤트 루프는 큐(queue)에서 가장 높은 우선순위를 가지는 이벤트 혹은 메시지를 가져와서 해당 이벤트나 메시지를 처리하도록 등록된 콜백함수를 호출해 준다.

    - 콜백 함수가 작업을 완료하면 반환한다.

    - 그래서 콜백 함수는 메모리 잠금이나 상호 배제가 필요없다. 콜백함수는 방해(interrupted)받지 않기 때문에 경쟁 X

    - 콜백함수가 끝나면 프로세싱 루프는 큐에서 그다음 이벤트나 메시지를 꺼내와서 등록된 콜백 함수를 호출하고 이 과정을 반복

     

    - 프로세싱 루프는 큐를 관리, 이 큐는 이벤트 큐 혹은 메시지 큐라고도 불린다.

    - 들어오는 이벤트나 메시지를 저장

     

    - 스레드를 사용하는 시스템의 경우, 한 스레드에 예외가 발생하면 그 스레드의 스택을 되감는다.

    - 그러면 그 스레드의 상태는 연관된 다른 스레드의 상태들과 일치 하지 않게 되고, 연달아 다른 스레드에 문제를 일으킬 수 있다.

     

     턴의 법칙

    - 프로세싱 루프의 한 반복은 턴(turn)이라고 불린다.

    - 턴의 법칙(Law of Turns) : 기다리지 말라. 블록하지 말라. 빨리 끝내라

    - 턴의 법칙은 프로세싱 루프에서 호출하는 콜백 함수, 그리고 콜백 함수가 직간접적으로 호출하는 모든 함수에 적용

    - 턴의 법칙을 위반하는 것은 단순히 현재 콜백을 지연하는 것뿐 아니라 큐에 있는 모든 것을 지연시킨다.

    - 따라서 턴의 법칙을 위반하는 함수는 수정되거나 혹은 별도의 프로세스로 격리 되어야 함.

    - 프로세스는 스레드와 비슷하지만 메모리를 공유하지 않는다.

    - 따라서 콜백 함수를 어떤 다른 프로세스로 격리해서 따로 작업을 하게 만들고, 작업이 끝나면 해당 프로세스가 메시지를 보내도록 하는 것도 좋은 방법이다.

    - 메시지는 큐로 전달되고 결국 비동기로 동작하게 되는 것임.

     

    서버 세성에서의 문제

    1. 콜백 지옥 ( Callback Hell )

    - 콜백 함수에 다음 작업을 요청하기 위한 코드가 들어가 있는 경우

    - 다음 작업을 요청하는 코드 역시 콜백 함수를 제공하는데, 그 콜백 함수에도 역시 다음 작업을 요청하는 코드가 있다

    - 이 연결고리가 계속 이어져서 프로그램은 읽기도 힘들고, 유지보수하기도 어려우며 깨지기 쉽다

     

    2. 프로미스 ( Promise )

    - 애초에 안전한 분산 프로그램 개발을 지원하기 위해 만들어진 기능이다

    - 하지만 JS에서 원래 의도된 새로운 패러다임의 기능들이 모두 사라짐

    - 남은 것은 이상한 실행 흐름 제어 기법 뿐...

    - 프로미스는 원래 지역적인 실행 흐름을 관리하려고 만든 기능이 아니다

    - 그런데 실행 흐름을 관리하려고 프로미스를 쓰니 이상한 것이 당연하다

    - 물론 콜백 지옥보다는 훨씬 낫지만, 만족스럽지는 않다

     

    3. Async Await

    - 순차적으로 실행되는 코드에 붙이면 마법처럼 비동기 코드로 만들어주는, 짝을 이루는 키워드

    - 만드는 코드가 실제로 얻게 되는 코드와 사뭇 다르다는 점에서 ES6의 제너레이터와 비슷하다

    - 신뢰도를 향상시키려고, 프로미스의 실망스러운 점을 대부분 감추고 있다

    - Async Await의 좋은 점은 옛날 패러다임과 동일한 형태로 코드를 작성해도 비동기 프로그래밍을 구현할 수 있다

    - 사실 그것이 제일 큰 문제이다..

     

    - 위에서 설명한 세 가지 실수는 대개 로직 및 실행 흐름과 아주 강력한 결합을 보인다.

    - 결국 너무 많은 이질적인 활동으로 인해 낮은 응집도를 보이게 된다.

    - 이런것들은 따로 분리하는 것이 좋다.

     

    리퀘스터

    - 설계는 모듈 단위로 해야함

    - 작업의 단위(예를 들면 서버에 요청을 보내거나, 데이터베이스에서 데이터를 읽어오거나, 프로세스를 시작하는 것 등) 별로 함수를 구분해야 한다.

    - 하나의 작업 단위만 수행하는 함수는 높은 응집도를 보인다.

    - 이런 함수들은 또한 첫 번째 인자로 콜백 함수를 전달받는다.

    - 작업이 끝나면 그 결과는 콜백 함수를 통해 전달된다.

     

    - 콜백 함수를 인자로 전달 받고, 호출 즉시 반환되지만 작업은 바로 끝나지 않고 미래의 언젠가 끝나는 그런 함수들을 리퀘스터(requestor)라고 부르겠다.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    댓글

Designed by Tistory.