일상/컴퓨터

[JavaScript 프로그래밍] 함수

미적미적달팽이 2024. 10. 10. 03:55

https://www.codeit.kr/topics/modern-javascript

 

모던 자바스크립트 - 자바스크립트 강의 | 코드잇

프로그래밍 언어도 기존의 문제점을 보완하거나 유용한 기능을 추가해서 새로운 버전을 만들기도 하는데요. 최근에 가장 많이 쓰이는 형태의 자바스크립트를 '모던 자바스크립트'라고 합니다.

www.codeit.kr

 

함수 다루기

자바스크립트에서 키워드는 특정한 기능을 수행하기 위해 미리 정의된 단어들이다. 예를 들어, function, var, let, const, if, else, return 등이 있다. 이 키워드들은 자바스크립트 문법의 일부로, 변수 이름이나 함수 이름 등으로 사용할 수 없다. 키워드는 특정한 동작을 수행하거나 구조를 정의하는 데 사용된다. 자바스크립트에서는 function 키워드를 이용해서 함수를 작성할 수 있다. 소괄호를 닫은 다음 중괄호를 이용해서 함수 내에서 동작할 내용을 작성할 수 있다.

// 1. 함수 선언(Function Declaration)
function 함수이름(파라미터) {
  동작
  return 리턴값;
}

함수 소괄호 안에 파라미터를 작성하거나 return을 이용해서 리턴값을 반환할 수 있다. 이렇게 function 키워드를 이용해서 함수를 작성하는 것을 함수 선언(Function Declaration)이라고 한다. 

// 2. 함수 표현식(Function Expression)
const printCodeit = function () {
  console.log('Coodeit');
};

printCodeit();

함수 선언을 값처럼 활용하는 함수 표현식(Function Expression)이란 방법도 존재한다. 함수 선언을 변수에 할당하는 것을 함수 표현식이라고 하는 것이 아니라 함수 선언을 값처럼 사용하는 것을 지칭하는 것이다.

const myBtn = document.querySelector('#myBtn');

myBtn.addEventListener('click', function() {
  console.log('button is clicked!');
});

addEventListener에 두번째 파라미터로 함수를 전달한 것도 함수 표현식이다. 이 코드는 document.querySelector를 사용하여 id가 'myBtn'인 요소를 선택하고, 이를 myBtn이라는 상수에 저장하였다. 이후, myBtn 요소에 클릭 이벤트 리스너를 추가합니다. 사용자가 버튼을 클릭하면, 지정된 함수가 실행된다. 버튼이 클릭될 때마다 콘솔에 'button is clicked!'라는 메시지를 출력한다. 

함수 선언과 함수 표현식에는 어떤 차이가 있을까?

// 호이스팅
printCodeit();

function printCodeit() {
  console.log('Codeit');
}

함수 선언은 호이스팅으로 인하여 함수 선언이 위로 올라간듯한 현상이 발생해 선언 이전에 호출을 해도 사용이 가능하다.

printCodeit(); // ReferenceError

const printCodeit = function() {
  console.log('Codeit');
}

반면에 함수 표현식은 호이스팅이 일어나지 않아 선언 전에는 호출이 불가능하다.

다른 차이점은 스코프에 존재한다. 

function printcodeit() {
  function printJS() {
    console.log('JavaScript');
  }

  console.log('Codeit');
  printJS();
}

printCodeit();
printJs(); // ReferenceError

함수 선언은 변수 var처럼 함수 스코프를 가져서 전역 변수처럼 이용이 가능하다. 함수 안에서 선언된 함수는 밖에서 사용 불가능하지만 블록 안에 선언된 함수는 사용이 가능하다.

const x = 4;

if (x < 5) {
  function printJS() {
    console.log('JavaScript');
  }
}

{
  function printCodeit() {
    console.log('Codeit');
  }
}

printCodeit();
printJS();

함수 표현식은 어떤 변수에 할당하느냐에 따라 스코프가 달라질 수 있다.

var printJS = function () {
  console.log('JavaScript');
};

let printHi = function () {
  console.log('Hi');
};

const printCodeit = function () {
  console.log('Codeit');
};

