[JS] 3. 함수

Shy·2023년 9월 10일
0

NodeJS(Express&Next.js)

목록 보기
28/39

선언과 표현 그리고 호이스팅

함수 선언 (Function Declaration)

함수 선언은 가장 일반적인 방법으로 함수를 정의하는 방법이다.

function functionName(parameters) {
    // 함수 본문
}
function add(x, y) {
    return x + y;
}

함수 표현 (Function Expression)

함수 표현은 함수를 변수에 할당하는 방법이다.

const functionName = function(parameters) {
    // 함수 본문
};
const add = function(x, y) {
    return x + y;
};

참고: 화살표 함수 (Arrow Function)도 함수 표현의 한 형태이다.

const add = (x, y) => x + y;

호이스팅 (Hoisting):

호이스팅은 자바스크립트에서 변수 및 함수 선언을 상단으로 끌어올리는 (hoist) 동작을 말한다. 즉, 변수나 함수를 선언하기 전에 참조하거나 호출해도 에러가 발생하지 않게 하는 것이다.

함수 선언의 호이스팅

함수 선언은 호이스팅되어서 함수의 선언 이전에 호출할 수 있다.

hello(); // "Hello World!"가 출력됩니다.

function hello() {
    console.log("Hello World!");
}

함수 표현의 호이스팅

함수 표현은 변수의 호이스팅 규칙을 따른다. 따라서 변수는 호이스팅되지만, 할당은 호이스팅되지 않는다. 그러므로 함수 표현식의 경우 호이스팅 때문에 에러가 발생할 수 있다.

hello(); // TypeError: hello is not a function

const hello = function() {
    console.log("Hello World!");
};

이 예에서는 hello 변수는 호이스팅 되었지만, 함수를 참조하고 있지 않기 때문에 TypeError가 발생한다.

결론: 호이스팅은 자바스크립트에서 종종 혼란을 일으키는 주제이다. 따라서 가능한 변수와 함수는 사용하기 전에 선언하는 것이 좋다.

반환 및 종료

함수는 코드의 재사용성을 높이고 구조화된 방식으로 작업을 수행하기 위해 사용된다. 함수 내에서 특정 값이나 객체를 반환하거나 함수의 실행을 종료하기 위한 방법에 대해 알아보겠다.

함수의 반환 (Return)

return 문은 함수를 호출한 곳으로 값을 반환하거나 함수의 실행을 종료하기 위해 사용된다.

function functionName(parameters) {
    // ... 함수 본문 ...
    return returnValue;
}
function add(x, y) {
    return x + y;
}

const result = add(5, 3);  // result는 8이 됩니다.

특징

return 문 다음의 코드는 실행되지 않는다.
return 문 없이 함수가 종료되면, 그 함수는 undefined를 반환한다.

함수의 종료

함수 내에서 return 문을 사용하면 해당 위치에서 함수의 실행이 종료된다. 이를 통해 특정 조건에서 함수를 조기에 종료할 수 있다.

function isAdult(age) {
    if (age < 0) {
        return "Invalid age!";
    }
    if (age < 18) {
        return "Not an adult!";
    }
    return "Is an adult!";
}

위의 예에서, 첫 번째 if 문에서 return 문이 실행되면 함수는 "Invalid age!"를 반환하고 즉시 종료된다. 두 번째 if 문도 마찬가지로 동작하며, 조건이 모두 거짓인 경우 마지막 return 문이 실행된다.

요약

  • 함수에서 return 문을 사용하면 특정 값을 반환하거나 조건에 따라 함수의 실행을 조기 종료할 수 있다.
  • return 문 없이 함수를 종료하면 undefined가 반환된다.
  • return 문은 함수 내에서 특정 로직을 수행한 후 해당 위치에서 함수의 실행을 즉시 종료시킨다.

매개변수 패턴

