A Developing Developer

DAY 22. 주특기(Node.js) Javascript 심화 Chapter 03. this, Chapter 04. 콜백 함수 본문

내일배움캠프 4기/TIL

DAY 22. 주특기(Node.js) Javascript 심화 Chapter 03. this, Chapter 04. 콜백 함수

H-JJOO 2022. 12. 13. 21:45

Node.js 주특기 주가 시작되었는데,

 

계획 체크리스트가 이상한건지, 내가 착각한건진 모르겠지만,

 

나는 체크리스트 대비 2일 느리다.

 

체크리스트에는 Javascript 심화 가 포함되어있지 않았고, 발제 자료에는 순서상 Javascript 심화 -> Node.js 입문 -> Node.js 숙련 인데...

 

내가 조금 늦장부려서 2일만에 Javascript 심화를 완강하긴 했지만(Chapter 5 클로저 빼고),

 

에이 핑계 늘어놔봤자 달라지는 건 없고, 그렇다고 밤새서 Node.js 입문을 들을 생각도 없다.

 

내일 아침부터 Node.js 입문을 시작해서 끝을 보겠다는 마인드로 임해야겠다.

 

============================================================================================

- Javascript 심화 (C튜터)

  • Chapter03. this

- this 는 함수를 호출할 때 결정이 된다!

 

- 전역 공간에서는 전역 객체를 가르킨다!

 

- 함수와 메서드의 기준은 독립성으로, 함수는 독립적인 기능을 수행하고 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행하는 것이다.

 

- 함수로서 자체적으로 호출될 경우 this 는 window 객체를 의미

var func = function (x) {
    console.log(this, x);
};
func(1); // Window { ... } 1

- method 메소드를 호출한 주체는 obj 객체이다. 이 경우 this 는 obj 객체를 의미

var obj = {
    method: func,
};
obj.method(2); // { method: f } 2

- 함수로서의 호출과 메서드로서의 호출 구분 기준 : . []

var obj = {
    method: function (x) {
        console.log(this, x)
    }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

- iv. 메서드 내부에서의 this

var obj = {
    methodA: function () {
        console.log(this)
    },
    inner: {
        methodB: function () {
            console.log(this)
        },
    }
};

obj.methodA(); // this === obj
obj['methodA'](); // this === obj
obj.inner.methodB(); // this === obj.inner
obj.inner['methodB'](); // this === obj.inner
obj['inner'].methodB(); // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner

- 함수로서 호출할 때 그 함수 내부에서의 this

★함수로서 ‘독립적으로’ 호출할 때는 this는 전역 객체(window)★

- 메서드의 내부 함수에서의 this 우회

 

1.  변수 활용

var obj1 = {
    outer: function () {
        console.log(this);
        var innerFunc1 = function () {
            console.log(this); // -> window 전역객체
        }
        innerFunc1();

        var self = this; // -> obj1
        var innerFunc2 = function () {
            console.log(self); // self 를 호출함으로 window 가 아닌 obj 객체를 호출
        };
        innerFunc2(); //
    }
};
obj1.outer();

2. 화살표 함수 : ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없음(따라서, this는 이전의 값-상위값-이 유지됨)

var obj = {
    outer: function () {
        console.log(this);
        var innerFunc = () => {
            console.log(this);
        };
        innerFunc(); // 함수로써의 호출
    }
}

obj.outer();

- 생성자 함수 내부에서의 this

 

i. 생성자 : 구체적인 인스턴스를 만들기 위한 일종의 틀
ii. 공통 속성들이 이미 준비돼 있음

var Cat = function (name, age) {
    this.bark = '야옹';
    this.name = name;
    this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi

생성자 내부에서는 this 가 해당 인스턴스이다.

 

- 명시적 this 바인딩 : 자동으로 부여되는 상황별 this의 규칙을 깨고 this에 별도의 값을 저장하는 방법

1.  call 메서드 : 호출 주체인 함수를 즉시 실행하는 명령어

var func = function (a, b, c) {
    console.log(this, a, b, c);
};

func(1, 2, 3); // Window{ ... } 1 2 3
func.call({x: 1}, 4, 5, 6
}
; // { x: 1 } 4 5 6

var obj = {
    a: 1,
    method: function (x, y) {
        console.log(this.a, x, y);
    }
};
obj.method(2, 3); // 1 2 3
obj.method.call({a: 4}, 5, 6); // 4 5 6

2. apply 메서드 : call 메서드와 완전 동일, 두 번째 인자가 배열인 부분만 다름

console.log(this, a, b, c);
}
;
func.apply({x: 1}, [4, 5, 6]); // { x: 1 } 4 5 6
var obj = {
    a: 1,
    method: function (x, y) {
        console.log(this.a, x, y);
    }
};
obj.method.apply({a: 4}, [5, 6]); // 4 5 6

3. call / aplly 메서드 활용 -> 유사배열객체(array-like-object)에 배열 메서드를 적용

객체지만 배열처럼 동작할 수 있도록 구조를 잡아놓은 객체이다.

1. 반드시 length 가 필요, 이 조건은 필수, 없으면 유사배열아님
2. index 번호가 0번부터 시작해서 1씩 증가, 안그래도 되지만 예상치 못한 결과 발생.

- slice() 함수 : 새로운 배열을 만드는데, 특정 범위를 복사한 값들을 담고있다.
첫번째 인자로 시작 인덱스, 두번째 인자로 종료 인덱스를 받으며, 시작 인덱스 부터 종료 인덱스까지 값을 복사하여 반환.

//객체에는 배열 메서드를 직접 적용할 수 없다.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있다.
var obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};
Array.prototype.push.call(obj, 'd');//유사배열이지만 배열처럼 push 가능
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