그렇다면 둘 중 어떤 방법을 사용하는 것이 좋을까? 함수 선언은 변수 선언과 구별이 가기에 가독성 측면이나 자유로운 위치에서 함수를 호출할 수 있다는 장점이 있기에 선호되기도 하고, 함수 표현식은 선언 이후에 무조건 호출이 가능하고 변수 스코프를 활용할 수 있다는 장점이 있어서 선호가 되기도 한다. 한 가지 방법을 선택해서 일관되게 사용하는 것이 중요하다.

const x = 5;

if (x < 5) {
  const printHi = function () {
    console.log('Hi!');
  };
}

printHi();

이 코드는 어떻게 실행될까?

함수를 만드는 부분만 보게 되면 printHi 함수는 함수 표현식으로 만들어졌다. 하지만, 함수 표현식은 무조건 블록 스코프를 가지진 않는다. 어떤 변수에 할당하느냐에 따라 변수의 스코프가 다르게 결정되는데, 만약 var 키워드로 선언한 변수에 할당한다면 그 함수는 함수 스코프를 가지게 된다. 그리고 주어진 코드에서 if문의 조건 부분을 보면, 변수 x가 가지고 있는 값이 5이기 때문에 5 < 5는 false가 된다. 결과적으로 if문 내부의 코드는 동작하지 않게 되고, 9번 줄에서는 만들어지지 않은 함수를 호출하고 있으니 당연히 에러가 발생하게 된다.

Named Function Expression (기명 함수 표현식)

함수 표현식으로 함수를 만들 때는 선언하는 함수에 이름을 붙여줄 수도 있다. 이름이 있는 함수 표현식, 즉 기명 함수 표현식이라고 부른다. 함수 표현식으로 함수가 할당된 변수에는 자동으로 name이라는 프로퍼티를 가지게 된다.

const sayHi = function () {
  console.log('Hi');
};

console.log(sayHi.name); // sayHi

이렇게 이름이 없는 함수를 변수에 할당할 때는 변수의 name 프로퍼티는 변수 이름 그 자체를 문자열로 가지게 된다. 하지만 함수에 이름을 붙여주게 되면, name 속성은 함수 이름을 문자열로 갖게 된다.

const sayHi = function printHiInConsole() {
  console.log('Hi');
};

console.log(sayHi.name); // printHiInConsole

이 함수 이름은 함수 내부에서 함수 자체를 가리킬 때 사용할 수 있고 함수를 외부에서 함수를 호출할 때 사용할 수는 없다.

const sayHi = function printHiInConsole() {
  console.log('Hi');
};

printHiInConsole(); // ReferenceError

기명 함수 표현식은 일반적으로 함수 내부에서 함수 자체를 가리킬 때 사용된다. 아규먼트로 숫자 값을 전달하고 전달받은 그 값이 0이 될 때까지 하나씩 값을 줄이면서 자기 자신을 호출하는 countdown이라는 함수를 함수 표현식으로 작성되어 있다. 이런 식으로 자기 자신을 부르는 함수를 재귀 함수(Recursive function)라고 부른다. 그런데 만약 이 함수를 복사하려고 다른 변수에 똑같이 담았다가, countdown 변수에 담긴 값이 변하게 되면 문제가 발생하게 된다.

let countdown = function(n) {
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    countdown(n - 1);
  }
};

let myFunction = countdown;

countdown = null;

myFunction(5); // TypeError

myFunction 함수를 호출했을 때, 함수가 실행되긴 하지만, 6번줄 동작을 수행할 때 호출하려는 countdown 함수가 이미 12번에서 null 값으로 변경되었기 때문에 함수가 아니라는 TypeError가 발생한다. 이런 상황을 방지하기 위해서 함수 내부에서 함수 자신을 사용하려고 하면 함수표현식에서는 반드시 기명 함수 표현식을 사용하는 것이 좋다.

let countdown = function printCountdown(n) {
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    printCountdown(n - 1);
  }
};

let myFunction = countdown;

countdown = null;

myFunction(5); // 정상적으로 동작

함수 표현식을 작성할 때, 함수에 이름을 지정할 수 있다는 점과 특히 이렇게 함수 내에서 함수를 가리켜야 할 때는 꼭 함수 이름을 작성해주는 것이 안전하다는 점을 기억해야한다.

즉시 실행 함수

일반적으로는 함수를 먼저 선언한 다음,선언된 함수 이름 뒤에 소괄호를 붙여서 함수를 실행하는데 때로는 함수가 선언된 순간에 바로 실행을 할 수 있다.

(function () {
  console.log('Hi!');
})();

