본문 바로가기
기타 대외활동/투두몰 서포터즈

[구글 앱스 스크립트] 3주차 세 번째 : 자바스크립트 함수

by bri9htstar 2023. 7. 16.

함수 선언과 호출

함수란 일련의 처리를 모아둔 것이며, 다음 방법으로 정의할 수 있다.

 

  • function문을 이용한 함수 선언
  • 함수 리터럴
  • 화살표(arrow) 함수

천천히 알아보자.

 

function문을 이용한 함수 선언

이제까지 예제에서 해왔던 함수 선언 방법이다.

 

function 함수명() {
  // 처리
}

 

두 개 이상의 함수를 스크립트 편집기에서 어떻게 관리할까?

 

꼭 저장을 해야한다!

 

두 개의 함수를 적고 저장하면, 이렇게 스크립트 편집기 툴바에서 각 함수명을 선택할 수 있다.

이렇게 gs 파일에서는 여러 함수를 정의할 수도 있고, 선택해서 실행할 수도 있고, 다른 함수에서 호출할 수도 있다.

 

function sayHello() {
  console.log('Hello!');
}

function sayGoodBye() {
  sayHello(); // 함수 sayHello를 호출 
  console.log('Good bye.');
}

 

다음 코드를 적고 sayGoodBye를 실행하면 로그에는 Good Bye. 전에 Hello! 가 출력된다.

같은 프로젝트 안에서는 다른 gs 파일에 입력한 함수도 호출할 수 있으니 기억해두자.

 

스크립트 파일에 여러 함수를 선언하면 스크립트 편집기의 드롭다운에 여러 함수가 표시되어 선택하기 번거로워진다. 드롭다운에 표시하지 않을 함수에는 함수명 마지막에 _(언더스코어)를 붙여 프라이빗 함수로 만든다.

 

function 함수명_() {
  // 처리
}

 

프라이빗 함수는 프로젝트 안에서는 계속 호출할 수 있지만 프로젝트 바깥에서는 보이지 않으며 호출할 수 없다. 스크립트 편집기의 드롭다운 메뉴에도 나타나지 않는다.

 

sayGoodBye를 프라이빗 함수 처리하자 보이지 않는다. 어디갔어 이거!

 

인수와 반환값도 다른 프로그래밍 언어처럼 있다. 호출한 함수에 전달하는 값을 인수, 호출한 함수에서 받는 값을 반환값이라고 부른다. 다음과 같이 선언한다.

 

function 함수명(파라미터 1, 파라미터 2, …) {
  // 처리
  return 반환값;
}

 

함수가 호출될 때 전달하는 값을 저장하는 변수를 파라미터라 부르며 함수명 뒤에 소괄호에 지정한다. let 등 키워드는 필요하지 않다. 인수가 여럿일 때는 콤마로 구분해서 작성한다. 반환값은 return문으로 작성한다. return문 이후의 처리는 실행되지 않으므로 주의하자.

 

function myFunction() {
  console.log('사각형의 넓이: ${calArea_(3, 4)}'); // 사각형의 넓이 : 12
}

function calArea_(x, y) {
  return x * y;
}

 

인수 3, 4를 전달하여 파라미터에 저장되고 calcArea_(3, 4)를 로그 출력하는 일련의 과정을 이해하자.

return문을 생략하거나 return문의 반환값을 지정하지 않으면 undefined가 반환값이 된다.

 

자바스크립트에서는 함수에 전달하는 인수의 수와 이를 받는 파라미터의 수가 반드시 일치하지 않아도 된다. 인수 수가 파라미터 수보다 많을 때는 나머지 인수는 사용되지 않고 버려진다. 한편 파라미터 수가 인수 수보다 많을 때는 남은 파라미터는 undefined가 된다. 파라미터에 인수가 주어지지 않았을 때 undefined가 아닌 미리 지정한 값을 갖도록 할 수도 있다. 이를 기본 인수라 부르며 파라미터를 다음과 같이 기술한다.

 

function 함수명(…, 파라미터 = 값, …) {
  // 처리
}

 

파라미터에 대응하는 인수가 주어지지 않았을 때 파라미터는 등호 뒤에 입력한 값을 갖게 된다.

 

function myFunction() {
  logMessage_('bri9htstar', 'Good morning'); // Good morning, bri9htstar.
  logMessage_('todomall'); // Hello, todomall.
}

function logMessage_(name, msg = 'Hello') {
  console.log('${msg}, ${name}.');
}

 

함수에 임의 수의 인수를 전달하고 싶을 때는 어떻게 하면 될까? 인수을 배열로 전달하는 방법이 있지만 다른 방법으로 나머지 인수가 있다. 나머지 인수는 전달한 인수 중 나머지 인수를 준비된 배열의 요소로서 전달하는 기법이며 다음 구문과 같이 파라미터 앞에 . (마침표)를 3개 입력해서 선언한다.

 