//유사배열객체에서 배열처럼 함수를 쓰기위해서는 call 메소드를 활용가능

var arr = Array.prototype.slice.call(obj); // 전체 배열 복사
console.log(arr); // [ 'a', 'b', 'c', 'd' ]

- 최대/최소값 예제

//비효율
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function (number) {
    if (number > max) {
        max = number;
    }

    if (number < min) {
        min = number;
    }
});
console.log(max, min);
//효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);
//Spread Operation(ES6)
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);

//https://paperblock.tistory.com/62

 

[ES6] Spread Operator (스프레드 연산자)

ES6에서는 '...'와 같이 다소 특이한 형태의 문법이 추가되었습니다. 점 3개가 연달아 붙어있는 이 표시는 Spread Opertor(스프레드 오퍼레이터, 스프레드 연산자, 전개 구문, 펼침 연산자...)를 나타내

paperblock.tistory.com

- bind 메서드 : call과 비슷하지만, 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드, 함수에 this를 미리 적용하고 부분 적용 함수 구현에 사용된다.

var func = function (a, b, c, d) {
    console.log(this, a, b, c, d);
};

func(1, 2, 3, 4); // window객체

var bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

var bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

 

  • Chapter04. 콜백 함수 

1. 콜백 함수

 

a. 다른 코드의 인자로 넘겨주는 함수

 

b. 콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행(제어권)

 

c. callback = call(부르다) + back(되돌아오다) = 되돌아와서 호출해줘!

 

d. 즉, 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수. 콜백 함수를 위임받은 코드는 ★자체적으로★ ★내부 로직에★ 의해 이 콜백 함수를 적절한 시점에 실행

 

2. 제어권

- 호출 시점

var count = 0;
//timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지' 알려주는 id값
var timer = setInterval(function () {
    console.log(count);
    if (++count > 4) clearInterval(timer);
}, 300);