함수선언 부분을 소괄호로 감싼 다음에 바로 뒤에 함수를 실행하는 소괄호를 한 번 더 붙여주는 방식을 사용하면 함수가 선언된 순간 바로 실행이 된다. 이렇게 함수 선언과 동시에 즉시 실행되는 함수를 가리켜 즉시 실행 함수 (표현)이라고 하고, 영어로는 Immediately Invoked Function Expression, 줄여서 IIFE라고 부른다.

(function (x, y) {
  console.log(x + y);
})(3, 5);

즉시 실행 함수도 일반 함수처럼 파라미터를 작성하고, 함수를 호출할 때 아규먼트를 전달할 수도 있는데 한 가지 주의할 점은 즉시 실행 함수는 함수에 이름을 지어주더라도 외부에서 재사용할 수 없다.

(function sayHi() {
  console.log('Hi!');
})();

sayHi(); // ReferenceError

그래서 일반적으로는 이름이 없는 익명 함수를 사용하는데, 다만, 함수 내부에서 자기 자신을 호출하는 재귀적인 구조를 만들고자 할 땐 이름이 필요할 수도 있으니까 주의해서 사용해야 한다.

(function countdown(n) {
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    countdown(n - 1);
  }
})(5);

즉시 실행 함수는 말 그대로 선언과 동시에 실행이 이뤄지기 때문에 일반적으로 프로그램 초기화 기능에 많이 활용된다.

(function init() {
  // 프로그램이 실행 될 때 기본적으로 동작할 코드들..
})();

혹은 재사용이 필요 없는, 일회성 동작을 구성할 때 활용하기도 하거나, 함수의 리턴값을 바로 변수에 할당하고 싶을 때 활용할 수 있다.

const firstName = 'Young';
const lastName = 'Kang';

const greetingMessage = (function () {
  const fullName = `${firstName} ${lastName} `;

  return `Hi! My name is ${fullName}`;
})();

즉시 실행 함수에서 사용하는 변수들은 함수 내에서만 유효하기 때문에 이런 점을 활용하면, 일시적으로 사용할 변수의 이름들을 조금 자유롭게 작성할 수도 있다.

값으로서 함수

자바스크립의 주요 개념은 이렇게 있다.

  • 객체: 데이터와 기능을 묶어놓은 구조. 예를 들어, { name: 'Codeit', greet: function() { console.log('Hello!'); } } 같은 형태로 존재.
  • 메소드: 객체의 프로퍼티로 정의된 함수로, 객체와 관련된 동작을 수행. 예를 들어, { greet: function() { console.log('Hello!'); } }에서 greet은 메소드
  • 프로퍼티: 객체의 속성을 나타내는 값. 주로 데이터를 저장하는 데 사용. 예를 들어, { name: 'Codeit' }에서 name은 프로퍼티
  • 키워드: 자바스크립트 언어에서 특별한 의미를 가지는 예약어로, 변수나 함수 이름으로 사용할 수 없음. 예를 들어, if, else, return 등이 존재.

자바스크립트는 함수를 특별한 종류의 값으로 취급을 한다. typeof 연산자로 함수의 타입을 출력하면 function으로 출력이 된다. 하지만 실질적으로는 객체 취급이다.

const printJS = function () {
  console.log('JavaScript');
};

console.log(typeof printJS);

console.log가 아닌 console.dir로 다양한 타입들의 값들을 콘솔에 출력해보면

console.dir(0);
console.dir('codeit');
console.dir(true);
console.dir(null);
console.dir(undefined);
console.dir({});
console.dir([]);
console.dir(printJS);
function
0
'codeit'
true
null
undefined
{}
[]
[Function: printJS]

함수는 다른 기본형 타입과는 다르게 객체와 비슷한 형태로 출력이 된다. 함수를 더 자세히 보면

printJS()
arguments: null
alter: null
length: 0
name: "print JS"
prototype: {constructor: f}
__proto__: f()
[[FunctionLocation]]: index.js:2
[[Scopes]]: Scopes [2]

여러 개의 프로퍼티를 가지는 객체 형태를 하고 있다.

자바스크립트에서 함수는 어디에서나 할당될 수 있고 다양한 형태로 호출될 수 있다. 

const myObject = {
  brand: 'Codeit',
  bornYear: 2017,
  isVeryNice: true,
  sayHi: function(name) {
    console.log(`Hi! ${name}`);
  }
};

myObject.sayHi('JavascScript');

