함수의 결과가 반복적으로 사용될 경우 변수에 담아서 사용하는 것이 좋고
단일로만 사용된다면 함수의 결과가 사용되는 곳에서 함수를 호출하는 것도 효과적이다.
함수 선언문과 함수 표현식
함수 선언문(정의문): function 키워드와 함수의 이름을 직접적으로 선언하는 방법. (기명함수)
function sum(x, y) {
return x + y
}
함수 표현식: 함수의 이름을 사용하지 않고 변수에 담아서 사용하는 방법. (익명함수)
함수를 값으로 취급하기 때문에 함수에 이름이 없더라도 변수에 담아서 값을 받을 수 있다. 변수에 함수를 넣게 되면 그 변수가 그 함수의 이름을 대신하게 된다.
const sum = function (x, y) {
return x + y
};
sum(1, 2);
식은 세미콜론으로 끝나는 특징이 엄밀히 존재한다.
return 알아둘 점
const sum = function (x, y) {
if (x < 2) {
return
}
return x + y
}
console.log(sum(7, 3)) // 결과: 10
return를 함수 안에서 사용할 수 있고 사용되면 함수 밖으로 어떤 데이터를 내보내기도 하지만 그 부분에서 함수가 종료된다는 것을 알 수 있다.
즉시 실행 함수, IIFE(Immediately-Invoked Function Expression)
정의되자마자 즉시 실행되는 함수 표현식을 말한다. 'Self-Executing Anonymous Function'이라고도 불리며, 전역 스코프에 불필요한 변수를 추가해서 오염시키는 것을 방지할 수 있을 뿐 아니라 IIFE 내부 안으로 다른 변수들이 접근하는 것을 막을 수 있는 방법이다. -MDN
익명함수이기 때문에 재사용은 적합하지 않다! 물론 기명으로도 가능은 하다.
하지만 보통 함수에게 이름을 짓는 것은 호출이 목적인 경우가 대부분인데,
한번 자동으로 실행된 이후 생명을 다하는 IIFE에게 이름을 지어주는 것은 의미가 없다.
목적
IIFE는 외부에서 접근할 수 없는 자체 Scope를 형성한다.
Parser는 JavaScript에서 변수의 Scope가 함수에 의해 정해진다는 것을 알고 있다. 그러므로 IIFE 함수는 상위 Scope에 접근할 수 있으면서도, 내부 변수를 외부로부터 보호해 Privacy를 유지할 수 있다.
따라서 IIFE 사용의 가장 큰 목적은 데이터 프라이버시와 코드 모듈화라고 할 수 있다.
사용 예시
const a = 7
function double() {
console.log(a * 2)
}
double();
(function () {
console.log(a * 2)
})(); // 방법1: (x)()
(function () {
console.log(a * 2)
}()); // 방법2: (x()) 권장
(() => {
console.log(a * 2)
})(); // 방법3: () => ()
값으로 만들어주기 위해 ()로 한번 감싸주고 바로 호출해 준다. 즉시 실행 함수를 다시 한번 호출하고 싶다면?
불가능하다. 이름이 없고, 변수에 담지도 않았기 때문이다. 실행하는 순간 값으로 취급해서 바로 호출하고 끝나버린 것이다. 애플리케이션 내에서 단 한 번만 실행해야 할 경우 즉시 실행 함수를 만든다.
JavaScript에서 함수를 호출하는 3가지 방법.
- () 괄호를 열고 닫기.
- call이라는 함수가 제공하는 메서드 사용.
- apply를 이용해서 호출하는 방법.
funtionA();
funtionA.call();
funtionA.apply();
call과 apply는 일반적으로 쓰이는 호출 방법은 아니고 javascript인자와 관련된 특수한 용도가 있다.
javascript 같은 경우 함수 자체가 어떻게 만들어져 있든 이름이 같고 호출 문법만 맞는다면 문제없이 호출이 된다.
함수 입장에서 2가지 경우의 수를 생각해야 한다. 첫 번째, 호출할 인자가 없을 때, 두 번째, 인자를 더 많이 주었을 때.
1개만 받고 싶은데 2개, 3개 들어온다면 어떻게 처리할 것인가?
가변인자로 처리해 줄 수 있다. 예를 들어.
function sum(a, b, c) {
return a + b + c;
}
const abcSum = sum(10, 20, 30); // 60이 abcSum에 들어감.
만약 sum의 인자에 (10, 20) 두 개만 넣으면? (10, 20, 30, 20) 3개 이상으로 넣을 수 있다면? 더욱 용도가 다양해질 수 있다.
function sum() {
let s = 0;
for(let i = 0; i < arguments.length; i++) {
s = s + arguments[i];
}
return s;
}
const abcSum = sum(10, 20, 30, 50); // 110이 abcSum에 들어감.
arguments에는 유사배열의 형태로 인자들이 들어오고 있기 때문에 a, b, c라는 매개변수는 기술해 줄 필요가 없다.
하지만 이 코드는 문제가 있다. sum 함수의 시그니처(외모)를 보고 함수의 이름가 인자 정보를 알 수 있어야 하는데 알 수가 없다. 이 함수가 가변인자를 처리하는지, 인자를 아무것도 받지 않는지에 대해 말이다. 함수 내부를 열어봐야 알 수가 있다.
함수를 사용하기 위해 매번 소스코드를 열어보고 함수의 사용법, 함수가 어떻게 만들어졌는지를 매번 리딩해야 한다.
함수의 시그니처에 최대한 많은 정보를 표현해 주는 것이 중요하다. 그런 맥락에서 새로운 스펙이 추가되었다.
바로 전개 파라미터이다.
function sum(...args) {
let s = 0;
for(let i = 0; i < args.length; i++) {
s = s + args[i];
}
return s;
}
const abcSum = sum(10, 20, 30, 50); // 110이 abcSum에 들어감.
전개 파라미터를 이용하여 가변인자를 처리하는 함수라는 정보를 풍부하게 제공해 줄 수 있다.
call과 apply
공통점: 첫번째 인자로 context 객체를 받음.
차이점: call은 인수 목록을 apply는 인수 배열의 형태로 받는다.
function sum(...args) {
let s = 0;
for(let i = 0; i < args.length; i++) {
s = s + args[i];
}
return s;
}
sum.call(null, 10, 20, 30);
sum.apply(null, [10, 20, 30]);
두 함수 호출은 동일한 결과는 가져오고 형태만 다르다. call은 인자를 코드로 쓰고 있기에 인자를 추가하려면 코드 자체를 바꾸어야 한다.
apply는 데이터 즉, 배열의 형태로 들어오기에 다른 쪽으로 뺄 수 있다.
function sum(...args) {
let s = 0;
for(let i = 0; i < args.length; i++) {
s = s + args[i];
}
return s;
}
const arr = [10, 20, 30]
sum.apply(null, arr);
arr변수에 데이터만 추가하면 되고 호출 코드는 변하지 않는다.
생성기 함수: generator function
function에 *을 사용하여 나타낸다. 통상적인 함수의 작동 방식과 완전히 다르다. generator함수는 최초에 호출하면 함수가 실행되지 않고 실행 준비 상태로 만든다. 그리고 객체 하나를 반환하는데 그 객체 안에는 함수가 실행 준비를 마쳤으니 그 함수를 실행할 도구를 담은 객체를 반환한다. 그 도구를 이용해서 함수를 실행했다가 멈췄다가를 반복할 수 있다. 일반 함수와 달리 호출을 한 번 하고 여러 번 호출해서 그 함수가 종료되지 않았는데, 그 함수에 다시 들어갔다 나오고 들어갔다가 나오고. 이런 식의 동작을 할 수 있다.
코드를 작성하고 실행해 보면서 알아보자.
이 함수가 일반 함수라면 1번에서 바로 gen으로 들어가서 3,5,6 호출이 끝나고 1번의 g에 리턴하여 2,4,6을 실행할 것이다.
2번이 실행되면 3번의 yield로 갔다가 4번으로 돌아온 다음 5번으로 다시 들어간다. 아까 실행했던 내용을 스킵하고 다음 내용으로 들어갔다. generator함수는 실행을 일시 중지 시키고 바깥으로 나갔다가 next함수의 호출로 실행을 재개시킬 수 있다.
function makeInfiniteEnergyGenerator() {
let energy = 0; // energy 값을 클로저 공간에 가둠.
return function(booster = 0) {
if(booster) {
energy += booster;
} else {
energy++;
}
return energy;
}
}
const energy = makeInfiniteEnergyGenerator();
for(let i = 0; i < 5; i++) {
console.log(energy()); // 1 2 3 4 5
}
console.log(energy(5) // 10
위 코드를 생성기 함수로 만들어보자.
function* InfiniteEnergyGenerator() {
let energy = 1;
while (true) {
const booster = yield energy;
if(booster) {
energy += booster;
} else {
energy++;
}
}
}
const energyGenerator = InfiniteEnergyGenerator();
for(let i = 0; i < 5; i++) {
console.log(energyGenerator.next());
}
console.log(energyGenerator.next(5))
제너레이터 함수는 첫 번째 호출을 하면 일반 함수와 달리 자신을 실행시키지 않는다. 정확히는 실행시키데 필요한 도구를 갖고 있는 객체를 만들어서 넘겨준다. energyGenerator 상수가 그 도구를 담고 있는 객체가 된다. 핵심적인건 next인데 제너레이터 함수 본체 실행을 재개시킬 수 있는 함수이다. yield 키워드는 제너레이터를 멈추게 한다. 멈춘 상태에서 yield로 선언한 값을 호출자(next)에게 energy 값을 객체 형태로 포장하여 반환해준다. 그 다음 next가 실행을 재개하기에 yield 다음 코드인 대입문 코드를 재개하여 booster에 energy 값이 들어온다. 제너레이터 함수에서 return 문을 만나면 done 속성이 true가 된다.
'개발자의 공부 > JS' 카테고리의 다른 글
[JS문법]객체(Object)란? (0) | 2022.09.13 |
---|---|
JS 코드 개선+Optional chaining+null 병합 연산자 (1) | 2022.09.10 |
[JS]LocalStorage와 SessionStorage(내용 업데이트) (0) | 2022.09.05 |
[JS문법]for...of와 for...in (0) | 2022.09.03 |
코드 프로그래머스 문제 풀이4 (0) | 2022.08.31 |