var count = 0;
var cbFunc = function () {
    console.log(count);
    if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
code                           호출 주체        제어권
cbFunc();                       사용자          사용자
setInterval(cbFunc, 300);    setInterval     setInterval
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

- 인자 (map 함수 예제)

//Array.prototype.map(callback[, thisArg])
//-> this 생략하면 전역객체 바인딩
//callback: function(currentValue, index, array)
//map : 배열의 모든 요소를 꺼내어 처음부터 끝까지 콜백함수 반복호출 + 새로운 배열 리턴


//map 메소드 : callback 함수 내의 logic 을 배열의 첫번재 요소부터 마지막 요소까지 새로운 객체로 return 해준다.

var newArr = [10, 20, 30].map(function (currentValue, index) {
    console.log(currentValue, index);
    return currentValue + 5; // 15, 25 ,35
});
console.log(newArr);

콜백함수의 인자 2개의 위치가 바뀌면 기대값과 다르게 작동한다.

-> 제어권이 넘어갈 map 함수의 규칙에 맞게 호출해야한다.!

 

-  복습  -
1. 함수로서의 호출  method(); 호출 주체를 명시할수 없기때문에 전역객체를 바라봄
2. 메서드로서의 호출 obj.method(); obj 라는 호출 주체가 있기때문에 this 가 obj 랑 바인딩

- this

i. ★콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조
ii. ★제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우 에는 그 대상을 참조

iii. 별도의 this를 지정하는 방식과 제어권에 대한 이해가 중요

 

- Array.prototype.map 구현

Array.prototype.map = function (callback, thisArg) {
    var mappedArr = [];
    for (var i = 0; i < this.length; i++) {
        var mappedValue = callback.call(thisArg || window
            , this[i], i, this);
        mappedArr[i] = mappedValue;
    }
    return mappedArr;
};

★ 콜백 함수는 함수다 ★ 콜백함수를 인자로 넣어준다면 함수로써의 호출이기때문에 this 를 잃어버린다!

 

- bind 메서드의 활용

var obj1 = {
    name: 'obj1',
    func: function () {
        console.log(this.name);
    }
};
//함수 잡채를 obj1에 바인딩
setTimeout(obj1.func.bind(obj1), 1000);
var obj2 = {name: 'obj2'};
//함수 잡채를 obj2에 바인딩
setTimeout(obj1.func.bind(obj2), 1500);

- 콜백 지옥과 비동기 제어

 

 a. 콜백지옥이란
        i. 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 지옥 수준인 경우
        ii. 주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생
        iii. 가독성, 수정도 어려움

 

 b. 동기 vs 비동기
        i. 동기 : synchronous
            1. 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식
        ii. 비동기 : a + synchronous ⇒ async라고들 흔히 부름
            1. 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
            2. setTimeout,  addEventListner 등
            3. 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드
        iii. 웹의 복잡도가 올라갈 수록 비동기적 코드의 비중이 늘어남 = 콜백 지옥

//0.5초 주기마다 커피 목록을 수집하고 출력
//각 콜백은 커피 이름을 전달하고 목록에 이름을 추가
//문제점 : 들여쓰기 ㄷㄷ, 값 전달의 순서가 아래 -> 위
setTimeout(function (name) {
    var coffeeList = name;
    console.log(coffeeList);
    setTimeout(function (name) {
        coffeeList += ', ' + name;
        console.log(coffeeList);
        setTimeout(function (name) {
            coffeeList += ', ' + name;
            console.log(coffeeList);
            setTimeout(function (name) {
                coffeeList += ', ' + name;
                console.log(coffeeList);
            }, 500, '카페라떼');
        }, 500, '카페모카');
    }, 500, '아메리카노');
}, 500, '에스프레소');

- 콜백 지옥 해별방법 1 - 기명함수로 변환

var coffeeList = '';
var addEspresso = function (name) {
    coffeeList = name;
    console.log(coffeeList);
    setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function (name) {
    coffeeList += ', ' + name;
    console.log(coffeeList);
    setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function (name) {
    coffeeList += ', ' + name;
    console.log(coffeeList);
    setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function (name) {
    coffeeList += ', ' + name;
    console.log(coffeeList);
};
setTimeout(addEspresso, 500, '에스프레소');
//가독성 좋음
//위 -> 아래
//흠... 근데, 한 번만 쓸건데 이렇게 이름을 다 붙여야 한다고?
//위 코드는 근본적인 해결책은 아닌 것 같아 -> 비동기 작업의 동기적 표현이 필요해..!!

- 콜백 지옥 해별방법 2 - 비동기 작업의 동기적 표현(1) - Promise(1)

//new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행된다.
//그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우
//resolve(또는 reject) 둘 중 하나가 실행되기 전까지는
//다음(then), 오류(catch)로 넘어가지 않는다.
//따라서, 비동기작업이 완료될 때 비로소 resolve, reject 호출하는 방법으로
//비동기 -> 동기적 표현이 가능하다.

new Promise(function (resolve) {
    setTimeout(function () {
        var name = '에스프레소';
        console.log(name);
        resolve(name);
    }, 500);
}).then(function (prevName) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            var name = prevName + ', 아메리카노';
            console.log(name);
            resolve(name);
        }, 500);
    });
}).then(function (prevName) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            var name = prevName + ', 카페모카';
            console.log(name);
            resolve(name);
        }, 500);
    });
}).then(function (prevName) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            var name = prevName + ', 카페라떼';
            console.log(name);
            resolve(name);
        }, 500);
    });
});