function 함수명(…, ...파라미터) {
  // 처리
}

 

말로만 들으면 어려울텐데 예제를 보면 이해가 된다.

 

function myFunction() {
  logMembers_('bri9ht', 'star', 'todo', 'mall');
}

function logMembers_(first, second, ...members) {
  console.log('first, second');
  console.log(members);
}

 

구문에서 파라미터는 배열이며 나머지 인수를 순서대로 해당 배열의 요소로 추가한다.

 

실행 결과

 

인수 및 반환값은 객체나 배열을 저장할 수도 있다. 반환값은 하나만 지정할 수 있지만 객체나 배열을 지정함으로써 실질적으로는 여러 값을 반환할 수 있다. 인수 전달 방법에는 값 전달과 참조 전달의 두 종류가 있다는 점에 주의해야 한다.

 

수치, 문자열, 논리값 등 프리미티브값을 인수로 지정할 때는 값을 복제해서 함수에 전달한다. 이를 값 전달이라 한다.

 

function myFunction() {
  const x = 10;
  console.log('func1_(x)의 값 : ${func1_(x)}'); // func1_(x)의 값: 11
  console.log('x의 값: ${x}'); // x의 값 : 10
}

function func1_(y) {
  y += 1;
  return y;
}

 

x의 값은 변하지 않는다. 변수 x의 값을 전달하고 함수 func는 그 값에 1을 더한 뒤 반환값으로 돌려준다.변수 x의 값을 전달한 시점에 함수 func1_에는 메모리의 다른 주소에 파라미터 y의 영역을 확보하고 거기에 전달받은 값을 복제한다. 따라서 파라미터 y가 변경되어도 원래 변수 x와 그 값에는 영향을 주지 않는다.

 

객체나 배열을 인수로 지정하면 객체나 배열의 메모리 주소, 즉 참조값을 전달한다. 이를 참조 전달이라 한다.

 

function myFunction() {
  const x = [10, 20, 30];
  console.log('func2_(x)의 값 : ${func2_(x)}'); // func2_(x)의 값: 11, 20, 30
  console.log('x의 값: ${x}'); // x의 값 : 11, 20, 30
}

function func2_(y) {
  y[0] += 1;
  return y;
}

 

함수 func2_에서는 변수 x를 파라미터 y로 받아 그 요소값에 1을 더해 반환값으로 돌려준다. 파라미터 y를 변경했으므로 변수 x의 요소에는 아무런 일도 일어나지 않을 것처럼 보이지만 실제로 x[0] 값에도 1이 더해진 것을 알 수 있다. 파라미터 y을 변경했으므로 변수 x의 요소에는 아무런 일도 일어나지 않을 것처럼 보이지만 실제로 x[0] 값에도 1이 더해진 것을 알 수 있다.

변수 x에는 배열이 저장되어 있으며 인수로 전달한 것은 참조값, 즉 메모리 상의 주소이다. 함수 func2_의 파라미터 y에는 그 참조값이 설정된다. 따라서 변수 x와 파라미터 y가 가리키는 배열은 동일한 주소에 존재하는 배열이며 실질적으로 동일한 것이 된다.GAS에서는 객체나 배열의 전달도 빈번하게 일어난다. 그 때는 참조 전달이 되고 변경 내용이 호출한 함수 밖에서도 적용된다는 점을 기억해두자.

 

도큐멘테이션 코멘트

프로그램 안에서 여러 차례 호출되는 처리나 부품처럼 다른 프로그램에도 재사용되는 처리는 함수로 부품화하는 것이 좋다. 코드 관리나 유지보수가 쉬워지고 재사용성이 높아진다. 이 때 해당 함수가 어떤 역할을 하는지, 인수나 반환값이 어떤 것인지를 주석으로 남겨두면 편리하다. 주석을 기술하는 기법이 정해져 있으며 이를 도큐멘테이션 코멘트라 부른다. 마치 파이썬에서 docstring과 같은 역할 같다고 느꼈다.

 

  • 함수 선언 직전에 기재한다.
  • /**로 시작해 /*로 끝난다.
  • 개요와 특정 태그를 이용한 정보로 구성된다.

 

태그는 @로 시작하는 키워드이고 GAS에서는 다음과 같이 사용할 수 있다.

 

@param 인수의 정보를 추가한다. {데이터 타입} 파라미터명 - 개요
@return 반환값의 정보를 추가한다. {데이터 타입} - 개요
@customfunction 스프레드시트의 지원 후보로 한다. -

 

도큐멘테이션 코멘트 예시이다.

 

/**
 * 부가세 포함 가격을 반환하는 함수
 * 
 * @param {Number} price - 가격
 * @param {Number} taxRate - 부가세율 (부가세율 0.1)
 * @return {Number} - 부가세 포함 가격
 */

