DAY - 08

공부 저장소·2021년 4월 27일
post-thumbnail

함수 표현식 (2)

콜백 함수

앞선 DAY - 07 함수 표현식 (1) 게시글에서 하였던 것처럼 함수를 값처럼 전달하여 사용하는 예시인 함수 표현식을 더 살펴보겠다.

매개변수가 3개 있는 함수ask(question, yes, no)를 작성해보겠다.
각 매개변수는 아래와 같은 의미를 가지며 예시를 나타낸다.
question - 질문
yes - "yes"라고 답한 경우에 실행되는 함수
no - "no"라고 답한 경우에 실행되는 함수

function ask(question, yes, no) {//(1)
  if (confirm(question) yes()//(2)
  else no();//(3)
}

function showOk() {
  alert( "동의하셨습니다." );
}

function showCancel() {
  alert( "취소 버튼을 누르셨습니다." );
}


ask("동의하십니까?", showOk, showCancel);//(4)

괄호 안의 숫자로 표시한 라인에서 내가 이해한 것을 설명하자면 이러하다.

(1) 아래 ask함수에 들어가있는 인수가 차례로 들어간다.
      즉, question엔"동의하십니까?"기 yes엔 showOK가 no엔 showCancel을 받는다.
(2) 확인을 눌러 true 값을 받는 다면 yes() 즉 showOk()를 실행하고
(3) 취소를 눌러 false값을 받는다면 no() 즉 showCancel()를 실행한다.
(4) ask를 실행하면 함수 showOk와 showCancel가 ask 함수의 인수로 전달됨

여기서 함수 ask의 인수, showOKshowCancel콜백 함수 또는 콜백 이라고 불린다.

함수를 함수의 인수로 전달하고 필요하다면 인수로 전달한 그 함수를 나중에 호출(called back)하는 것이 콜백 함수의 기본 개념이다.

간단히 요약하면 위 예시에선 사용자가 "yes"로 대답한 경우에는 showOK가 콜백이 되고 "no"라고 대답한 경우 showCancel이 콜백된다.

위 예시를 더 짧게 줄이면

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "동의하십니까?",
  function() { alert("동의하셨습니다."); },
  function() { alert("취소 버튼을 누르셨습니다."); }
);

이러한 형태로 함수 실행문인 ask(...) 안에 콜백할 함수를 이름 없이 선언하는데 이와 같은 이름 없는 함수는 익명 함수(annonymous function)이라고 부른다. 익명 함수는 변수에 할당된 게 아니기 때문에 ask 바깥에선 접근할 수 없다.

처음에 이해가 잘 가지 않았지만 형태나 기능이 앞으로 자주 쓰이게 될 것 같으니 여러 연습을 해보아야겠다.

함수는 동작을 나타내는 값이다.

문자열이나 숫자 등의 일반적인 값들은 데이터를 나타낸다.
함수는 하나의 동작을 나타낸다.
동작을 대변하는 값인 함수를 변수 간 전달하고 동작이 필요할 때 이 값을 실행할 수 있다.

함수 표현식 VS 함수 선언문

함수 표현식과 선언의 차이에 대해 알아보자.

첫 번째 차이점 은 우선 문법에서 함수 생성 시에 함수의 위치 이다. 간단한 예시를 통해 차이를 알아보자.
  • 함수 선언문 : 주요 코드 흐름 중간에 독자적인 구문 형태로 존재한다.
// 함수 선언문
function sum(a, b) {
  return a + b;
}
  • 함수 표현식 : 표현식이나 구문 구성(Syntax contruct) 내부에 생성된다. 아래 예시에서는 함수가 할당 연산자 =를 이용해 만든 "할당 표현식" 우측에 생성됐다.
// 함수 표현식
let sum = function(a, b) {
  return a + b;
};
두 번째 차이점 은 자바스크립트 엔진이 언제 함수를 생성하는지에 있다.

함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성한다. 따라서 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있다.

위 예시를 이용해 설명해 보도록 하겠다. 스크립트가 실행되고, 실행 흐름이 let sum = function…의 우측(함수 표현식)에 도달 했을때 함수가 생성된다. 이때 이후부터 해당 함수를 사용(할당, 호출 등)할 수 있다.

하지만 함수 선언문은 조금 다르다.

함수 선언문은 함수 선언문이 정의되기 전에도 호출할 수 있다.

따라서 전역 함수 선언문은 스크립트 어디에 있느냐에 상관없이 어디에서든 사용할 수 있다.

이게 가능한 이유는 자바스크립트의 내부 알고리즘 때문이다. 자바스크립트는 스크립트를 실행하기 전, 준비단계에서 전역에 선언된 함수 선언문을 찾고, 해당 함수를 생성한다. 스크립트가 진짜 실행되기 전 "초기화 단계"에서 함수 선언 방식으로 정의한 함수가 생성되는 것이다.

스크립트는 함수 선언문이 모두 처리된 이후에서야 실행된다. 따라서 스크립트 어디서든 함수 선언문으로 선언한 함수에 접근할 수 있는 것이다.

예시를 살펴봅시다.

sayHi("John"); // Hello, John

function sayHi(name) {
  alert( `Hello, ${name}` );
}

