// 제어권
// 1. 호출 시점에 대한 제어권을 갖는다
// setInterval: 반복해서 매개변수로 받은 콜백함수의 로직을 수행
var count = 0;
var cbFunc = function () {
console.log(count);
if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
0, 1, 2, 3, 4 => setInterval이 호출 시점 제어권을 가져 300ms마다 실행함
// 제어권
// 인자에 대한 제어권을 갖는다
// map 함수
// index, currentValue: 사람이 이해할 수 있는 변수명일 뿐
var newArr = [10, 20, 30].map(function (index, currentValue) {
console.log(index, currentValue);
return currentValue + 5;
});
console.log(newArr);
10 0
20 1
30 2
[ 5, 6, 7 ]
=> index와 currentValue 순서를 바꾸었을 때 의도하지 않은 값이 나온다.
map 메서드가 인자 제어권을 갖기 때문에 정의된 규칙대로 작성해야 한다
map(현재 배열 요소, 인덱스, 호출한 배열)
Array.prototype.map123 = function (callback, thisArg) {
// map 함수에서 return할 결과 배열
var mappedArr = [];
for (var i = 0; i < this.length; i++) {
var mappedValue = callback.call(thisArg || globalThis, this[i]);
mappedArr[i] = mappedValue;
}
return mappedArr;
};
var newArr = [1, 2, 3].map123(function (number) {
return (number = 2);
});
console.log(newArr);
[2, 2, 2]
=> for (var i = 0; i < this.length; i++)
여기서 this는 map123 메서드를 호출한 배열 [1, 2, 3]을 참조한다.
var mappedValue = callback.call(thisArg || globalThis, this[i]);
callback 함수를 실행할 때 this가 어떤 객체를 참조할지 설정하고있다. 밑에서 newArr이 콜백함수만 전달하고 thisArg 인자는 전달하지 않고 있기 때문에 globalThis가 참조된다. 그리고 this[i]는 메서드를 호출한 배열 [1, 2, 3]을 참조한다. 이는 콜백함수의 number 인자로 들어간다. 따라서 배열의 i번째 인덱스에 콜백함수를 호출하여 반환된 값 number = 2가 할당되어 [2, 2, 2]가 출력된다.
var obj = {
vals: [1, 2, 3],
logValues: function (v, i) {
console.log(">>> test starts");
if (this === global) {
console.log("this가 global입니다 원하지 않는 결과!");
} else {
console.log(this, v, i);
}
console.log(">>> test ends");
},
};
// method로서 호출
obj.logValues(1, 2);
// forEach, map, filter
[4, 5, 6].forEach(obj.logValues);
test starts
{ vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
test ends
test starts
this가 global입니다 원하지 않는 결과!
test ends
test starts
this가 global입니다 원하지 않는 결과!
test ends
test starts
this가 global입니다 원하지 않는 결과!
test ends
=> obj.logValue(1,2)로 메소드 호출. 이때 this가 참조하는 것은 obj이므로 if 조건을 거짓이 되어 else 구문이 실행된다. 따라서 obj 객체, 1, 2가 출력된다.
[4, 5, 6].forEach(obj.logValues) -> [4, 5, 6] 배열이 forEach 메소드를 호출. 이때 콜백함수로 obj.logValues를 전달하였다. 이는 메소드로 보이지만 obj.logValues가 가리키는 함수만 전달하는 것으로 this가 obj를 참조하지 않고 전역객체를 참조한다. 따라서 if문의 조건이 참이다.
var obj1 = {
name: "obj1",
func: function () {
var self = this; //이 부분!
return function () {
console.log(self.name);
};
},
};
var callback = obj1.func();
setTimeout(callback, 1000);
obj1
=> self 변수에 미리 this를 할당하여 사용한다. 여기서 this는 obj1을 참조한다. 함수를 리턴했을 때 클로저에 의해 리턴된 함수의 self도 동일하게 obj1을 가리킨다. 반환된 함수를 setTimeout의 콜백함수로 전달하면 obj1 출력.
4-1 call 활용
var obj3 = { name: "obj3" };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
obj3
=> call 메소드로 obj1.func 메소드를 실행하되, this 참조를 obj3로 지정한다.
4-2 bind 활용
// bind
var obj1 = {
name: "obj1",
func: function () {
console.log(this.name);
},
};
var boundObj1 = obj1.func.bind(obj1);
setTimeout(boundObj1, 1000);
var obj2 = { name: "obj2" };
setTimeout(obj1.func.bind(obj2), 1500);
obj1, obj2
=> bind 메소드로 this가 참조할 객체를 지정하여 새로운 함수를 반환할 수 있다.
// 비동기적 코드의 이해
setTimeout(function () {
console.log("여기가 먼저 실행될까?!?!");
}, 1000);
console.log("여기좀 봐주세요!!!");
여기좀 봐주세요!!!, 여기가 먼저 실행될까?!?!
setTimeout은 대표적인 비동기적 코드로 1000ms의 딜레이로 아직 콜백 함수가 실행되지 않았음에도 바로 다음 코드가 실행되는 걸 볼 수 있다.
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,
"에스프레소"
);
가독성이 떨어진다
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, '에스프레소');
각 함수에 이름을 붙여 다음에 실행될 함수를 콜백함수로 넣어주었다. 하지만 일일이 이름이 붙여야 하는 번거로움이 있다.
// 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) {
// 이 안에서도 새로게 promise를 만든다
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ", 에스프레소";
console.log(name);
resolve(name);
}, 500);
});
})
.then(function (prevName) {
// 이 안에서도 새로게 promise를 만든다
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ", 카페모카";
console.log(name);
resolve(name);
}, 500);
});
})
.then(function (prevName) {
// 이 안에서도 새로게 promise를 만든다
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ", 카페라떼";
console.log(name);
resolve(name);
}, 500);
});
});
에스프레소
에스프레소, 에스프레소
에스프레소, 에스프레소, 카페모카
에스프레소, 에스프레소, 카페모카, 카페라떼
=> Promise의 인자로 넘어가는 콜백함수는 바로 실행된다 즉 function (resolve) {
setTimeout(function () {
var name = "에스프레소";
console.log(name);
resolve(name);
}, 500);
})가 즉시 실행된다. 이 콜백 함수는 resolve와 reject 두 개의 콜백 함수를 인자로 가지며 각각 비동기 작업이 성공 / 실패했을 때 호출한다. resolve가 실행되면 then 메서드가 실행되어 다음 코드를 실행한다.
var addCoffee = function (name) {
return function (prevName) {
// 이 안에서도 새로게 promise를 만든다
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("카페라떼"));
에스프레소
에스프레소, 아메리카노
에스프레소, 아메리카노, 카페모카
에스프레소, 아메리카노, 카페모카, 카페라떼
=> addCoffee("에스프레소")() addCoffee 함수를 호출한다 이때 name 인자에 "에스프레소"를 전달한다. 그 결과로 prevName을 매개변수로 하는 함수를 반환한다. 하지만 호출 시 prevName 인자를 생략하였으므로 var newName = prevName ?${prevName}, ${name}
: name; 이 삼항 연산자에 의해 newName에 "에스프레소"만 할당한다. 그리고 resolve가 호출되어 newName을 반환한다. 이후 반환된 값이 then 메서드에 전달된다.
// 1 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ", " + name : name);
}, 500);
};
// 2 제너레이터 함수 선언
// yield 키워드로 순서 제어
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();
에스프레소
에스프레소, 아메리카노
에스프레소, 아메리카노, 카페모카
에스프레소, 아메리카노, 카페모카, 카페라떼
=> coffeeMaker.next(); 제너레이터 함수 실행.
var espresso = yield addCoffee("", "에스프레소"); yield 표현식에서 일시적으로 멈추고 비동기 작업을 기다린다. (addCoffee)
addCoffee가 호출되어 500ms 이후 next 메서드를 호출하여 중단했던 부분부터 다시 코드를 실행한다.
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));
};
// promise를 반환하는 함수인 경우 await를 만나면 무조건 끝날 때까지 기다린다
await _addCoffee("에스프레소");
console.log(coffeeList);
await _addCoffee("아메리카노");
console.log(coffeeList);
await _addCoffee("카페모카");
console.log(coffeeList);
await _addCoffee("카페라떼");
console.log(coffeeList);
};
coffeeMaker();
에스프레소
에스프레소, 아메리카노
에스프레소, 아메리카노, 카페모카
에스프레소, 아메리카노, 카페모카, 카페라떼
=> coffeeMaker(); 호출. 이는 anync 함수이다.
await _addCoffee("에스프레소"); _addCoffee 호출 이때 작업이 끝날 때까지 기다린다.
coffeeList += (coffeeList ? ", " : "") + (await addCoffee(name)); 처음 할당된 coffeeList의 값은 빈문자열이므로 "" 반환. 그리고 addCoffee 호출하여 반환한 값을 붙인다.
addCoffee를 호출하여 500ms 후에 "에스프레소" 반환. 이를 coffeeList에 더한다.