객체 안에 프로퍼티로 함수를 선언해서 이후에 호출해서 사용할 수도 있다.

const myArray = [
  'codeit',
  2017,
  true,
  function(name) {
    console.log(`Hi! ${name}`);
  },
];

myArray[3]('Codeit');

또는 배열의 요소로 선언을 해서 함수를 호출할 수도 있다.

다른 함수에 파라미터로 전달할 때 파라미터에서 선언하는 것 뿐만 아니라 이미 선언된 함수를 파라미터를 전달해서 원하는 조건에 따라 함수를 실행하게 할 수 있다.

function makeQuiz(quiz, answer, success, fail) {
  if (prompt(quiz) === answer) {
    console.log(success());
  } else {
    console.log(fail());
  }
};

function getSuccess() {
  return '정답';
};

function getFail() {
  return '오답!';
};

const question = '5 + 3 = ?';
makeQuiz(question, '8', getSuccess, getFail);

이렇게 다른 함수의 파라미터에 전달된 함수를 콜백 함수(Call-Back-Function)이라고 하며 여기는 getSuccess와 getFail이 콜백 함수이다.

function getPrintHi() {
  return function () {
    console.log('Hi!?');
  };
};

const sayHi = getPrintHi();

sayHi();
getPrintHi()();

함수가 하나의 값으로 취급된다는 것은 다른 함수의 리턴 값으로 사용이 될 수 있다는 것이다. 이 코드에서 getPrintHi는 함수를 리턴하고 있다. 이렇게 함수를 리턴하는 함수를 고차 함수(High Order Function)이라고 하며 변수에 호출된 값을 할당해서 활용하기도 하지만, 이중괄호를 이용해서 리턴할 수도 있다.

자바스크립트에서의 함수 특징을 정리해보자면, 
typeof 연산자로 함수의 타입을 확인하려고 하면 function 이라는 문자열이 리턴되고, 객체이며, 함수는 선언 자체로도 값으로 평가될 수 있기에 변수에 함수 선언을 할당하는 함수 표현식이 가능했었다. 함수는 변수나 다른 데이터 구조 안에 할당할 수 있고, 다른 함수의 파라미터로 전달될 수 있으며 다른 함수의 리턴값이 될 수도 있다. 이러한 조건을 모두 만족하는 함수를 일급 함수(First-Class-function)이라고 부른다.

파라미터

함수에 다양한 것들을 전달 받고자 하면 파라미터를 활용해서 함수 내부에서 변수처럼 사용할 수 있다.

// Parameter
function greeting(name = 'Codeit', interest) {
  console.log(`Hi! My name is ${name}!`);
}

greeting('JavaScript');
greeting('Codeit');
greeting('world');
Hi! My name is JavaScript!
Hi! My name is Codeit!
Hi! My name is world!

같은 함수라도 전달하는 값에 따라서 결과가 달라진다. 여기서 함수를 호출할 때 파라미터로 전달하는 값을 아규먼트 argument라고 한다. 따라서 아규먼트와 파라미터를 같은 취급할 수 있지만, 다르게 구분해야한다. 

ES2015에서 부터는 파라미터에 기본값을 사용하는 문법이 등장하기 시작했다.

// Parameter
function greeting(name = 'Codeit', interest) {
  console.log(`Hi! My name is ${name}!`);
  console.log(`I like ${interest}!`);
}

greeting('JavaScript');
Hi! My name is JavaScript!
I like undefined!

파라미터에 아무 값 없이 전달하게 된다면 undefined가 반환되게 된다. 또한, 아규먼트를 파라미터 수대로 전달하지 않으면, 첫번째 파라미터에 기본값이 지정되어 있더라도 첫번째에 할당이 되기 때문에 두번째 파라미터는 undefined를 반환하게 된다. 따라서 기본값을 지정하고 그것을 사용하고자 하면 후순위에 작성을 해야한다.

function greeting(name = 'Codeit', interest = 'Java') {
  console.log(`Hi! My name is ${name}!`);
  console.log(`I like ${interest}!`);
}

greeting (undefined,'Python');

파라미터의 기본값은 아규먼트에 undefined가 전달되었을 때 작동되기도 한다.

function defaultTest(x, y = x + 3) {
  console.log(`x: ${x}`);
  console.log(`y: ${y}`);
}

defaultTest(2);
// x: 2
// y: 5
defaultTest(2, 8);
// x: 2
// y: 8

