
자바스크립트(Javascript)에서 함수란
특정 요소가 특정 조건을 만족하는 1급 시민 혹은 1급 객체라고도 하며
쉽게 이해되는 의미로는, 특별한 목적의 작업을 수행되도록 만들어진 블록이라고 할 수 있다.
먼저, 1급 시민의 조건들을 알아보자.
- 변수에 할당할 수 있어야 한다.
- 함수의 인자로 해당 객체(여기선 함수)를 전달할 수 있어야 한다.
- 함수의 반환값으로 전달할 수 있어야 한다.
- 리터럴 방식의 함수 생성이 가능해야 한다.
- 런타임에서 멤버 변수(속성) 및 멤버 함수(메소드)로의 사용이 가능해야 한다.
다음과 1급 시민 조건을 만족하는 객체가 최종적으로 1급 객체가 되는 것이다.
본격적으로 함수의 선언 및 실행 방법에 대해 알아본다.
함수의 선언
함수는 선언문과 표현식으로 나뉜다.
공통되게 사용되는 문법 방식으로는
자바스크립트 데이터로써 동시에 () 기호로 Callable 가능한 객체라는 것이고,
{ } (블록 기호)를 사용하여 그 안에 실행문(명령문)을 선언한다는 것이다.
1. 선언문 : function 키워드로 시작해, 생성한 함수명으로 이루어져 있는 것이다.
function hello() {
console.log('Hello~!');
}2. 표현식 : 함수에 이름을 짓지 않고, 변수에 할당하는 익명 함수로 표현하는 것이다.
※ 만약 익명 함수가 아니라 기명 함수로 할당할 경우, 할당하는 변수명이 함수명이 된다.
const exp = function dec() {....}함수의 실행(호출)
함수를 사용할 때는 단순 함수 데이터인지, 호출한 후 반환되는 데이터가 있는지 파악해야 한다.
return 문을 사용해, 함수를 호출할 때, 함수 안에서 바깥으로 데이터를 내보낼 수 있다.
이를 반환 데이터라고 하며, return 문으로 반환 후에는 코드가 실행되지 않으므로 종료의 의미를 갖기도 한다.
다시 돌아와서 함수의 호출은 함수이름(매개변수) 형태로 호출되며
반환값이 있을 경우, 변수로 할당 받아서 사용할 수 있다. 반환값이 없는 경우에는 void 함수라고도 한다.
※ 참고 : 함수는 재사용할 수 있는 코드 단위이므로, 비슷한 형태의 코드들을 묶어서 재사용할 수도 있다!
function add(a, b) {
return a + b;
}
const a = add(1, 2) // 재사용
const b = add(3, 5) // 재사용
const c = add(6, 9) // 재사용객체 안의 함수 객체 사용하기
객체 안에 함수 객체를 만들어 객체를 통해 메소드를 불러와 실행해줄 수도 있다.
다음과 같은 객체를 만든다.
const eunhaa = {
name: 'eunhaa',
age: 15,
getName: function() {
return this.name;
}
}이 객체의 getName() 메소드를 사용해보자.
getName() 메소드가 어떤 값을 반환하고 있으므로, 변수로 할당받을 수 있다.
※ 참고로 this 키워드에 대한 자세한 내용은 추후 내용에서 살펴볼 것이다.
const hisName = eunhaa.getName();최종적으로 함수라는 데이터를 메소드 형식으로 객체 데이터 안의 데이터로 활용할 수 있는 예시였다.
화살표 함수
화살표 함수를 이용하면, 일반 익명 혹은 기명 함수보다 더 간결하게 함수를 작성할 수 있다.
다음과 같은 표현식으로 된 익명 함수가 있다고 해보자.
const double = function(x) { return x * 2; }
console.log('double data : ', double(7)) // 14동일한 로직을 아래 화살표 함수로 표현해줄 것이다.
기본적으로 화살표 함수는 익명 함수이며, 표현식이다.
const doubleArrow = x => x * 2;
console.log('doubleArrow : ', doubleArrow(7)) // 14위 화살표 함수에서 보면 알겠지만 여러가지 생략 특징이 있다.
- 매개변수가 1개인 경우 매개변수 괄호를 생략할 수 있다.
- 실행문이 return 문 하나일 경우 return 키워드를 생략할 수 있다.
- 실행문이 한 문장일 경우 {} 블록을 생략할 수 있다.
- 반환값이 객체 리터럴 형식일 경우 ()로 추가로 묶어줘야 에러가 나지 않는다.
- this를 가리키는 객체가 기본 함수와 다르다 (추후에 살펴볼 것!!)
즉시 실행 함수 (IIFE)
즉시 실행되는 함수 표현 (Immediately-Invoked Function Expression)은
함수 정의와 동시에 바로 실행하는 문법이다.
함수가 기본적인 내용으로 만들어지고 나서 바로 밑에서 한번 실행되고
더 이상 쓸일이 없다고 전제할 경우, 굳이 함수의 이름을 짓고 아래에서 호출할 필요가 없다.
예시를 살펴보면, 다음과 같은 로직을 함수로 실행했을 때 아래와 같이 작성할 수 있다.
const a = 7;
function double() {
console.log(a * 2)
}
double();이것을 똑같은 로직의 형태로 익명 함수로 바로 실행하고자 한다면 아래와 같이 작성한다.
(function() { console.log(a * 2) })();즉, 즉시실행함수(IIFE)는 기본적으로 (익명함수(표현식))(매개변수) 형태로 구성된다.
또, Callable 할수 있는 매개변수 부분을 소괄호 안으로 집어넣어서 내부 함수처럼 표현할수도 있다.
(function() { console.log(a * 2) }());참고로, 즉시 실행 함수는 앞서 실행된 실행문과의 구분을 위해 ;(세미콜론)으로 구분한다.
또 다른 목적으로는 함수 외부에서 내부 함수 변수에 접근할 수 없도록 한다.
const a = 7; // 전역 변수
(() => {
const b = 3; // 내부 변수
console.log(a, b) // 7, 3
})();
console.log(a, b) // b is not defined (내부 변수인 b에는 접근 불가)함수 호이스팅 (Function Hoisting)
함수 선언부가 유효범위(스코프) 최상단으로 끌어올려지는 현상을 말한다.
함수 호이스팅은 함수가 선언문인지, 표현식인지에 따라 호이스팅이 다르게 작용된다.
먼저 함수 선언식은 호이스팅이 되어
함수 선언문이 스코프의 최상단으로 끌어올려지고, 위에 함수를 호출할 수 있다.
const a = 7;
double(); // 호이스팅이 되므로, 선언문보다 위에 호출할 수 있음.
function double() {
console.log(a * 2)
}반대로, 함수 표현식은 함수 자체가 호이스팅되지 않고
안에 있는 변수만 호이스팅되어 위에 함수를 호출할 수 없다.
const a = 7;
double(); // double is not a function
const double = function () {
console.log(a * 2);
}변수 호이스팅 (Variable Hoisting)
자바스크립트(Javascript)에서 변수는 var, let, const 3가지가 존재한다.
자바스크립트 변수는 호이스팅이 모두 가능하지만
유효 범위(스코프)에 따라 해석되는 것이 다르다고 볼 수 있다.
먼저 각 변수들의 생성 라이프사이클을 분석해볼 필요가 있는데, 크게 var와 let / const로 나뉠 수 있다.
1. var
기본적으로 변수 생성은 선언 - 초기화 - 할당 순으로 이루어지지만
var는 선언과 초기화가 거의 동시에 이루어진다.
실행 시점에 스코프 최상단(전역 혹은 함수)에서 선언과 초기화가 이루어져
메모리가 undefined 상태로 살아있으며
선언문의 위치와 관계없이 참조 및 할당이 가능하다는 것이다.
2. let / const
let 과 const는 선언과 초기화가 동시에 이루어지지 않아, 호이스팅이 이루어지고 실행 시점에는
실질적인 초기화 및 할당까지 메모리가 존재하지 않으므로, 선언부 상단에서 참조 에러가 발생한다.
이것을 TDZ 영향을 받았다고 보기도 한다.
TDZ는 스코프 지입 시점과 초기화 시작 시점의 사이인 일시적 사각 지대라고 보면 되며,
변수는 존재하지만 초기화 및 할당이 되지 않은 구간이다.
콜백 함수
콜백 함수란, 대상 함수의 인수로 사용되는 함수를 말한다.
가장 간단한 예시로, 타이머 함수에 사용되는 setTimeout() 함수가 있다.
setTimeout이 함수 이름이고, 인자로 어떤 함수를 받으며, 또 다른 인수로 시간 변수를 받는데
여기서 인자로 받는 함수를 콜백 함수라고 부른다.
콜백 함수의 원리를 알아보기 위해 간단한 함수를 만들어 보겠다.
function timeout() {
setTimeout(() => { console.log('Eunhaa!') }, 3000)
}
timeout(); // 3초 뒤에 Eunhaa 문자가 콘솔에 출력되는 함수여기서 우리는 timeout() 함수는 건드리지 않은 채
다음 코드를 timeout() 실행되고 나서 실행될 것을 보장해주고 싶다.
여기서 할 수 있는 것이 timeout() 함수의 인수에 콜백 함수(익명 함수)를 받아
그것을 setTimeout() 안에서 실행하는 것이다.
function timeout(callback) {
setTimeout(() => {
console.log('Eunhaa!');
callback();
})
}
timeout(() => { console.log('Done') })즉, 내가 실행을 보장하고 싶은 곳에서 하나의 함수를 실행해줄 수 있는 것이다.
인자(매개변수) 패턴
앞서 말한 인자라는 것은 각 매개변수라고도 하는데, 함수 호출 시 함수 외부에서 내부로 데이터를 전달하는 것이다.
이 때, 전달받은 데이터가 순서대로 할당되어 함수 내부에서 사용할 수 있도록 만드는 것이다.
1. 기본 패턴 : 인자값으로 변수 값을 그대로 받아준다.
function sum(a, b) { return a + b }2. 기본값 패턴 : 기본값이 지정된 매개변수는 undefined 혹은 전달하지 않을 시 대신 기본 값을 전달한다.
function sum(a, b = 1) { return a + b }
console.log(sum(2)) // 2 + 1 = 33. 구조 분해 할당 : 매개변수로 객체나 배열을 저장하는 경우 특정 멤버를 추출할 수 있다.
const user = {
name: 'Eunhaa',
age: 15
}
//
function getName(user) {
return user.name;
}
function getName({ name }) {
return name;
}4. 배열 구조 분해 할당
배열은 객체와 다르게 일치시킬 키 값이 없으므로, 순서에 따라 매개변수를 추출해야 한다.
비교적 간단한 구조나, 초반 배열 아이템을 추출할 때 추천되는 방식이다.
const fruits = ['Apple', 'Banana', 'Cherry', 'Orange', 'Strawberry'];
function getSecond([,b]) { return b; } // 2번째 요소 가져오기
console.log(getSecond(fruits)) // Banana5. 나머지 매개 변수
스프레드 연산자를 사용해 일치하는 순서부터 이후 모든 인수를 매개변수가 배열로 전달받는다.
function sum(a, b, ...rest) {
return rest.reduce((acc, cur) => acc + cur, 0)
}
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8)) // 336. arguments
모든 함수 안에서 사용 가능한 내장 객체로, 매개변수 없이도 함수로 들어오는 모든 인수를 참조한다.
배열이 아닌 유사 배열이므로 배열 메소드를 사용할 수 없으나, Iterable 객체이므로 for문을 사용하여 처리한다.
function sum() {
let res = 0;
for item of arguments {
res += item;
}
return res;
}
const res = sum(1, 2, 3, 4, 5, 6, 7, 8) // 36함수 커링(Function Currying)
커링(Currying)이라는 개념은 여러 개의 인수를 받는 함수에서
인수를 하나씩 받는 함수로 나누어서, 순차적으로 호출하는 기법을 말하며
함수의 재사용성, 가독성, 유연성 등을 확보할 수 있다.
예를 통해 살펴보자.
function myConsole(level, message) {
console[level](message)
}
myConsole('log','my message');
myConsole('warn', 'Danger Ahead!');
myConsole('error', 'Error: System Failure!');위 예제는 두 개의 인수를 받아 처리하는데
level에는 메소드 이름을 받아 사용하고, message에는 그 메소드의 인자를 받아 사용한다.
이 2개의 인수를 받는 함수에서 각 하나의 인수를 받는 함수로 나눠 순차적으로 호출할 수 있다.
function useConsole(level) {
return function(message) { console[level](message) }
}
const log = useConsole('log');
const warn = useConsole('warn');
const error = useConsole('error');
log('Hello~!');
warn('Danger Ahead');
error('Error: System Failure');이 과정을 간단하게 하기 위해 lodash 라이브러리의 curry() 메소드를 이용할 수 있다.
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>function myConsole(level, message) { console[level](message) }
const useConsole = _.curry(myConsole);클래스(Classes)
자바스크립트(Javascript)에서 생성자 함수를 통해 클래스 개념을 이해할 수 있다.
1. 생성자 함수
아래와 같은 속성과 메소드로 구성되어 있는 객체가 있다고 해보자.
참고로 여기서 속성과 메소드를 합쳐서 멤버(Member)라고도 부른다.
const eunhaa = {
firstName: 'eunhaa',
lastName: 'kim'
getFullName: function () {
return `${this.firstName} ${this.lastName}`
}
}우리는 앞에서, getFullName()라는 메소드를 실행해주기 위해 아래와 같이 작성했었다.
console.log(eunhaa.getFullName()); // Eunhaa Kim하지만 여러 이름들을 출력해주기 위해서는 일일히 객체 데이터를 만들어줘야 했었는데
이것은 각 객체마다 메모리에 따로따로 할당되므로, 가독성 및 유지보수성이 떨어지는 코드이다
즉, 구조가 비슷한 객체 데이터의 경우에는 메모리 효율을 위해 사용하는 것이 클래스 개념이다.
아래와 같이 전체 로직을 수정해보자.
function User(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}새로운 함수를 만드는데, 이것은 나중에 생성자 함수가 될 것이다.
생성자 함수는 새로운 객체를 만들어, 그 객체에 멤버(속성 혹은 메소드)를 새로 할당할 수 있다.
내부에서는 객체 리터럴 방식으로 객체를 생성한다고 보면 되겠다.
const eunhaa = new User('eunhaa','kim');
console.log(eunhaa) // {'firstName': 'eunhaa', 'lastName': 'kim'}추가로, 여기서 생성자 함수로 생성된 활성화된 객체를 **인스턴스(Instance)**라고도 부른다.
그리고 다른 방식으로 생성자 함수에서 prototype 속성을 통해 메소드를 할당할 수도 있다.
prototype 객체는 딱 하나의 통일된 메모리를 관리하여 공통된 메소드 혹은 속성들을 관리할 수 있는 것이다.
user.prototype.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
}2. Javascript Classes Pattern
class 키워드를 통해 하나의 클래스를 만들 수 있으며,
기본적으로 constructor() 안에 생성자 함수가 들어가며
나머지 메소드들은 prototype 객체 안의 메소드들로 공통 관리되어 참조하여 사용한다.
class User {
constructor(first, last) {
this.firstName = first;
this.lastName = last;
}
getFullName() {
return `${this.firstName} ${this.lastName}`
}
}따라서, function() 키워드로 생성자 함수와 prototype 키워드로 공통된 메소드를 만드는 것 보다
classes pattern을 통해 클래스 내부에서 한꺼번에 관리하는 것이 메모리 절약 및 가독성에 도움이 된다.
Reference
JS 함수 핵심 패턴자바스크립트 함수에 대한 기본적인 사용 방법부터 관련 용어, 고급 기법 등의 관련된 다양한 함수 패턴과 여러 개념을 살펴봅니다.www.heropy.dev