함수의 매개변수는 함수가 작업을 수행하기 위해 필요한 정보를 받는 수단이다. 자바스크립트는 유연한 언어 특성 때문에 다양한 매개변수 패턴을 지원한다. 이러한 패턴에는 기본값, 나머지 매개변수, 구조 분해 등이 포함됩니다. 여기에 대해 자세히 살펴보겠다.

기본값(Default Parameters)

함수의 매개변수에 기본값을 제공할 수 있습니다. 이를 통해 해당 매개변수에 값이 전달되지 않았을 때 기본값을 사용할 수 있다.

function greet(name = "Guest") {
    console.log(`Hello, ${name}!`);
}

greet();          // "Hello, Guest!"
greet("John");    // "Hello, John!"

나머지 매개변수(Rest Parameters)

매개변수 이름 앞에 ...을 사용하여 주어진 개수보다 더 많은 인수를 배열로 받을 수 있다.

function addNumbers(...numbers) {
    return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(addNumbers(1, 2, 3, 4));  // 10

구조 분해 매개변수(Destructuring Parameters)

객체나 배열의 값을 개별 변수로 추출할 수 있다.

객체 구조 분해

function displayInfo({ name, age }) {
    console.log(`Name: ${name}, Age: ${age}`);
}

const person = {
    name: "Alice",
    age: 30
};

displayInfo(person);  // "Name: Alice, Age: 30"

배열 구조 분해

function displayNames([first, second]) {
    console.log(`First: ${first}, Second: ${second}`);
}

const names = ["Alice", "Bob"];

displayNames(names);  // "First: Alice, Second: Bob"

매개변수의 인수 전달 방식

자바스크립트는 매개변수를 값에 의한 전달(pass-by-value) 방식으로 처리한다. 그렇기 때문에 원시 데이터 타입(문자열, 숫자, 불리언 등)을 매개변수로 전달할 때는 해당 값을 복사하여 함수에 전달한다.
반면 객체, 배열과 같은 참조 타입의 경우, 참조(주소)를 전달하므로 함수 내에서 객체나 배열의 내용을 변경하면 원본 객체나 배열에도 영향을 미친다.

이러한 매개변수 패턴을 통해, 함수는 다양한 시나리오에 유연하게 대응하면서도 코드의 가독성과 효율성을 유지할 수 있다.

화살표 함수

화살표 함수(arrow function)는 ES6 (ES2015)에서 도입된 새로운 함수 문법이다. 화살표 함수는 전통적인 함수 표현식보다 간결하게 함수를 작성할 수 있으며, this 바인딩 방식에 있어서도 특별한 차이점이 있다.

기본 문법

const sum = (a, b) => a + b;
console.log(sum(1, 2));  // 3

위의 예에서 sum은 두 개의 인수를 받아 그 합계를 반환하는 화살표 함수이다.

블록 본문과 표현식 본문

  • 표현식 본문: 중괄호 {} 없이 작성하며, 표현식의 결과가 자동으로 반환된다.
const square = x => x * x;
  • 블록 본문: 중괄호 {}로 본문을 감싸면 여러 행의 코드를 포함할 수 있다. 이 경우, 값을 반환하려면 return문을 사용해야 한다.
const larger = (a, b) => {
    if (a > b) {
        return a;
    }
    return b;
};

this 바인딩

화살표 함수의 가장 큰 특징 중 하나는 함수 자체의 this 바인딩을 갖지 않는다는 것이다. 대신, 화살표 함수는 선언될 때의 주변 문맥(lexical context)의 this 값을 상속받는다. 이 특징은 특히 이벤트 핸들러나 콜백 함수에서 유용하게 활용된다.

function Timer() {
    this.seconds = 0;
    setInterval(() => {
        this.seconds++;
        console.log(this.seconds);
    }, 1000);
}

const timer = new Timer();

위의 예에서 setInterval 내의 화살표 함수는 Timer 함수의 this를 상속받기 때문에, this.seconds는 timer 객체의 seconds 프로퍼티를 참조한다.

화살표 함수와 일반 함수의 차이점

  • 화살표 함수는 this, arguments, super나 new.target과 같은 특별한 키워드를 바인딩하지 않는다.
  • 화살표 함수는 new 연산자로 인스턴스를 생성할 수 없습니다. 따라서 화살표 함수는 생성자 함수로 사용될 수 없다.
  • 화살표 함수는 yield 키워드를 포함할 수 없기 때문에 제너레이터 함수로 만들 수 없다.

화살표 함수는 특히 간결한 함수 표현이 필요하거나 특정 문맥에서의 this 바인딩 문제를 해결하고자 할 때 유용하게 사용된다.

즉시실행함수(IIFE)

즉시 실행 함수 표현식(Immediately Invoked Function Expression, IIFE)은 함수를 정의하자마자 바로 실행하는 JavaScript 함수 패턴이다. IIFE는 주로 다음 두 가지 목적으로 사용된다.

  1. 변수의 범위: IIFE는 새로운 변수 범위를 생성하므로, 그 안에서 정의된 변수들은 외부의 다른 코드와 충돌하지 않는다. 이를 통해 글로벌 범위의 변수 오염을 방지할 수 있다.

  2. 데이터 은닉: IIFE 내부의 변수나 함수는 외부에서 접근할 수 없으므로, 모듈 패턴을 만들 때 종종 사용된다.

IIFE의 기본 형태

(function() {
    // 코드...
})();

또는

(function() {
    // 코드...
}());

여기서 주목할 점은 함수를 괄호 ()로 감싸고, 마지막에 다시 ()를 사용하여 함수를 호출한다는 것이다.

(function() {
    var privateVar = "I am a private variable!";
    console.log(privateVar);
})();
// "I am a private variable!" 출력

// 아래 코드는 에러를 발생시킵니다. 
// 왜냐하면 privateVar는 IIFE의 범위 내에서만 정의되어 있기 때문입니다.
// console.log(privateVar);  

IIFE는 종종 변수나 설정들을 숨기기 위해 사용된다. 이는 라이브러리나 프레임워크 코드에서 흔히 볼 수 있는 패턴 중 하나이다.

콜백

콜백(callback)은 함수를 다른 함수의 매개변수로 전달하고, 어떤 이벤트가 발생하거나 특정 조건이 만족될 때 해당 함수를 실행하기 위한 메커니즘이다. JavaScript는 함수를 일급 객체로 취급하기 때문에, 다른 객체처럼 변수에 할당하거나 다른 함수의 인자로 전달하거나 반환할 수 있다.

콜백의 사용 사례

  1. 비동기 작업 처리: JavaScript에서 가장 흔한 콜백 사용 사례는 비동기 코드에서의 처리이다. 예를 들어, 네트워크 요청을 하거나 파일을 읽는 작업, 타이머에서의 대기 등이 있다.
setTimeout(function() {
    console.log("1 second has passed!");
}, 1000);
  1. 이벤트 리스너: 웹 페이지에서 사용자의 상호작용(버튼 클릭, 키보드 입력 등)을 처리할 때 사용한다.
document.getElementById("myButton").addEventListener("click", function() {
    console.log("Button clicked!");
});
  1. 배열의 고차 함수: 배열의 map, filter, reduce 등의 메서드는 콜백 함수를 인자로 받아 각 요소에 대해 해당 콜백 함수를 실행한다.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(function(num) {
    return num * 2;
});
console.log(doubled); // [2, 4, 6, 8]

콜백과 관련된 문제점

  1. 콜백 지옥(Callback Hell): 비동기 작업이 연속적으로 이루어져야 할 때 콜백 내에 콜백이 중첩되는 문제가 발생할 수 있다. 이로 인해 코드의 가독성이 저하되고 유지보수가 어려워질 수 있다.
asyncFunction1(function(result1) {
    asyncFunction2(function(result2) {
        asyncFunction3(function(result3) {
            // ... 
        });
    });
});
  1. 에러 처리: 중첩된 콜백 내부에서 발생한 에러를 외부로 전달하거나 관리하기가 어렵다.

이러한 문제점들로 인해 현대의 JavaScript 환경에서는 프로미스(Promise), async/await 등의 방식을 사용하여 비동기 처리를 더 간결하고 관리하기 쉽게 코딩하는 추세이다.

재귀

재귀는 함수가 자기 자신을 다시 호출하는 것을 의미한다. 재귀 함수는 주어진 문제를 동일한 유형의 더 작은 문제로 나누어 해결하는 알고리즘 구현에 자주 사용된다. 재귀는 매우 강력한 프로그래밍 기법이지만, 잘못 사용하면 스택 오버플로우와 같은 문제가 발생할 수 있기 때문에 주의가 필요하다.

재귀의 기본 구성 요소

  1. 베이스 케이스(Base Case): 재귀 함수는 종료 조건이 필요하다. 이를 베이스 케이스라고 한다. 베이스 케이스는 재귀 호출을 멈추게 하는 조건을 정의한다.

  2. 재귀적 호출(Recursive Call): 함수는 문제의 일부분을 해결한 다음, 자신을 다시 호출하면서 문제의 나머지 부분을 해결하려고 시도한다.

예제: 팩토리얼 계산

팩토리얼은 주어진 숫자 n에 대해 1부터 n까지의 모든 정수를 곱하는 것을 의미한다.

function factorial(n) {
    // 베이스 케이스
    if (n <= 1) {
        return 1;
    }
    // 재귀적 호출
    return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

재귀의 주의사항

  1. 무한 루프: 재귀 함수의 베이스 케이스가 올바르게 정의되지 않거나, 잘못 사용될 경우 함수가 무한히 호출될 수 있다.

  2. 성능 문제: 재귀는 종종 반복문보다 메모리를 더 많이 사용하므로, 성능 문제가 발생할 수 있다.

  3. 스택 오버플로우: JavaScript의 호출 스택 크기는 제한적이다. 깊은 재귀 호출은 스택 오버플로우를 일으킬 수 있다.

사용 사례

  1. 트리나 그래프 탐색: 재귀는 트리나 그래프와 같은 데이터 구조를 탐색하는 데 유용하다.

  2. 문제 분할: 복잡한 문제를 더 작은 문제로 분할하여 해결하는 데 사용된다 (예: 병합 정렬, 퀵 정렬).

  3. 수학적 연산: 피보나치 수열, 트리의 높이 계산 등 많은 수학적 연산은 재귀를 사용하여 간단히 구현할 수 있다.

재귀는 강력하고 유용한 기법이지만, 사용할 때 주의가 필요하다. 특히 베이스 케이스를 명확하게 정의하고, 필요한 경우에만 사용하는 것이 좋다.

호출 스케줄링

호출 스케줄링은 JavaScript에서 함수 실행을 특정 시간 뒤에 예약하거나 정기적인 간격으로 반복하여 실행하는 것을 의미한다. JavaScript에서는 setTimeout과 setInterval이라는 두 가지 주요 메서드를 제공하여 호출 스케줄링을 지원한다.

  1. setTimeout
    • setTimeout 함수는 지정된 시간(밀리초 단위)이 경과한 후에 특정 함수를 한 번만 실행하도록 예약한다.
    • setTimeout은 두 개의 인자를 받는다: 실행할 함수와 지연 시간(밀리초).
setTimeout(() => {
  console.log("2초 후에 실행됩니다!");
}, 2000);
  1. setInterval
    • setInterval 함수는 지정된 간격(밀리초 단위)마다 특정 함수를 반복해서 실행하도록 예약한다.
    • 마찬가지로 두 개의 인자를 받는다: 실행할 함수와 간격(밀리초).
setInterval(() => {
  console.log("매 3초마다 실행됩니다!");
}, 3000);

또한 이러한 메서드들은 예약된 호출을 취소하기 위해 사용할 수 있는 고유한 ID를 반환한다.

  • clearTimeout: setTimeout으로 예약된 호출을 취소한다.
  • clearInterval: setInterval로 예약된 반복 호출을 취소한다.
let timerId = setTimeout(() => console.log("이것은 실행되지 않습니다."), 1000);
clearTimeout(timerId);

주의사항

  • JavaScript의 이벤트 루프와 관련하여 setTimeout과 setInterval의 지연 시간은 보장되지 않는다. 지연 시간은 최소 지연 시간을 의미하며, 이벤트 루프가 바쁠 경우 실제 실행 시간이 지연될 수 있다.
  • 짧은 간격으로 setInterval을 사용할 때, 다음 호출이 시작되기 전에 이전 호출이 완료되었는지 항상 확인하는 것이 좋다. 필요한 경우 setTimeout을 사용하여 재귀적으로 호출 스케줄링을 관리할 수도 있다.

호출 스케줄링은 웹 애니메이션, 지연 실행, 정기적인 업데이트 또는 특정 이벤트 후에 특정 동작을 수행할 때 유용하다.

this

JavaScript의 this 키워드는 매우 중요한 개념이다. 그러나 다른 언어의 this와는 달리, JavaScript의 this는 다양한 문맥에서 다른 값을 가리키기 때문에 혼동을 일으킬 수 있다. 여기에는 그 주요 사용 사례와 규칙이 있다.

전역 문맥

  • 함수 외부에서, this는 전역 객체를 참조한다.
  • 브라우저에서는 this는 window 객체를 참조한다.
  • Node.js 환경에서는 this는 global 객체를 참조한다.
console.log(this);  // Window {...}

함수 내부

일반 함수에서의 this는 기본적으로 전역 객체를 참조한다. 그러나 엄격 모드(strict mode)에서는 undefined이다.

function test() {
    console.log(this);
}
test();  // Window {...} (non-strict mode)
         // undefined (strict mode)

메서드 내에서

객체의 메서드로서 함수가 호출되면, this는 해당 메서드를 호출한 객체를 참조한다.

const obj = {
    name: 'ChatGPT',
    describe: function() {
        console.log(this.name);
    }
};
obj.describe();  // ChatGPT

생성자에서

new 키워드를 사용하여 함수를 생성자로 호출할 때, this는 새로 생성된 객체를 참조한다.

function Person(name) {
    this.name = name;
}
const person = new Person('ChatGPT');
console.log(person.name);  // ChatGPT

이벤트 리스너에서

DOM 이벤트 리스너에서 this는 이벤트를 발생시킨 요소를 참조한다.

button.addEventListener('click', function() {
    console.log(this);  // <button> element
});

명시적 바인딩

call, apply, bind 메서드를 사용하여 함수 또는 메서드의 this 값을 명시적으로 설정할 수 있다.

function describe() {
    console.log(this.name);
}
const obj = { name: 'ChatGPT' };
describe.call(obj);  // ChatGPT

화살표 함수

화살표 함수는 자체 this 바인딩을 갖지 않는다. 대신, 화살표 함수는 자신을 둘러싸고 있는 스코프의 this 값을 상속한다.

const obj = {
    name: 'ChatGPT',
    describe: function() {
        const arrowFunction = () => {
            console.log(this.name);
        };
        arrowFunction();
    }
};
obj.describe();  // ChatGPT

이러한 규칙과 사용 사례들을 이해하는 것은 JavaScript 개발에 있어 핵심적이다. this는 때로는 예상치 못한 방식으로 동작할 수 있기 때문에, 사용하는 문맥을 항상 고려하면서 코드를 작성하는 것이 중요하다.

profile
초보개발자. 백엔드 지망. 2024년 9월 취업 예정

0개의 댓글