기본값은 다른 파라미터의 값을 활용해서 할당할 수도 있다. 아규먼트를 하나만 전달해서 x에 할당하고 y에 x를 이용해서 기본값을 설정하면 x를 사용한 값이 y에 할당된다.

function introduce(name = '홍길동', birth = 1443) {
  console.log(`안녕하세요 저는 ${name}입니다.`);
  console.log(`${birth}년에 태어났습니다.`);
}

introduce('장동건');
introduce('Joy', null);
introduce(undefined, 2090);

이 코드를 실행했을 때는 어떻게 결과가 나올까?

안녕하세요 저는 장동건입니다.
1443년에 태어났습니다.
안녕하세요 저는 Joy입니다.
null년에 태어났습니다.
안녕하세요 저는 홍길동입니다.
2090년에 태어났습니다.

첫 번째 함수 호출은 아규먼트로 문자열 하나만 전달하고 두 번째는 생략했다. 그러면 두 번째 파라미터는 기본값을 가지고 동작하게 된다.

두 번째는 문자열 Joy와 null 값을 아규먼트로 사용했다. 간혹 null 값과 undefined 를 혼동해서 null 값을 전달하더라도 기본값이 사용될 거라 오해할 수도 있는데, 함수를 호출할 때 아규먼트로 null 값을 사용하게 되면 해당 파라미터는 null 값을 그대로 전달받게 된다.

세 번째 함수 호출은 undefined 와 숫자 2090 을 아규먼트로 사용했다. undefined 값을 아규먼트로 사용했기 때문에 첫 번째 파라미터는 기본값을 가지고 동작하고, 두 번째 파라미터는 숫자 2090 라는 값을 전달받게 된다.

Arguments

함수에 전달하는 아규먼트를 다루는 방법도 여러가지이다. 함수에 파라미터를 3개를 지정하고, 아규먼트 개수를 제각각으로 전달해보자.

function printArguments(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
    console.log('---------------');
}

printArguments('Young', 'Koby', 'Mark');
printArguments('Captain');
printArguments('Jayden', 'Scotti');
printArguments('Suri', 'Jack', 'Joy', 'Noel');
Young
Koby
Mark
---------------
Captain
undefined
undefined
---------------
Jayden
Scotti
undefined
---------------
Suri
Jack
Joy
---------------

아규먼트가 파라미터보다 적으면 undefined로 나오고, 이상 할당을 하면 나머지는 버려지게 된다. 

아규먼트를 좀 더 유연하게 사용하려면 함수 내부에서 arguments란 객체를 사용하면 된다. arguments를 호출하게 되면 파라미터와 상관없이 전달된 값들을 저장하게 된다.

function printArguments(a, b, c) {
    console.log(arguments);
    console.log(arguments.length)
    console.log(arguments[0])
    for (const arg of arguments){
      console.log(arg);
    }
    console.log('---------------');
}

printArguments('Young', 'Koby', 'Mark');
printArguments('Captain');
printArguments('Jayden', 'Scotti');
printArguments('Suri', 'Jack', 'Joy', 'Noel');
[Arguments] { '0': 'Young', '1': 'Koby', '2': 'Mark' }
3
Young
Young
Koby
Mark
---------------
[Arguments] { '0': 'Captain' }
1
Captain
Captain
---------------
[Arguments] { '0': 'Jayden', '1': 'Scotti' }
2
Jayden
Jayden
Scotti
---------------
[Arguments] { '0': 'Suri', '1': 'Jack', '2': 'Joy', '3': 'Noel' }
4
Suri
Suri
Jack
Joy
Noel
---------------
[Arguments] { '0': 'Young', '1': 'Koby', '2': 'Mark' }
3
Young
Young
Koby
Mark
---------------
[Arguments] { '0': 'Captain' }
1
Captain
Captain
---------------
[Arguments] { '0': 'Jayden', '1': 'Scotti' }
2
Jayden
Jayden
Scotti
---------------
[Arguments] { '0': 'Suri', '1': 'Jack', '2': 'Joy', '3': 'Noel' }
4
Suri
Suri
Jack
Joy
Noel
---------------

