사용자가 로그인이나 로그아웃을 했을 때 안내 메시지를 보여주는 동작처럼 유사한 동작을 하는 코드가 여러 곳에서 필요할 때가 많다.
함수를 이용하면 유사한 동작을 하는 코드를 중복해 작성하지 않고도 여러 번 호출할 수 있다.
함수를 직접 만드는 방법을 알아보자!
함수선언방식(함수선언문) 으로 함수를 만들 수 있다. 다음과 같이 작성한다.
function showMessage() {
alert( '안녕하세요!' );
}
function 키워드, 함수이름, 괄호로 둘러싼 매개변수를 차례로 써주면 함수를 선언할 수 있다. 매개변수가 여러 개 있다면 각각을 콤마로 구분해준다. 이어서 함수 본문을 중괄호로 감싸 준다.
function name(param1, param2) {
...함수 본문...
}
새로 정의된 함수는 이름 옆에 괄호를 붙여 호출할 수 있다.
function showMessage() {
alert( '안녕하세요!' );
}
showMessage();
함수 내에서 선언한 변수인 지역변수는 함수 안에서만 접근할 수 있다.
function showMessage() {
let message = "안녕하세요!"; // 지역 변수
alert( message );
}
showMessage(); // 안녕하세요!
alert( message );
// ReferenceError: message is not defined
message 는 함수 내에서 선언한 지역변수이기 때문에 함수 바깥에서는 접근할 수 없다.
함수 내부에서 함수 외부 변수에 접근할 수 있다.
let userName = 'John';
function showMessage() {
let message = 'Hello, ' + userName;
alert(message);
}
showMessage(); // Hello, John
함수를 이용해 외부 변수를 수정할수도 있다.
let userName = 'John';
function showMessage() {
userName = "Bob"; // (1) 외부 변수를 수정함
let message = 'Hello, ' + userName;
alert(message);
}
alert( userName ); // 함수 호출 전이므로 John 이 출력됨
showMessage();
alert( userName ); // 함수에 의해 Bob 으로 값이 바뀜
외부 변수는 지역 변수가 없는 경우에만 사용할 수 있다.
함수 내부에 외부 변수와 같은 이름의 지역변수가 선언되었다면, 내부 변수가 외부 변수를 가린다. (외부 변수의 값은 수정되지 않는다.)
let userName = 'John';
function showMessage() {
let userName = "Bob"; // 외부 변수와 같은 이름의 지역변수 선언
let message = 'Hello, ' + userName; // Bob
alert(message);
}
alert( userName ); // 외부 변수인 John 출력
showMessage();
alert( userName ); // John 출력. 함수는 외부 변수에 접근하지 않으므로 값이 변경되지 않음
💡 전역 변수매개변수를 이용하면 임의의 데이터를 함수 안에 전달할 수 있다. 매개변수는 인수(parameter) 라고 부르기도 한다.
함수를 호출하면, 함수에 전달된 인자는 지역변수 from 과 text 에 복사된다. 그 후 함수는 지역변수에 복사된 값을 사용한다.
전역변수 from 이 있고, 이 변수를 함수에 전달하는 경우 함수가 from 을 변경하지만, 변경 사항은 외부 변수 from 에 반영되지 않는다. 함수는 언제나 복사된 값을 사용하기 때문이다.
function showMessage(from, text) {
from = '*' + from + '*'; // "from"을 변경한다.
alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// 함수는 복사된 값을 사용하기 때문에 바깥의 "from"은 값이 변경되지 않음
alert( from ); // Ann
매개변수에 값을 전달하지 않으면 그 값은 undefined 가 된다. 위에서 정의함 함수는 매개변수가 2개지만, 인수를 하나만 넣어서 호출할 수도 있다.
function showMessage(from, text) {
alert( from + ": " + text );
}
showMessage("Ann"); // Ann: undefined
매개변수에 값을 전달하지 않아도 undefined 를 출력되지 않게 하려면 기본값을 설정해주어야 한다.
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given
기본값으로는 함수를 넣어줄 수 도 있다.
function showMessage(from, text = anotherFunction()) {
// anotherFunction()은 text값이 없을 때만 호출됨
// anotherFunction()의 반환 값이 text의 값이 됨
}
함수 선언부에서 매개변수 기본값을 설정하기보다 함수 실행 도중 기본값을 설정하는 것이 맞는 경우도 있다.
이러한 경우 매개변수를 undefined 와 비교하여 매개변수 생략 여부를 검사한다.
function showMessage(text) {
if (text === undefined) {
text = '빈 문자열';
}
alert(text);
}
showMessage(); // 빈 문자열
논리 연산자 || 를 사용해도 동일하게 작동한다.
function showMessage(text) {
text = text || '빈 문자열';
...
}
모던 자바스크립트 엔진이 지원하는 nullish 병합 연산자 ?? 를 사용하면 0처럼 falsy 로 평가되는 값들을 일반 값처럼 처리할 수 있다.
// 매개변수 'count'가 null, undefined 이면 'unknown'을 출력해주는 함수
function showCount(count) {
alert(count ?? "unknown");
}
showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown
함수를 호출했을 때 함수를 호출한 그곳에 특정 값을 반환하게 할 수 있다. 이 특정 값을 반환 값이라고 부른다.
반환 값은 지시자 return 을 사용해 표현한다. return 은 함수 내 어디서든 사용할 수 있고, return 을 만나면 함수 실행이 즉시 중단되며 호출한 곳에 값을 반환한다.
함수 하나에 여러 개의 return 문이 올 수도 있다.
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('보호자의 동의를 받으셨나요?');
}
}
let age = prompt('나이를 알려주세요', 18);
if ( checkAge(age) ) {
alert( '접속 허용' );
} else {
alert( '접속 차단' );
}
지시자 return 만 명시하는 것도 가능하며, 이런 경우 함수가 즉시 종료된다.
function showMovie(age) {
if ( !checkAge(age) ) {
return;
}
alert( "영화 상영" ); // (*)
// ...
}
위 예시에서 checkAge()가 false 를 반환하면 함수가 종료되므로 alert() 가 실행되지 않는다.
💡 return 문이 없거나 return 으로 종료되는 함수반환값으로 여러 줄을 작성하고 싶다면 표현식이 return 지시자가 있는 곳에서 시작하도록 해야 한다.
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
)
함수란 어떤 동작을 수행하기 위한 코드를 모아 놓은 것이다. 따라서 함수의 이름은 대개 동사이다.
함수 이름은 가능한 간결하고 명확해야 하고, 함수의 동작을 설명할 수 있어야 한다.
코드를 읽는 사람이 함수 이름만 보고도 함수의 기능을 짐작할 수 있도록 작성해야 한다.
함수의 동작을 축약해서 설명해주는 동사를 접두어로 붙여 함수 이름을 만드는 것이 관습이지만, 팀 내에서 합의된 접두어만 사용해야 한다.
다음은 대체로 사용하는 접두어들이다.
showMessage(..) // 메시지를 보여줌
getAge(..) // 나이를 나타내는 값을 얻고 그 값을 반환함
calcSum(..) // 합계를 계산하고 그 결과를 반환함
createForm(..) // form을 생성하고 만들어진 form을 반환함
checkPermission(..) // 승인 여부를 확인하고 true나 false를 반환함
💡 함수는 동작 하나만 담당해야 한다.함수는 간결하고, 한 가지 기능만 수행할 수 있게 만들어야 한다. 함수가 길어지면 함수를 잘게 쪼갤 때가 되었다는 것으로 받아들여야 한다. 함수를 쪼개는 것은 쉬운 작업이 아니지만, 분리해 작성하면 많은 장점이 있다. 장점으로는 테스트와 디버깅이 쉬워지고, 함수 그 자체로 주석의 역할까지 한다는 점이 있다!
다음의 두 코드를 비교해 보면 어떤 동작을 하는지 알아차리기 쉬운 쪽이 어디인지 명확하게 알 수 있다.
일반적으로 작성한 코드 (showPrimes 함수 내에 소수 판별 기능을 포함)
function showPrimes(n) {
nextPrime: for (let i = 2; i < n; i++) {
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
alert( i ); // 소수
}
}
소수 판별 기능을 분리해서 작성한 코드
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i); // a prime
}
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if ( n % i == 0) return false;
}
return true;
}
이렇게 이름만 보고도 어떤 동작을 하는지 알 수 있는 코드를 자기 설명적 코드라고 부른다.