- 콜백 지옥 해별방법 3 - 비동기 작업의 동기적 표현(2) - Promise(2)

var addCoffee = function (name) {
    return function (prevName) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                var newName = prevName ? (prevName + ', ' + name) : name;
                console.log(newName);
                resolve(newName);
            }, 500);
        });
    };
};
addCoffee('에스프레소')()
    .then(addCoffee('아메리카노'))
    .then(addCoffee('카페모카'))
    .then(addCoffee('카페라떼'));
//직전 예제의 반복부분을 함수화 한 코드이다.

- 콜백 지옥 해별방법 4 - 비동기 작업의 동기적 표현(3) - Generator

var addCoffee = function (prevName, name) {
    setTimeout(function () {
        coffeeMaker.next(prevName ? prevName + ', ' + name : name);
    }, 500);
};
var coffeeGenerator = function* () {
    var espresso = yield addCoffee('', '에스프레소');
    console.log(espresso);
    var americano = yield addCoffee(espresso, '아메리카노');
    console.log(americano);
    var mocha = yield addCoffee(americano, '카페모카');
    console.log(mocha);
    var latte = yield addCoffee(mocha, '카페라떼');
    console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
//*가 붙은 함수가 제너레이터 함수
//제너레이터 함수 실행 시, Iterator 반환(next()를 가지고 있음)
//iterator 은 객체를 next 메서드로 순환 할 수 있는 객체다.
//next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield에서 stop
//이후 다시 next 메서드를 호출하면 멈췄던 부분 -> 그 다음의 yield까지 실행 후 stop
//즉, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부
//소스가 위 -> 아래 순차적으로 진행

- 콜백 지옥 해별방법 5 - 비동기 작업의 동기적 표현(4) - Promise + Async / await

var addCoffee = function (name) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(name);
        }, 500);
    });
};
var coffeeMaker = async function () {
    var coffeeList = '';
    var _addCoffee = async function (name) {
        coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
    };
    await _addCoffee('에스프레소');
    console.log(coffeeList);
    await _addCoffee('아메리카노');
    console.log(coffeeList);
    await _addCoffee('카페모카');
    console.log(coffeeList);
    await _addCoffee('카페라떼');
    console.log(coffeeList);
};
coffeeMaker();
//ES2017에서 새롭게 추가된 async/await 문을 이용했다.
//비동기 작업을 수행코자 하는 함수 앞에 async
//함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await
//Promise ~ then과 동일한 효과를 얻을 수 있다.

 

============================================================================================

오늘은 진짜 하루종일 뭐했나 싶을정도로 한심한 하루였다.

 

계획대로면 Node.js 입문을 시작했어야했는데...

 

스트레스도 받고, 생각보다 Javascript 심화가 더 심화해서 돌려보느라 오래걸렸다.

 

(그렇다고 완벽하게 이해한것도 아니라 더 스트레스 받는다.)

 

Javascript 심화 정리하기전에 먼저 작성 중인데, 얼른 정리하고 쉬어야겠다.

 

아 맞다 오늘 새로운 팀 배정받았는데, 드디어 팀장에서 벗어났다. (+ 뭔가 1인분만 해도 될거같다는 기대감, 우리 팀원들 기대가 됩니다.)

 

L 팀장님 만세