이 객체는 배열과 비슷한 형태를 띄고 있지만 배열의 메소드는 사용이 불가능하다. length 프로퍼티로 아규먼트 개수를 확인할 수 있고, 인덱싱을 통해 접근이 가능하며 반복문에도 사용이 가능하다. 객체를 사용해서 전달되는 아규먼트에 따라 유연하게 동작하는 함수를 만들 수 있다. 하지만 함수 내에서 파라미터 이름이나 함수, 변수 이름을 중복해서 arguments로 지정해서는 안된다.

function firstWords() {
  let word = '';

  for(const arg of arguments) {
    word += arg[0];
  }

  console.log(word);
}

firstWords('나만', '없어', '고양이');
firstWords('아니', '바나나말고', '라면먹어');
firstWords('만두', '반으로', '잘라먹네', '부지런하다');
firstWords('결국', '자바스크립트가', '해피한', '지름길');
firstWords('빨간색', '주황색', '노란색', '초록색', '파란색', '남색', '보라색');
나없고
아바라
만반잘부
결자해지
빨주노초파남보

함수를 호출할 때 전달되는 아규먼트의 개수가 불규칙적일 때 arguments 객체를 유용하게 활용해 볼 수 있다. arguments 객체는 유사 배열 객체이기 때문에 indexing을 통해 요소에 접근하거나 for of문으로 반복을 통해 요소를 하나씩 살펴볼 수 있다. 그리고 자바스크립트에서 문자열은 배열과 비슷한 특징들이 몇 가지 중 하나는 대괄호 표기법을 통해 indexing이 가능하다는 점이다.

Rest Parameter

하지만 arguments 객체에도 단점이 존재한다. 배열의 메소드를 사용할 수 없다는 점, 그리고 따로 묶어서 사용하려면 인덱싱을 사용해야하는 점 등이 있다. 이러한 단점을 보완하기 위해 ES2015부터 Rest Parameter가 등장했다.

function printArguments(...args) {
  // args 객체의 요소들을 하나씩 출력
  for (const arg of args) {
    console.log(arg); 
  }
}

printArguments('Young', 'Mark', 'Koby');

레스트 파라미터를 사용하고 싶다면 일반 파라미터 앞에 점 3개를 붙이면 된다. arguments 객체는 유사 배열이기에 배열 메소드를 사용할 수 없지만, Rest Parameter는 배열이기에 사용이 가능하다.

// Rest Parameter
function printArguments(...args){
  console.log(arg.splice(0, 2));
  console.log(arguments.splice(0, 2));
  console.log('---------------');
}

printArguments('Young', 'Koby', 'Mark');
printArguments('Captain');
printArguments('Jayden', 'Scotti');
printArguments('Suri', 'Jack', 'Joy', 'Noel');

이 코드에서 arg는 레스트 파라미터이기에 splice 메소드가 잘 작동하지만 아규먼트 객체는 오류가 발생한다.

// Rest Parameter
function printRank(first, second, ...others) {
    console.log('코드잇 레이스 최종 결과');
    console.log(`우승: ${first}`);
    console.log(`준우승: ${second}`);
    for (const arg of others) {
        console.log(`참가자: ${arg}`);
    }
}

printRank('Phil', 'Won', 'Nunu', 'Mini', 'Luke');

또한 일반 파라미터와 사용이 가능하기에 앞에 지정된 일반 파라미터에 할당된 아규먼트를 제외한 나머지 아규먼트가 레스트 파라미터에 할당된다. 명확하게 다룰 부분은 일반 파라미터로, 유연하게 다룰 수 있는 부분은 레스트 파라미터로 지정해서 필요에 따라 사용이 가능하다. 레스트 파라미터가 가진 장점들이 많기에 대체로 많이 사용하지만 상황에 따라 선택해서 사용해야 한다.

function ignoreFirst(...rest) {
  rest.shift();

  for (const el of rest) {
    console.log(el);
  }
}

ignoreFirst('1세대', '2세대', '3세대');
ignoreFirst('곰팡이', '강아지', '고양이');
ignoreFirst(20, 9, 18, 19, 30, 34, 40);
2세대
3세대
강아지
고양이
9
18
19
30
34
40

rest parameter를 활용해서 함수를 호출할 때 여러 개의 아규먼트를 전달할 경우 첫 번째 아규먼트는 무시하고 두 번째 아규먼트 부터 하나씩 콘솔에 출력하는 ignoreFirst 함수이다. rest parameter가 arguments 객체와는 다르게 배열이라는 점을 응용해서, 아규먼트로 전달하는 값들 중 가장 첫 번째를 제외한 값들을 콘솔에 출력해야 하니, 가장 첫 요소를 제거해 주는 배열의 shift 메소드를 활용하는 방식이다. shift 메소드로 첫 요소를 제거한 다음 for of문을 통해 각 요소를 콘솔에 출력하게 된다.

