※ 책 [혼자 공부하는 자바스크립트]를 공부하며 기록, 정리해 놓은 노트이다.
목차
함수의 기본 형태
요약:
익명 함수
(=함수 표현식)
function () {}
: 이름이 없는 함수
const sayHello = function(){
return "hello, world!"
}
선언적 함수
(=함수 선언식)
function 함수이름() {}
: 이름 있는 함수
function sayHello () {
return "hello, world!"
}
매개변수와 리턴값
매개변수: 함수의 괄호 안에 넣는 변수
리턴값: 함수의 최종적인 결과. return 키워드 뒤에 붙인다.
기본적인 함수 예제
윤년을 확인하는 함수
<script>
// 연도를 입력하면 윤년이면 true를 반환하는 함수
function isLeapYear(year) {
// 4로 나누어 떨어지는 해는 보통 윤년인데
// 100으로 나누어 떨어지면 윤년이 아니고
// 또 400으로 나누어 떨어지면 윤년이다.
return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)
}
</script>
- 근데 이게 어떻게 400을 true로 뱉는다는 거지? 저 || 부분 말이야…
⇒ A. &&가 우선돼서 계산된다. 사실은 ( (year % 4 === 0) && (year % 100 !== 0) ) || (year % 400 === 0) 인 것이다. 그래서 year가 400일 땐 (true && false) || true ⇒ false || true ⇒ true가 되는 것.
- 근데 짧은 조건문에서는 앞에 것이 false면 뒤에 것은 보지도 않는다고 했는데… 아니다. 어쨌든 뒤에가 or라서 보긴 해야되는 거구나.
A부터 B까지 더하는 함수
// A부터 B까지 모든 정수를 더해서 반환하는 함수
// 예를 들어 1과 5를 받으면 1+2+3+4+5 = 15를 반환함
function addAtoB(A, B) {
let sum = 0
for (let i = A; i <= B; i++) {
sum += i
}
return sum
}
만약 let sum을 선언만 해주고 초기 할당을 해주지 않으면 리턴값이 NaN이 된다. 왜냐면 for문 안에서 sum += i를 할 때 ‘이미 할당된 상태인’ sum을 한 번 가져와줘야 하기 때문에 문제가 생기는 것.
최솟값 구하는 함수
// 배열을 받아 최솟값을 반환하는 함수
function getMin(array) {
let min = array[0]
for (let val of array) {
if (val < min)
min = val
}
return min
}
나머지 매개변수 rest parameter
가변 매개변수 함수: 호출할 때 매개변수의 개수가 고정되어 있지 않은 함수. 나머지 매개변수(…)를 이용해 만든다.
function 함수이름(…나머지 매개변수) { }
⇒ “…”뒤의 매개변수들이 배열로 전달된다.
function sample(...items) {
console.log(items)
}
> sample(1, 2, 3)
[1, 2, 3]
나머지 매개변수와 일반 매개변수 조합하기
function 함수이름(매개변수, 매개변수, …나머지 매개변수) { }
당연히 ‘나머지’로 퉁쳐질 매개변수들의 자리는 매개변수들 중 가장 마지막이 되어야 한다.
> function sample(a, b, ...c) {
console.log(a, b, c)
}
> sample(1)
1 undefined []
> sample(1, 2)
1 2 []
> sample(1, 2, 3, 4, 5)
1 2 (3) [3, 4, 5]
일반 매개변수를 비우고 호출하면 ⇒ ‘undefined’로 전달됨
나머지 매개변수를 비우고 호출하면 ⇒ 빈 배열로 알아서 전달됨
필요 매개변수를 안 채웠다고 에러가 나지는 않는듯..?
매개변수의 자료형에 따라 다르게 작동하는 getMin()함수
// 배열이나 나열된 매개변수(=나머지 매개변수)로 숫자들을 넣으면
// 그 중 최솟값을 반환하는 함수
// 필요 매개변수 자리를 다 안채우고 호출해도 에러는 안 나는 걸 활용하면...
function getMin2(num1, ...nums) {
let items
let min
// 매개변수의 자료형에 따라 조건 분기하기:
// 첫 인수로 숫자가 아닌 배열을 넣어버렸을 경우에 대비해, 첫 수가 배열인지를 체크해줘야 한다
if (Array.isArray(num1)) {
items = num1 // 첫 인자인 num1이 배열이다
min = items[0]
} else {
// 아니다 ...nums쪽이 배열이다
items = nums
min = num1
}
for (let item of items) {
if (item < min)
min = item
}
return min
}
객체가 정확히 배열인지 확인할 때 : Array.isArray(대상객체)
전개 연산자 spread operator
: 배열을 전개해서 함수의 매개변수로 전달해주는 연산자 “…”. 나머지 매개변수 때처럼 배열 앞에 마침표 3개를 찍어 전달해주면 된다. 얘는 호출할 때 쓰는 애라는 것 명심!
함수이름(…배열)
// 같은 함수에 전개 연산자(...)로 호출을 해주고 말고의 차이:
function sample(...items) {
console.log(items)
}
const array = [1, 2, 3, 4]
console.log('# 전개 연산자 사용 안 한 경우:')
sample(array) // => items = [array] => [[1,2,3,4]]
console.log('# 전개 연산자를 사용한 경우:')
sample(...array) // => items = [1,2,3,4]
=> 결과:
# 전개 연산자 사용 안 한 경우:
[Array(4)]
# 전개 연산자를 사용한 경우:
(4) [1, 2, 3, 4]
기본 매개변수
함수이름(매개변수, 매개변수=기본값)
기본값을 지정하는 매개변수는 뒤쪽으로 몰아줘야 함을 잊지 말자.
// 기본 매개변수를 활용한 윤년 함수 - 올해가 윤년인지 자동으로 확인하는 함수:
function isLeapYear(year=new Date().getFullYear()) {
console.log(`매개변수 year: ${year}`)
return (year % 4 === 0) && (year % 100 !== 0) || (year % 400 === 0)
}
console.log(`올해는 윤년일까? ==> ${isLeapYear()}!`)
=> 결과:
매개변수 year: 2022
올해는 윤년일까? ==> false!
- 기본 매개변수 자리에 저런식으로 메소드 리턴값을 넣을 수도 있다.
- 이전에 isLeapYear(year) 이렇게 정의된 함수가 있더라도 새로 정의된 isLeapYear(year=기본값)으로 덮어씌워진다. 변수, 상수는 같은 이름으로 재정의가 안되는데 함수는 허락해놨네?
⇒ A. 어쨌든 같은 시그니처를 가진 함수가 여러 개면 제일 마지막 것으로 확정된다는 룰이 있다고 한다.
- 자바스크립트는 함수가 정의된 순서가 아니라 호출된 순서로 실행된다고 하던데, 그럼 같은 시그니처를 가진 함수들 ‘사이에’ 이 함수명을 호출하면 어떻게 될까? = 아예 정의되기 전의 함수를 호출하면 어떻게 될까? 일단 거기까지 읽어들이는 건 순차적으로 하는 거긴 한건가? 그러니까 애초에 함수를 읽어들여서 ‘저장’은 해뒀어야 하는 것일 것 같다.
- ⇒ 선언적 함수는
let max = function (array) { }
와 같은 기능을 수행한다고도 명시되어 있는데, 정확히 이렇게 사용했을 때 let max는 여러번 정의하지 못함을 확인했다. 아주 정면 대립각인데…⇒ A.
const max = function (array) { }
처럼 상수로 선언해주는 게 맞고, 이렇게 하면 ‘나도 모르게 이미 정의된 함수인 줄 모르고 또 덮어씌우는(오버로딩하는) 우’를 범하지 않을 수 있다는 장점이 있다. 한 번 더 체크해주는 안전바랄까.
- 아니면, 기본값을 정해둔 매개변수와 일반 매개변수를 각각 다른 함수로 인식해버리는 건가??
⇒ A. 그렇지는 않다고 한다. 조금 다른 얘기를 덧붙이자면 차라리 일반 매개변수와 기본 매개변수를 각각 다른 타입의 변수로 지정하고 싶을 때 일반과 기본을 섞어 오버로딩을 한다고 함.
구버전 자바스크립트에서는…
나머지 매개변수가 없었다
그래서 가변 매개변수 함수를 구현할 때 arguments라는 배열 비슷한 변수를 함수 내부적으로 사용했었다.
전개 연산자가 없었다
그래서 배열을 전개해서 인자로 전달하고 싶을 땐 apply()라는 함수를 이용했다.
기본 매개변수도 없었다
그래서 삼항연산자나 짧은 조건문을 활용해 함수 내부에서 기본값을 세팅해줬다.
한마디로 함수에 일반 매개변수밖에 못 썼다. 부를 때도 마찬가지.
function 함수(1일반, 2기본, 3나머지) ⇒ 이 중에 2기본과 3나머지, 그리고 전개 연산자가 없었다니… 그냥 일반 매개변수밖에 못썼다는 얘기잖아, 한마디로.
함수 고급: 함수를 매개변수로 전달하는 특성
요약:
지금까지 본 함수들:
- 선언적 함수 (=함수 선언식)
의 활용: 선언적 함수를 만드는 대신 익명 함수로 만들고 ‘상수’에 저장하는 꿀팁. ‘전에 이미 정의되었던 것인지’ 상수 재정의 문제로 한 번 더 체크해주기 때문에 더 안전하다.
function max(array) { } // 선언 방식 = const max = function (array) { } // 익명 방식
- 익명 함수 (=함수 표현식)
function () { let pi = 3.141592 console.log(`새로운 파이 값은 ${pi}입니다.`) })
- 익명 함수를 곧바로 호출하는 즉시 호출 함수: 변수 이름 충돌 문제를 해결하기 위한 용도. 블록을 다르게 하기 위한 처치일 뿐.
(function () { let pi = 3.141592 console.log(`새로운 파이 값은 ${pi}입니다.`) })()
- 익명 함수를 곧바로 호출하는 즉시 호출 함수: 변수 이름 충돌 문제를 해결하기 위한 용도. 블록을 다르게 하기 위한 처치일 뿐.
- 화살표 함수: 익명 함수의 변형
- 선언적 함수를 콜백 함수로 가지는 선언적 함수
- 익명 함수를 콜백 함수로 가지는 선언적 함수
- ⇒ 그렇게 보니 익명 함수는 콜백 함수를 가질 수가 없겠다. 콜백 함수로 뭐가 쓰이기로 ‘약속된’ 상태여야 하니.
- 화살표 함수를 콜백 함수로 가지는 선언적 함수 : 화살표는 익명 함수의 변형임을 기억하고, 그 익명함수는 보따리 함수에서 (배열의)요소마다 호출된다는 것을 기억하면 해석 끝!
콜백 함수 callback function
: 매개변수로 전달하는 함수
자바스크립트는 (몸집이 큰)함수도 하나의 자료형이라는 개념으로 접근한다. 그래서 매개변수로 전달할 수 있다.
선언적 함수를 콜백으로 사용하기:
<script>
console.log('\n# 선언 함수로 콜백 사용: ')
function callThreetimes(callback) {
for (let i = 0; i < 3; i++)
callback(i)
}
function print(i) {
console.log(`${i}번째로 함수 호출`)
}
callThreeTimes(print)
</script>
=> 결과:
# 선언 함수로 콜백 사용:
0번째로 함수 호출
1번째로 함수 호출
2번째로 함수 호출
저기에 인자로 전달되는 ‘함수명’이 내부에서 ‘callback’이란 이름으로 통하게 되는 방식이다.
그래서 print()같은 함수를 전달했다면 내부에선 print(0)
, print(1)
, print(2)
이렇게 쌈빡한 방식으로 인자까지 받아서 호출될 수 있는 것이다!
콜백으로 ‘함수 이름만’ 넣어주므로 그 넘겨지는 함수가 무슨 매개변수값들을 갖든지 상관없다!?
⇒ 아니다. 사전에 콜백으로 받을 함수가 몇 개의 어떤 매개변수들을 가질지 약속되어야 한다.
익명 함수를 콜백으로 사용하기:
// 익명 함수로 콜백 사용하기:
// callThreeTimes(callback) 함수는 위와 똑같으므로 재활용하고,
console.log('\n# 익명 함수로 콜백 사용: ')
callThreeTimes(function (i) {
console.log(`${i}번째로 함수 호출`)
})
=> 결과:
# 익명 함수로 콜백 사용:
0번째로 함수 호출
1번째로 함수 호출
2번째로 함수 호출
콜백(함수)을 보따리에 넣고 함께(먼저) 불리는 함수(=”you could call the function with a callback”), 콜백을 실행하는 함수(=”a function that runs the callback”)를 임시로 보따리 함수라고 불러야겠다.
그럼 콜백 함수를 사용하는 두 가지 단계가 생기는데,
- 보따리 함수를 정의할 땐 :
function callThreetimes(callback) { }
⇒ 그냥 콜백 함수 이름만 딸랑 (일반 매개변수로)넣어서 정의. 근데 이 단계에서 이미 콜백으로 들어올 함수가 몇 개의 어떤 역할의 매개 변수를 가질지 약속된 상태여야 한다. 그래야 보따리 함수에서 그 매개변수들로 조믈락거릴 수 있을 테니까. 마치 자바의 함수형 인터페이스 같다. 대강 ‘한 인자를 받아 불리언을 반환하는 형’, ‘두 인자를 받아 한 값을 반환하는 형’ 등등으로 대분류가 나뉘어 있고 거기에 맞는 별명을 붙인 걸로 보따리 함수 정의에 이용했던 것처럼, 여기서도 그렇다고 보면 되겠다.
- 보따리 함수를 호출할 때:
callThreeTimes(print)
혹은callThreeTimes(function (i) { })
⇒ 이미 선언되어 있는 함수면 그냥 함수명만, 익명 함수를 그자리에서 만든다면 매개 변수 형식을 갖추어서 콜백으로 넣어준다.// 위에서 익명 함수로 콜백을 넣어줄 때, 매개변수를 i가 아니라 yohoho같은 걸로 넣어줘도 똑같이 기능한다: callThreeTimes(function (yohoho) { console.log(`${yohoho}번째로 함수 호출`) })
2-1. 따라서 콜백으로 들어가는 함수의 매개변수 이름을 뭘로 하든 상관없다. 콜백 함수 내에서 일관성을 갖추기만 한다면! 왜냐면 보따리 함수에서 콜백이 불릴 땐 print(i) ⇒ print(0) 처럼 변수명이 값으로 치환되어 불리게 되기 때문이다. 한 마디로, 보따리 안의 변수들은 보따리 안에서만, 콜백 안의 변수들은 콜백 안에서만 일관되게 작성되면 된다. 그럼 문제 없이 콜백이 호출되고 실행된다.
이것처럼:
// 보따리 함수 정의: function callThreetimes(callback) { for (let i = 0; i < 3; i++) callback(i) } // 선언 함수로 콜백을 사용: function print(yohoho) { console.log(`${yohoho}번째로 함수 호출`) } callThreeTimes(print) // print(0), print(1), print(2)의 순서로 불리게 됨. // 익명 함수로 즉석에서 콜백 넣어줌: 역시나 익명(0), 익명(1), 익명(2)의 순서로 불리게 된다. callThreeTimes(function (yohoho) { console.log(`${yohoho}번째로 함수 호출`) })
이 두 단계만 기억하자!
콜백 함수를 활용하는 메소드 - 배열.forEach()
배열에 딸린 보따리 메소드들은, (만약 콜백을 받는 함수라면) 콜백 형식으로 function (value, index, array) { }
이렇게 다들 넣어주면 된다. forEach() 뿐만 아니라 map()이나 filter() 등도 마찬가지. 한 마디로 배열의 메소드들이 받는 콜백은 value, index, array 이 순서와 용도로 매개변수들이 약속되어 있다, 이 얘기다. 이 때 index와 array는 필요없으면 명시하지 않아도 된다.
// 배열.forEach() 메소드:
const numbers = [273, 52, 103, 32, 57]
numbers.forEach(function (value, index, array) {
console.log(`${index}번째 요소 : ${value}`)
})
해석: forEach()는 콜백을 매개 변수로 받는 보따리 함수다. 보따리 함수가 내부적으로 어떻게 생겼는지는 몰라도, ‘익명(매개변수1, 매개변수2, 매개변수3)’ 이렇게 콜백을 부르도록 약속되어 있을 것이다.
⇒ 익명 함수로 콜백이 주어졌다: “이 보따리는 이런이런 매개변수 3개를 가지는 콜백을 받기로 약속돼있구만!”을 곧바로 읽어내면 됨.
⇒ 보따리의 역할은? 콜백의 매개변수로 쓰인 네이밍이 바로 그 뜻대로 행동하도록 기능을 구현해주는 역할. 즉 저 위의 ‘index’라는 변수가 진짜 ‘array’의 인덱스 값이 될 수 있도록 요리해서 반환해줌.
forEach()의 콜백이 가진 약속: 매개 변수 3개를 받는다. 리턴값은 없다. 그 자체 내에서 뭐라도 하라는 뜻.
forEach() 보따리가 하는 일: 자기자신 배열의 요소마다 callback을 불러줌. 끝.
자기자신 배열에서 요소 각각의 값, 인덱스, 배열(자기자신)을 콜백에 순서대로 인자로 넣어서 호출한다. 끝!
// 상상해본 forEach의 모습: forEach(callback) { // 자기자신 배열을 this라고 한다면, for (let i = 0; i < this.length; i++) { callback(i, this[i], this) } }
콜백 함수를 활용하는 메소드 - 배열.map()
: 콜백 함수에서 리턴한 값들을 기반으로 새로운 배열을 만드는 함수
// 콜백 내부에서 값들을 제곱하고, 보따리인 map()은 그걸로 새로운 배열을 만들어 준다:
let numbers2 = [273, 52, 103, 32, 57]
numbers2 = numbers2.map(function (value, index, array) {
return value * value
})
numbers2.forEach(console.log)
=> 결과:
74529 0 (5) [74529, 2704, 10609, 1024, 3249]
2704 1 (5) [74529, 2704, 10609, 1024, 3249]
10609 2 (5) [74529, 2704, 10609, 1024, 3249]
1024 3 (5) [74529, 2704, 10609, 1024, 3249]
3249 4 (5) [74529, 2704, 10609, 1024, 3249]
이게 뭐야?!!
map()의 콜백이 가진 약속: 매개 변수 3개를 받는다. 리턴값으로 (매개 변수 3개를 마구 요리하고 난) 하나의 결과값을 반환한다.
map() 보따리가 하는 일: 자기자신 배열의 요소마다 callback을 불러줌 + 새 배열을 반환함.
자기자신 배열이 가진 요소들 하나하나를 루프 돌면서 각각의 값, 인덱스, 배열(자기자신)을 콜백에 순서대로 인자로 넣어서 호출한다. 되돌아온 값들을 새로운 배열에 차례대로 넣어서 반환한다.
// 상상해본 map()의 모습: map(callback) { // 자기자신 배열을 this라고 한다면, const new_list = [] for (let i = 0; i < this.length; i++) { new_list.push(callback(i, this[i], this)) } }
numbers2.forEach(console.log) 해석:
forEach()의 콜백은 매개 변수 3개를 가져야 하는데, console.log()가 그런 애였던가?
console.log()의 정의: log(...data: any[]): void;
가변 매개변수 함수였군. 가변 함수면 콜백의 매개 변수 제한 조건에서도 자유로운가보다. 즉 콜백조건으로 아무리 ‘넌 매개 변수 3개짜리여야 해!’라고 깐깐하게 굴어도 ‘난 1개 이상 매개변수를 가진다’의 가변 함수면 다 그 자리에 들어갈 수 있는 것(?)
잠깐만… log()는 결국 매개변수로 배열을 받고 리턴값이 없는 함수인 건데, forEach() 보따리는 콜백에게 매개 변수 3개만 전달해주고는 알아서 하라고 냅주는 애인데, 뭐지..? forEach()에게… 아! 둘 다 void를 반환하는 경우에는 보따리 → 콜백에게로 흐름이 넘어가는 거라고 보면 되듯이, forEach()는 그냥 log()에게 매개 변수 3개를 전달해주고 실행이 완전히 끝났다. 이 3개를 나머지 매개변수 즉 배열로 받아들인 log()는 이제 자기가 할 일, 즉 전달받은 매개 변수 모두를 차례로 화면에 출력해주는 역할을 하게 되는 것이다. 그래서 출력값이 저 위에처럼 ‘값, 인덱스, 배열자체’로 나왔던 거군.
- numbers2.forEach(console.log(1)) 같이 해주면? [1, 값, 인덱스, 배열]로 처리되어 차례로 출력될까, 콜백에 자체 인자는 넣으면 안된다고 에러가 뜰까?
⇒ 1이 출력되고 Uncaught TypeError: undefined is not a function 에러가 뜬다.
⇒ A. 대신 numbers2.forEach((number) => { console.log(number); ...; }); 이렇게 하면 원하는 파라미터를 넣어서 콜백을 넘겨줄 수 있다!! 보통 이렇게 많이 쓴다고 한다.
배열.map() 다른 예시:
const array1= [1,4,9,16];
//map 사용
const map1 = array1.map(x=>x*2);
//함수가 적용된 결과로 새로운 배열을 반환합니다.
console.log(map1); //[2, 8, 18, 32]
콜백 함수를 활용하는 함수 - 배열.filter()
: 콜백 함수에서 리턴하는 값이 true인 것들만 모아서 새로운 배열을 만드는 함수
// 콜백 내부에서 짝수인 수들을 '참'으로 반환하고, 보따리인 filter()는 콜백이 뱉는 리턴값들 중 '참'인 애들만 모아서 원래 요소로 새 배열에 넣고 반환해준다:
const numbers3 = [5, 4, 3, 2, 1, 0]
const evenNumbers = numbers3.filter(function (value) {
return value % 2 === 0
})
console.log(`\n원래 배열: ${numbers3}`)
console.log(`짝수만 추출: ${evenNumbers}`)
filter()의 콜백이 가진 약속: 매개변수 하나를 받는다. 리턴 값으로 불을 반환한다.
filter()가 내부적으로 하는 일:
자기자신 배열이 가진 요소들 하나하나를 루프 돌면서 콜백에 넣고 불러준다. ‘참’으로 되돌아 나오는 애들을 기억했다 새로운 배열에 넣어서 반환한다.
// 상상해본 filter()의 모습: filter(callback) { // 자기자신 배열을 this라고 한다면, const new_list = [] for (let val of this) { if (callback(val)) { new_list.push(val) } } }
배열.filter() 다른 예시:
const fruits= ['사과', '귤', '배', '감', '바나나', '키위'];
//filter 사용
const result = fruits.filter(fruit => fruit.length > 2);
//함수가 적용된 결과로 새로운 배열을 반환합니다.
console.log(result); //['바나나']
어떤 팁:
리턴값이 없는 콜백을 가지는 보따리 함수는 (변수를 넘겨주는)실행 흐름이 보따리 → 콜백 → 끝
이렇게 되는 것 같고, 리턴값이 있는 콜백을 가지는 보따리 함수는 큰 흐름이 콜백 → 보따리 → 리턴값
이렇게 되는 것 같다.
화살표 함수 arrow function
: 익명 함수(=함수 ‘표현식’)를 간단하게 사용하기 위한 목적으로 만들어진 함수 생성 문법. ‘function’ 키워드 대신 화살표 ‘⇒’를 사용한다. 주로 콜백 함수에 ‘function () { }’ 대신 사용된다.
(매개변수) ⇒ { }
function (value1, value2) { console.log(value1 * value2) }
⇒
(value1, value2) => { console.log(value1 * value2) }
혹은
(매개변수) ⇒ 리턴값
function (value) { return value * value }
⇒
(value) => value * value “(매개 변수) ⇒ 리턴값” 형태
블록이 없으면 리턴값, 있으면 실행문장도 덧붙여있다는 소리(주로 리턴값이 없는 void).
“return” 키워드를 생략하여 한 줄로 표현하는 방식을 암시적 반환(implicit return)이라고 한다.
map()에 적용해 보기:
// 1단계. 원래의 익명 함수 형태:
let array1 = [0, 1, 2, 3, 4]
array1 = array1.map(function (value, index, array) {
return value * value
})
console.log(`\n${array1}`)
// 2단계. 콜백에 필요한 매개변수만 입력해서 사용한 형태:
let array2 = [0, 1, 2, 3, 4]
array2 = array2.map(function (value) {
return value * value
})
console.log(`\n${array2}`)
// 3단계. 화살표 함수로 만들어 버린 콜백:
let array3 = [0, 1, 2, 3, 4]
array3 = array3.map((value) => value * value)
console.log(`\n${array3}`)
=> 1, 2, 3단계 모두 결과는:
(5) [0, 1, 4, 9, 16]
메소드 체이닝 method chanining
: 어떤 메소드가 리턴하는 것을 기반으로 해서 함수를 줄줄이 사용하는 것.
배열의 메소드의 ‘콜백을 화살표 함수로’ 구현하고 ‘메소드 체이닝’으로 줄줄이 엮은 예제:
// 화살표 함수와 메소드 체이닝: filter() -> map() -> forEach()
let numbers4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers4
.filter((value) => value % 2 === 0)
.map((value) => value**2)
.forEach((value) => {
console.log(value)
})
타이머 함수
타이머 걸기: 특정 시간 후에 / 특정 시간마다 콜백 함수를 호출한다.
setTimeout(함수, 시간)
, setInterval(함수, 시간)
타이머 종료하기: setTimeout()과 setInterval() 함수로 설정한 타이머를 제거한다.
clearTimeout(타이머_ID)
, clearInterval(타이머_ID)
// 타이머 함수로 타이머 걸기:
setTimeout(() => {
console.log('1초 후에 실행됩니다')
}, 1 * 1000)
let count = 0
let id
id = setInterval(() => {
console.log(`1초마다 실행됩니다 (${count}번째)`)
count++
}, 1 * 1000)
// 그리고 10초 후에 저절로 위의 반복 타이머를 끝나게 하기:
setTimeout(() => {
console.log('== 10초가 지나 반복 실행되던 타이머를 끝냅니다 ==')
clearInterval(id)
}, 10 * 1000)
즉시 호출 함수
: 익명 함수를 생성하고 곧바로 호출하는 패턴. 변수의 이름 충돌을 막기 위해서 코드를 안전하게 사용하는 방법이다.
(function () { })()
⇒ HTML에서 script 태그를 여러 개 사용하다보면 변수 이름이 자주 충돌하게 되는데… 같은 스코프 단계에 있는 (같은 이름의)변수는 무조건 충돌이 일어난다. 그래서 즉시 호출 함수랑 무슨 상관이라는 거지?
스코프scope: 변수가 존재하는 범위
⇒ 스코프 단계를 변경하는 방법:
- (그냥 맥락없이) 중괄호를 사용해서 블록을 만들거나,
- 함수를 생성해서 블록을 만든다.
그러면 섀도잉이 되어 같은 이름의 변수 충돌을 막을 수 있다.
섀도잉shadowing: 내부 변수가 외부 변수를 가리는 현상
자 여기서 문제!
구 버전 자바스크립트에서 사용하던 변수 var 키워드는 2번 방법을 통해서만 섀도잉이 됐었다.
그래서 아예 충돌이 될 만한 문장들을 다 급조한 함수 블록 안에 넣어버리는 방법을 많이들 사용했다고(그리고 아마 지금도).
결론: 충돌 문제를 해결하기 위해 즉시 호출 함수를 만들어 사용한다.
예시:
// 이렇게 같은 이름을 쓰면 Identifier 'pi' has already been declared 에러가 난다:
<script>
let pi = 3.14
console.log(`파이 값은 ${pi}입니다.`)
</script>
<script>
let pi = 3.141592
console.lot(`새로운 파이 값은 ${pi}입니다.`)
</script>
// 그래서 나중에 또 정의되는 변수명을 즉시 호출 함수로 감싸주고 바로 호출한다:
<script>
let pi = 3.14
console.log(`파이 값은 ${pi}입니다.`)
</script>
<script>
(function () {
let pi = 3.141592
console.lot(`새로운 파이 값은 ${pi}입니다.`)
})()
</script>
엄격 모드 strict mode
<script> ‘use strict’ 문장… </script>
: ‘use strict’라고 블록 첫머리에 박아놓고 시작하는 자바스크립트 작성법
모질라 엄격 모드 문서 | 엄격 모드 - 모던 JavaScript 튜토리얼
즉시 호출 함수를 쓸 때 이 엄격 모드를 적용하는 경우가 많다. 해당 블록(=함수 내부)에서만 엄격 모드가 적용된다.
// 일반적인 엄격 모드 사용 패턴: 즉시 호출 함수를 쓸 때
<script>
(function () {
'use strict'
실행 문장(들)
})()
</script>
보아하니, ‘use strict’가 적힌 해당 블록 내에서만 엄격 모드가 적용된다는 것 같다.
엄격 모드를 사용하면 실수를 줄일 수 있어서 좋다.
익명 함수와 선언적 함수의 차이
선언적 함수와 익명 함수는 사용하는 상황이 비슷해서 주변 동료들과 맞춰서 편한 대로 쓰는 편이라고 한다.
같은 블록 내에서의 선언적 함수를 먼저 쭈욱 생성하고, 익명 함수는 그 후에 처음부터 한 줄 한 줄 코드를 실행하다가 만났을 때 생성하게 된다!
따라서 선언적 함수는 정의 전에 호출을 해도 괜찮고, 익명 함수는 반드시 순서(정의→ 호출)대로 호출이 위치해야 한다.
ex) 둘이 짬뽕된 경우:
// 1. 익명 함수를 생성
함수 = function () {
console.log('익명 함수입니다.')
}
// 2. 선언적 함수를 생성
function 함수() {
console.log('선언적 함수입니다.')
}
// 3. '함수'를 호출
함수()
⇒ 실행은 2 → 1 → 3번 순서가 된다. 따라서 ‘익명 함수입니다.’가 출력되게 된다.
- 어쨌든 “선언적 함수는 우리가 코드를 읽는 순서와 다른 순서로 함수가 선언되는데, 함수를 같은 이름으로 덮어쓰는 것은 굉장히 위험한 일이다. 그래서 안전하게 사용할 수 있는 익명 함수를 더 선호하는 것이다”라는데, 이게 웬말??? 어쨌든 같은 이름으로 또 정의했을 때 둘 다 덮어 씌워지기는 매한가지잖아?
코드 블록: <script>
태그로 구분된 공간 또는 함수나 반복문이나 어쨌든 블록{ }
으로 구분된 공간. 변수명의 중복을 구분하는 기준이 된다.
블록이 다른 경우에는, 한 블록을 훑어서 선언적 함수를 먼저 만들고 다시 그 블록을 처음부터 실행한 후에, 그 다음 블록으로 넘어가 같은 작업을 계속한다.
ex) 블록이 다른 경우에 선언적 함수가 실행되는 순서:
<script>
// 1.
선언적함수()
// 2.
function 선언적함수() {
console.log('1번째 선언적 함수입니다.')
}
</script>
<script>
// 3.
function 선언적함수() {
console.log('2번째 선언적 함수입니다.')
}
</script>
<script>
// 4.
선언적함수()
<script>
—| | | —> 블록 A | | | __|
—| | —> 블록 B | __|
—| | —> 블록 C __|
⇒ 우선 블록으로는 A → B → C 순차적으로 실행된다. 그리고 블록 당 선언적 함수가 먼저 ‘훑어’져 정의되는 걸 적용하면, 2 → 1 → 3 → 4 이런 순서가 된다.
⇒ 결과: 1번째 선언적 함수입니다. 2번째 선언적 함수입니다.
- “이처럼 블록이 나뉘어진 경우에는 선언적 함수의 실행 흐름을 예측하는 것이 훨씬 힘들어집니다. 다른 프로그래밍 언어에 비해 자바스크립트는 <script>태그의 사용으로 이처럼 블록이 예상하지 못하게 나뉘는 문제 등이 발생할 수 있어 안전을 위해 익명 함수를 더 많이 사용하는 편입니다.” ⇒ 블록이 예상 못하게 나뉘는 문제까지는 이해하겠다. 근데 왜 익명함수가 더 안전하다는 것일까?
현대적인 키워드 let과 const는 동명 정의를 애초에 방지한다. = 더 안전하다.
예를 들면 같은 이름의 (선언적)함수 → 이후에 다시 let (익명)함수로 재정의하려고 할 시 let이 ‘같은 이름으로 또 정의하냐’ 에러를 내보낸다.
근데 같은 이름의 선언적 함수 → var 익명 함수로 재정의 할 때는 통과가 된다.
같은 이름의 let/const → const/let의 교차 재정의 뿐만 아니라, function → let/const 의 재정의 또한 막는다. var → let/const 또한 마찬가지. 이를 보면 let과 const가 ‘재선언으로 불리는 쪽에서’ 중복 이름을 체크하고 방지한다는 것을 알 수 있다.
옛날의 var는 그러질 못하고 그냥 덮어씌웠다고 한다(?). 그래서 덮어쓰는 문제가 발생함.
- 근데 “var 키워드는 함수 블록을 사용하는 경우에만 변수 충돌을 막을 수 있다.”는 말(p232)은, 결국 충돌이 일어난다는 거잖아? 그걸 막을 수 있는 방법이 함수 블록 하나밖에 없다는 게 애로사항이었던 거고. 그래서 즉시 호출 함수를 끌어와 썼던 거고. 어쨌든 그렇다면 var도 똑같은 걸 재정의 하는 데에 제동을 거는 기능이 있었다는 건데…
⇒ 이 경우엔 같은 이름의 선언적 함수 → var 익명 함수로 재정의 하는 경우만을 말한 것인듯 싶다.
아무튼 그래서 오류의 위험을 줄이기 위해 한 가지로 통일해서 사용하는 게 좋다면, 익명 함수로 통일하는 편이 안전해서 더 선호된다고 한다.
참고:
[책] 혼자 공부하는 자바스크립트 - 윤인성
Uploaded by N2T