function includeTax(price, taxRate = 0.1) {
  return price * (1 + taxRate);
}

 

스크립트 편집기 내에서 /**을 적으면 자동으로 양식이 완성되어 편하게 쓸 수 있다.도큐멘테이션 코멘트 표기법에 관한 참고 페이지 : https://jsdoc.app/index.html

 

 

 

함수 리터럴

function (파라미터 1, 파라미터 2, …){
  // 처리
}

 

함수 리터럴로 정의한 함수는 정의 시점에 이름을 갖지 않는다는 점 때문에 의명 함수 또는 무명 함수 등으로 불린다. return문과 반환값 또한 함수 선언과 마찬가지로 생략할 수 있다. 함수 리터럴로 정의한 함수는 변수나 상수에 대입할수 있으며 그 대입식을 함수식이라 부른다. 함수식은 대입식으므로 스테이트먼트 끝에 ;(세미콜론)을 붙여야 한다.

아까 배운 예제를 함수 리터럴을 이용한 정의로 바꿔보자.

 

const sayHello_ = function() {
  console.log('Hello!');
};

function sayGoodBye() {
  sayHello_(); // 함수 sayHello를 호출 
  console.log('Good bye.');
}

 

직사각형의 넓이를 구한 함수 예제도 바꿔보자.

 

function myFunction() {
  console.log('사각형의 넓이: ${calArea_(3, 4)}'); // 사각형의 넓이 : 12
}

const calArea_ = function(x, y) {
  return x * y;
};

 

 

화살표 함수

함수 리터럴은 표기를 줄여서 더욱 스마트하게 기술할 수 있는 구문을 제공한다 이를 화살표 함수라고 한다. 점점 생략할 수 있어 간단하게 함수를 표기할 수 있다.

 

{파마리터1, 파라미터2, …) => {
  //처리
}

 

전달받은 파라미터를 소괄호 안에 나열하고 이어서 =와 > 기호, 그 뒤에 함수에 포함할 처리를 중괄호 안에 입력한다. 파라미터 목록을 화살표(=>)로 처리해 전달하는 듯한 이미지를 떠올리면 된다. 화살표 함수를 이용한 함수도 변수나 상수에 대입할 수 있으며 그 대입식을 화살표 함수식이라고 부른다. 화살표 함수를 이용해 함수를 대입한 변수나 상수는 그 이름을 함수명으로 호출할 수 있다.

 

const sayHello_ = () => {
  console.log('Hello!');
}

 

파라미터가 하나일 때는 인수를 감싸는 괄호를 생략할 수 있다.

 

파라미터 => {
  // 처리
}

 

함수에 포함된 스테이트먼트가 하나일 때는 중괄호도 생략할 수 있다.

 

(파라미터 1, 파라미터 2, …) => 스테이트먼트

 

함수에 포함된 스테이트먼트가 하나이고 그것이 return문이라면 키워드 return도 생략하고 반환값만 쓸 수도 있다.

 

(파라미터 1, 파라미터 2, …) => 반환값

 

다음은 위의 예제들을 더욱 간단히 쓴 경우이다.

 

// 스테이트먼트가 하나만 있는 경우
const sayHello_ = () => console.log('Hello!');

// return문만 있는 경우
const calcArea_ (x, y) => x * y;

// 파라미터가 하나일 경우
function myFunction() {
  console.log('정사각형의 넓이: ${calcSquareArea_(3)}'); // 정사각형의 넓이 : 9
}

const calcSquareArea_ = x => x ** 2;

 

정사각형의 넓이를 구하는 함수 calcSqaureArea_를 만들면 파라미터는 하나이다. 이처럼 파라미터가 하나인 경우에는 파라미터를 감싸는 소괄호도 생략할 수 있다.

화살표 함수는 이해하기 어려울 수 있지만 함수 리터럴을 짧게 하므로 코드 양을 크게 줄일 수 있다.

 

 

스코프

글로벌 영역

GAS에서는 어떤 함수에도 포함되지 않는 영역에 스테이트먼트를 입력할 수 있으며 이 영역을 글로벌 영역이라 부른다. 프로젝트에 포함된 함수가 호출되면 호출된 함수보다 먼저 글로벌 영역에 기술된 스테이트먼트가 실행된다.

 

console.log('Hello!');

function myFunction() {
  console.log('Good night…');
}

console.log('Good bye.');

 

가령 위의 코드를 실행하면 'Hello!', 'Good bye.', 'Good night…' 순으로 코드가 실행된다.이렇게 글로벌 영역에서 스테이트먼트를 기술하면 실행 순서를 알기 어렵기 때문에, 특별한 이유가 없는 한 글로벌 영역에서는 입력하지 않도록 하고 입력해야 한다면 특정 gs 파일의 가장 위에 모아두는 것이 좋다.

 

스코프

변수나 상수에는 어디에서 이들을 참조할 수 있는 가를 의미하는 범위가 정해져있다. 이를 스코프라고 부른다.

 

글로벌 스코프 프로젝트 전체에서 참조할 수 있다.
로컬 스코프 특정한 범위에서만 참조할 수 있다.
함수 스코프 선언된 함수에서만 참조할 수 있다.
블록 스코프 선언된 블록 안에서만 참조할 수 있다.

 

함수 안 또는 블록 안에서 let 키워드나 const 키워드를 이용해 선언한 변수나 상수는 해당 함수 또는 블록 안에서만 참조할 수 있다. 이렇게 특정한 범위에서만 참조할 수 있는 변수나 상수를 로컬 변수 또는 로컬 상수라고 부른다.예제를 보면 이해가 쉽다.

 

const msgGlobal = 'Hello Global!';

function myFunction() {
  const msgLocal = 'Hello Local!';

  if (true) {
    const msgBlock = 'Hello Block!';
  }
}

console.log(msgGlobal);
console.log(msgLocal);
console.log(msgBlock);

 

이 경우에는 상수 msgGlobal의 로그만 표시할 수 있다. 다른 상수 참조에서는 Reference Error가 발생한다.

 

const msgGlobal = 'Hello Global!';

function myFunction() {
  const msgLocal = 'Hello Local!';

  if (true) {
    const msgBlock = 'Hello Block!';
  }
  
  console.log(msgGlobal);
  console.log(msgLocal);
  console.log(msgBlock);
}

 

함수 안에서 상수를 참조할 경우, 상수 msgGlobal과 msgLocal은 참조할 수 있으며 msgBlock은 참조할 수 없다.

 

const msgGlobal = 'Hello Global!';

function myFunction() {
  const msgLocal = 'Hello Local!';

  if (true) {
    const msgBlock = 'Hello Block!';
    
    
    console.log(msgGlobal);
    console.log(msgLocal);
    console.log(msgBlock);
  }
}

 

블록 안에서 상수를 참조할 경우, 모든 상수를 참조할 수 있다.

 

글로벌 스코프를 가진 변수나 상수가 어디서든 쓸 수 있어 융통성 있고 편리하다고 생각할 수 있으나, 그럴 경우 덮어 쓰기를 할 가능성이 있다. 그리고 프로젝트 안에서 같은 변수명이나 상수명을 사용할 수 없다. 다양하게 미칠 수 있는 영향에 주의를 기울이면서 개발하는 것은 난이도가 높을 뿐만 아니라 많은 수고가 든다. 스코프는 가능한 한 좁은 범위로 한정하자.

 

블록

블록이란 스테이트먼트를 그룹화한 것이다. 블록은 중괄호를 이용해서 감싼다.

 

{
  // 처리
}

 

블록은 임의의 위치에 작성할 수 있으며 이를 이용해 함수나 상수의 블록 스코프를 만들 수 있다. 예시로 if문이 있다.

 

// if문 사용
if (조건식) 블록

// 블록 안의 스테이트먼트가 하나뿐인 if문
if (조건식) 스테이트먼트

 

블록 안의 스테이트먼트가 하나뿐이면 중괄호로 감싸 그룹을 만들 필요가 없으므로 간단히 기술해도 된다.

 

function myFunction() {
  let num = 1;
  for (let i = 1; i <= 10; i++) {
    num *= 2;
    console.log('i의 값 : ${i}, num의 값: ${num}');
    if (num >= 50) break;
  }
}

 

이렇게 break가 담긴 if문을 한 행으로 기술할 수 있다.if문뿐만 아니라 블록을 이용해 기술할 수 있는 분기, 반복 등의 구문에서도 동일하다. 블록 안의 처리가 한 스테이트먼트이면 중괄호를 생략할 수 있다. function문, 함수 리터럴, 메서드 구문 등에서는 생략할 수 없으니 주의하자.


깔끔하게 함수에 대해 알아봤다. 프로그램을 부품화하여 가독성과 유지 보수성을 높일 수 있으니 꼭 활용하는 것이 좋고, 그 과정에서 스코프 개념을 인지하고 있는 것이 중요하다!

조금 지쳤는데 아직 할 만합니다.

다음 주차가 마지막 공부이니 한번 열심히 나아가보자. 파이팅!


위 글은 투두몰 서포터즈 활동의 일환으로 작성된 글입니다.

https://edu.todomall.kr/?utm_source=supporters&utm_medium=contents

 

투두몰 ㅣ 일잘러의 투두리스트를 훔치다

일잘러의 투두리스트를 훔치다! 오피스 툴을 직접 따라하며 배우고, 과제를 통해 결과물을 만들어요.

edu.todomall.kr

 

댓글