function ignoreFirst(first, ...rest) {
  for (const el of rest) {
    console.log(el);
  }
}

또 다른 방법으로는 rest parameter를 일반 파라미터와 함께 사용해 함수를 호출할 때 전달하는 아규먼트들은 앞쪽에 선언된 일반 파라미터에 먼저 할당되고, 나머지 값들이 rest parameter에 배열로 모이는 특징을 활용함으로써 파라미터 하나를 먼저 선언해 두고 두 번째 파라미터를 rest parater로 작성해서 for of문을 활용하면 된다.

Arrow Function

ES2015년에 등장한 문법으로서 기존의 함수 선언 방식을 간결하게 만들어 준다. 이름이 없는 익명 함수로서 이름을 가진 변수에 할당하거나 다른 함수의 아규먼트를 선언할 때 주로 활용된다.

// 일반적인 함수 선언
const getTwice = Function(number) {
  return number * 2;
};

console.log(getTwice(5));

const myBtn = document.querySelector('#myBtn');

myBtn.addEventListener('click', function() {
  console.log('button is clicked!');
});

Arrow Function은 function 키워드를 사용하지 않고, 소괄호 오른편에서 등호와 부등호를 사용해서 이름처럼 화살표 기호를 표현해준다.

// Arrow Function
const getTwice = (number) => {
  return number * 2;
};

console.log(getTwice(5));

const myBtn = document.querySelector('#myBtn');
myBtn.addEventListener('click', () => {
  console.log('button is clicked!');
});

또는 콜백함수로 사용할 때도 이용이 가능하다.

// 화살표 함수 정의
const getTwice = (number) => {
  return number * 2;
};

// 콜백 함수로 활용
myBtn.addEventListener('click', () => {
  console.log('button is clicked!');
});

기존의 키워드를 이용한 함수 선언을 간결하게 하기 위해 만들어진 것으로 더 간결하게도 표현이 가능하다.

// 함수 표현식
const getTwice = function(number) {
    return number * 2;
};

// 함수 선언
function getTwice(number) {
    return number * 2;
}

// 화살표 함수
const getTwiceArrow = number => number * 2;

console.log(getTwiceArrow(6));

하지만 모든 함수들이 간결하게 표현을 하수는 없고 특정 상황에서만 가능하다. 파라미터가 하나일 때만 소괄호를 생략할 수 있다. 다만, 가독성을 위해서 소괄호를 선호하는 방법도 존재한다. 또한, 함수가 retun 키워드로만 이루어져 있다면 중괄호와 함게 생략이 가능하다. 하지만, 조건문이나 반복문 등 리턴 외의 다른 문장이 있다면 생략 불가능하다. 그리고 리턴되는 값이 객체이면 객체의 중괄호를 함수의 중괄호로 해석하기에 오류가 발생하므로, 소괄호로 한번 더 감싸줘야 한다.

// 1. 함수의 파라미터가 하나 뿐일 때
const getTwice = (number) => {
  return number * 2;
};

// 파라미터를 감싸는 소괄호 생략 가능
const getTwice = number => {
  return number * 2;
};

// 2. 함수 동작 부분이 return문만 있을 때
const sum = (a, b) => {
  return a + b;
};

// return문과 중괄호 생략 가능
const sum = (a, b) => a + b;

// 객체 중괄호에 소괄호 추가
const getCodeit = () => ({
  name: 'Codeit',
});

console.log(sum(2, 3));
console.log(getCodeit());

arrow function을 사용할 때 주의할 점은 arguments 객체가 사용이 불가능하다.

this 키워드

자바스크립트에서 "this" 키워드를 콘솔로 출력하면 전역 객체인 윈도우 객체가 출력된다. 

console.log(this);
Window {window: Window, self: Window, document: document, name:"", location: Location, ...}

this는 함수 내부에서 객체의 메소드를 만들 때 중요한 역할을 하게 된다.

const user = {
  firstName: 'Tess',
  lastName: 'Jang',
  getFullName: function() {
    return `${user.firstName} ${user.lastName}`;
  }
};

console.log(user.getFullName());

