A Developing Developer
DAY 22. 주특기(Node.js) Javascript 심화 Chapter 03. this, Chapter 04. 콜백 함수 본문
DAY 22. 주특기(Node.js) Javascript 심화 Chapter 03. this, Chapter 04. 콜백 함수
H-JJOO 2022. 12. 13. 21:45Node.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
- 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 팀장님 만세
'내일배움캠프 4기 > TIL' 카테고리의 다른 글
DAY 24. 주특기(Node.js) Node.js 입문주차 1-17 5-1 준비하기 ~ 1-27 서버와 도메인 연결하기 + 삽질 (0) | 2022.12.15 |
---|---|
DAY 23. 주특기(Node.js) Node.js 입문주차 1-1 필수 프로그램 설치~1-16 REST API 개발 (0) | 2022.12.14 |
DAY 21. 발제 주특기(Node.js) Javascript 심화 Chapter 01. 데이터 타입, 02. 실행 컨텍스트 (0) | 2022.12.12 |
DAY 20. 2ed 팀 프로젝트 6일차 (발표) + J 튜터님 특강 (DB 기초) (0) | 2022.12.09 |
DAY 19. 2ed 팀 프로젝트 5일차 (0) | 2022.12.08 |