함수 선언문, sayHi는 스크립트 실행 준비 단계에서 생성되기 때문에, 스크립트 내 어디에서든 접근할 수 있습니다.

그러나 함수 표현식으로 정의한 함수는 함수가 선언되기 전에 접근하는 게 불가능합니다.

sayHi("John"); // error!

let sayHi = function(name) {  // (*) 마술은 일어나지 않습니다.
  alert( `Hello, ${name}` );
};

함수 표현식은 실행 흐름이 표현식에 다다랐을 때 만들어집니다. 위 예시에선 (*)로 표시한 줄에 실행 흐름이 도달했을 때 함수가 만들어집니다. 아주 늦죠.

즉, 나의 생각으로 요약을 하자면 선언문 형식의 스크립트문은 스크립트 내에서 선언문을 모두 읽어서 생성하고 나서야 스크립트를 읽는 것이고 표현식 형식의 스크립트문은 함수 실행 부분에서 함수가 필요할 때에 자신보다 앞선 라인에서 함수를 읽어서 생성하고 실행하는 형식이다.

때문에 표현식 형식은 실행문이 표현식보다 뒤에 위치해야 한다.

세 번째 차이점 은, 스코프입니다.

엄격 모드에서 함수 선언문이 코드 블록 내에 위치하면 해당 함수는 블록 내 어디서든 접근할 수 있다. 하지만 블록 밖에서는 함수에 접근하지 못한다.

예시를 들어 설명해 보겠다. 런타임에 그 값을 알 수 있는 변수 age가 있고, 이 변수의 값에 따라 함수 welcome()을 다르게 정의해야 하는 상황이다. 그리고 함수 welcome()은 나중에 사용해야 하는 상황이라고 가정해 보겠다.

함수 선언문을 사용하면 의도한 대로 코드가 동작하지 않는다.

let age = prompt("나이를 알려주세요.", 18);

// 조건에 따라 함수를 선언함
if (age < 18) {

  function welcome() {
    alert("안녕!");
  }

} else {

  function welcome() {
    alert("안녕하세요!");
  }

}

// 함수를 나중에 호출합니다.
welcome(); // Error: welcome is not defined

함수 선언문은 함수가 선언된 코드 블록(예시에서는 if문) 안에서만 유효한데 if문 바깥에는 유효하지 못하기에 이런 에러가 발생한다.

또 다른 예시를 살펴보자.

let age = 16; // 16을 저장했다 가정합시다.

if (age < 18) {
  welcome();               // \   (실행)
                           //  |
  function welcome() {     //  |
    alert("안녕!");        //  |  함수 선언문은 함수가 선언된 블록 내
  }                        //  |  어디에서든 유효합니다
                           //  |
  welcome();               // /   (실행)

} else {

  function welcome() {
    alert("안녕하세요!");
  }
}

// 여기는 중괄호 밖이기 때문에
// 중괄호 안에서 선언한 함수 선언문은 호출할 수 없습니다.

welcome(); // Error: welcome is not defined

그럼 if문 밖에서 welcome 함수를 호출할 방법은 없는 걸까?

이럴 때 함수 표현식을 사용하면 가능하다. if문 밖에 선언한 변수 welcome에 함수 표현식으로 만든 함수를 할당하면 된다.

이제 코드가 의도한 대로 동작한다.

let age = prompt("나이를 알려주세요.", 18);

let welcome;

if (age < 18) {

  welcome = function() {
    alert("안녕!");
  };

} else {

  welcome = function() {
    alert("안녕하세요!");
  };

}

welcome(); // 제대로 동작합니다.

표현식으로 welcome이라는 객체에 함수를 할당하여 사용하면 if문 바깥에서도 정상적으로 사용이 된다.

물음표 연산자 ?를 사용하면 위 코드를 좀 더 단순화할 수 있다.

let age = prompt("나이를 알려주세요.", 18);

let welcome = (age < 18) ?
  function() { alert("안녕!"); } :
  function() { alert("안녕하세요!"); };

welcome(); // 제대로 동작합니다.

이렇게 마지막 예시처럼 때에 따라서 표현식과 선언문 알맞은 생성식을 사용하고 간결화 할 수 있는 능력까지 갖춰야겠다.

한 방식만 옳은 것일까?

이렇게 본다면 표현식이 당연히 더 범위가 넓어 보일 수도 있다. 하지만 정답은 없다.
출처의 원작자 분이 남긴 글은 아래와 같다.

함수 선언문을 이용해 함수를 선언하는 걸 먼저 고려하는 게 좋습니다. 함수 선언문으로 함수를 정의하면, 함수가 선언되기 전에 호출할 수 있어서 코드 구성을 좀 더 자유롭게 할 수 있습니다.
함수 선언문을 사용하면 가독성도 좋아집니다. 코드에서 let f = function(…) {…}보다 function f(…) {…} 을 찾는 게 더 쉽죠. 함수 선언 방식이 더 “눈길을 사로잡습니다”.
그러나 어떤 이유로 함수 선언 방식이 적합하지 않거나, (위 예제와 같이) 조건에 따라 함수를 선언해야 한다면 함수 표현식을 사용해야 합니다.




출처

내용 : https://ko.javascript.info/intro
썸네일 제작 : https://www.canva.com/

profile
https://github.com/WonseoYang

0개의 댓글