user 객체에 firstName과 lastName 프로퍼티를 만들고 getFullName 메소드에는 user 객체의 프로퍼티를 합쳐서 문자열을 리턴하고 있다. 이 메소드를 다른 객체에도 적용하고 싶을 때 함수 외부로 분리하게 될 때 문제가 발생하게 된다. 

// getFullName 함수 정의
function getFullName() {
  return `${user.firstName} ${user.lastName}`;
}

// user 객체 정의
const user = {
  firstName: 'Tess',
  lastName: 'Jang',
  getFullName: getFullName
};

// admin 객체 정의
const admin = {
  firstName: 'Alex',
  lastName: 'Kim',
  getFullName: getFullName
};

// 함수 호출 및 결과 출력
console.log(user.getFullName());
console.log(admin.getFullName());

getFullName은 user 객체를 가리키고 있기에 다른 객체의 메소드를 호출하려고 해도 user 객체의 메소드를 출력하게 된다.

// getFullName 함수 정의
function getFullName() {
  return `${this.firstName} ${this.lastName}`;
}

하지만 this를 사용하게 되면 그 객체에 맞게 선택해서 출력하게 된다. this는 함수를 호출하는 객체를 가리키는 키워드로 작성할 때 값이 미리 결정되는 게 아니라 함수가 호출될 때 어떤 객체가 함수를 호출했는지에 따라 상대적으로 값이 변하게 된다.

// this
console.log(this);

function printThis() {
	console.log(this);
}

printThis();
{}
Window {window: Window {...}, self: Window ...}

그냥 this를 사용하거나 함수로 호출하게 되면 window 객체를 가리킨다.

// this
console.log(this);

function printThis(){
	console.log (this);
};

const myObj = {
	content:'myObj',
	printThis: printThis,
};

const other0bj = {
	content:'otherObj',
	printThis: printThis,
};

myObj.printThis();
other0bj.printThis()
{}
{ content: 'myObj', printThis: [Function: printThis] }
{ content: 'otherObj', printThis: [Function: printThis] }

어떤 객체의 메소드로 호출이 되는 경우에는 호출하는 함수의 객체가 this에 담기기 때문에 똑같은 함수를 호출했지만, 각기 다른 출력 결과를 보여준다.Arrow Function에서 this는 호출한 대상에 따라 상대적으로 변하지 않고 선언되기 직전에 유효한 this 값과 똑같은 값을 가지고 동작을 하게 된다. 따라서 객체에 메소드를 만드려고 할 때는 일반적인 함수 선언이 좋다.

const getFullName = () => `${this.firstName} ${this.lastName}`;

const user = {
  firstName: 'Ted',
  lastName: 'Chang',
  getFullName: getFullName,
};

console.log(user.getFullName());
undefined undefined

getFullName이 선언 될 때 this의 값은 전역객체인 window 객체가 됩니다. getFullName 함수를 실행하려고 하면, window 객체의 firstName과 lastName 프로퍼티에 접근하려고 하게 된다. 그래서 코드를 실행하면 undefined undefined 가 출력되는 것이다.만약 메소드를 만들 때 this에 메소드를 호출한 객체를 가리키고 싶다면 arrow function 보다는 일반 함수를 활용해야 한다.

const obj1 = {
  name: '객체 1',
  getName: function() {
    return this.name;
  }
};

const obj2 = {
  name: '객체 2'
};

// obj1의 getName 메소드를 obj2에서 호출
obj2.getName = obj1.getName;

console.log(obj1.getName()); // "객체 1"
console.log(obj2.getName()); // "객체 2"

this를 활용한 메소드를 다른 여러 객체에 활용한다고 해서 반드시 전역 스코프를 가지는 함수를 만들 필요는 없다. 일반함수로 this를 활용한 메소드를 객체 내부에서 선언하고 다른 객체에서 그 메소드를 참조하더라도 this는 항상 그 메소드를 호출한 객체를 가리킨다.

다양한 함수의 형태

// 변수에 할당해서 활용
const printJS = function () {
  console.log('JavaScript');
};

// 객체의 메소드로 활용
const codeit = {
  printTitle: function () {
    console.log('Codeit');
  }
}

// 콜백 함수로 활용
myBtn.addEventListener('click', function () {
  console.log('button is clicked!');
});

// 고차 함수로 활용
function myFunction() {
  return function () {
    console.log('Hi!?');
  };
};

 

반응형