Function

반환 값

함수 호출의 값은 반환 값이다. 함수 바디 안에 return 키워드를 사용하면 함수를 즉시 종료하고 값을 반환하게 된다. 그 값이 함수 호출의 값이다.

function getGreeting() {
  return 'Hello World!'
}

getGreeting() // "Hello World!"

WARNING

return 을 명시적으로 호출하지 않으면 반환값은 undefined 가 된다.

호출과 참조

자바스크립트에서의 함수는 객체이다. 함수명 뒤에 괄호를 쓰지 않으면 다른 값과 마찬가지로 하수를 참고하는 것으로, 그 함수는 실행되지 않는다.

function getGreeting() {
  return 'Hello World!'
}

// 함수를 변수에 할당하여 함수를 호출할 수 있다.
const f = getGreeting
f() // "Hello World!"

// 함수를 객체 프로퍼티에 할당할 수 있다.
const o = {}
o.f = getGreeting
o.f() // "Hello World!"

// 함수를 배열 요소로 할당할 수 있다.
const arr = [1, 2, 3]
arr[1] = getGreeting
arr[1]() // "Hello World!"

함수와 매개변수

함수를 호출하면서 정보를 전달할 때는 함수 매개변수를 사용하면 된다. 매개변수는 함수가 호출되기 전에는 존재하지 않는다는 점을 제외하면 일반적인 변수와 같다.

function avg(a, b) {
  return (a + b) / 2
}

// 함수가 호출되면 정해진 매개변수는 값을 받아 실제 매개변수가 된다.
avg(5, 10) // 7.5

함수를 호출하면 함수 매개변수는 변수 자체가 아니라 그 값을 전달 받는다.

function f(x) {
  console.log(`f 내부: x =${x}`)
  x = 5
  console.log(`f 내부: x=${x} (할당 후)`)
}
let x = 3
console.log(`f 를 호출하기 전: x = ${x}`)
f(x)
console.log(`f 를 호출한 다음: x = ${x}`)

// 실행 결과
// f 를 호출하기 전: x = 3
// f 내부: x =3
// f 내부: x=5 (할당 후)
// f 를 호출한 다음: x = 3

함수 안에서 x 에 값을 할당하더라도 함수 바깥의 변수 x 에는 아무 영향을 주지 않는다. 하지만, 함수 안에서 객체 자체를 변경하면, 그 객체는 함수 바깥에서도 바뀐 점이 반영된다.

function f(o) {
  o.message = `f 안에서 수정함 (이전 값: '${o.message}')`
}
let o = {
  message: '초기 값'
}
console.log(`f를 호출하기 전: o.message = "${o.message}`)
f(o)
console.log(`f를 호출한 다음: o.message = "${o.message}`)

// 실행 결과
// f를 호출하기 전: o.message = "초기 값
// f를 호출한 다음: o.message = "f 안에서 수정함 (이전 값: '초기 값')

함수 안의 o 와 함수 바깥의 o 는 서로 다른 개체이다. 하지만 그 둘은 같은 같은 객체를 가리키고 있다.

function f(o) {
  o.message = 'f에서 수정함'
  o = {
    message: '새로운 객체!'
  }
  console.log(`f 내부: o.message="${o.message}" (할당 후)`)
}

let o = {
  message: '초기 값'
}

console.log(`f를 호출하기 전: o.message="${o.message}"`)

f(o)
console.log(`f를 호출한 다음: o.message="${o.message}"`)

// 실행 결과
// f를 호출하기 전: o.message="초기 값"
// f 내부: o.message="새로운 객체!" (할당 후)
// f를 호출한 다음: o.message="f에서 수정함"

위 예제에서는 함수 내부의 매개변수 o 와 함수 바깥의 변수 o 가 다르다. f 를 호출하면 둘은 같은 객체를 가리키지만, f 내부에서 o 에 할당한 객체는 새로운, 전혀 다른 객체이다. 함수 바깥의 o 는 여전히 원래 객체를 가리키고 있다.

매개변수가 함수를 결정하는가?

어떤 함수를 호출하든 그 함수에서 정해진 매개변수 숫자와 관계없이 몇 개의 매개변수를 전달해도 된다. 정해진 매개변수에 값을 제공하지 않으면, 암시적으로 undefined 가 할당된다.

function f(x) {
  return `in f: x=${x}`
}
f() // "in f: x=undefined"

매개변수 해체

function getSentence({ subject, verb, object }) {
  return `${subject} ${verb} ${object}`
}

const o = {
  subject: 'I',
  verb: 'love',
  object: 'Javascript'
}

getSentence(o) // "I love Javascript"

WARNING

프로퍼티 이름은 반드시 유효한 식별자여야 하고, 들어오는 객체에 해당 프로터티가 없는 변수는 undefined 를 할당 받는다.

// 배열 해체
function getSentence([subject, verb, object]) {
  return `${subject} ${verb} ${object}`
}

const arr = ['I', 'love', 'Javascript']
getSentence(arr) // "I love Javascript"

TIP

확장 연산자(...)를 써서 남는 매개변수를 처리할 수 있다.

function addPrefix(prefix, ...words) {
  const prefixedWords = []
  for (let i = 0; i < words.length; i++) {
    prefixedWords[i] = prefix + words[i]
  }
  return prefixedWords
}

addPrefix('con', 'verse', 'vex') // ["converse", "convex"]

매개변수 기본값

TIP

매개변수에 값을 제공하지 않으면 undefined 가 값으로 할당되지만, ES6 이후 부터는 매개변수에 기본값을 지정할 수 있다.

function f(a, b = 'default', c = 3) {
  return `${a} - ${b} - ${c}`
}

f(5, 6, 7) //"5 - 6 - 7"
f(5, 6) // "5 - 6 - 3"
f(5) // "5 - default - 3"
f() // "undefined - default - 3"

this 키워드

일반적으로 this 는 객체의 프로퍼티인 함수에서 의미가 있다. 메서드를 호출하면 this 는 호출한 메서드를 소유하는 객체가 된다.

const o = {
  name: 'Sun',
  speak() {
    return `My name is ${this.name}!`
  }
}

o.speak() // "My name is Sun!"

this 는 함수를 어떻게 선언했느냐가 아니라 어떻게 호출했느냐에 따라 달라진다. 즉, this 가 o 에 묶인 이유는 speak 가 o 의 프로퍼티여서가 아니라, o 에서 speak 를 호출했기 때문이다.

const speak = o.speak
speak === o.speak // true
speak() // "My name is undefined!"

WARNING

함수를 이렇게 호출하면 자바스크립트는 이 함수가 어디에 속하는지 알 수 없으므로 this 는 Name 이 아닌 undefined 에 묶이게 됩니다.

함수 표현식과 익명 함수

자바스크립트는 익명 함수(anonymous function)도 지원한다. 익명 함수에서는 함수에 식별자가 주어지지 않는다. 함수 표현식은 함수 이름을 생략할 수 있다는 점을 제외하면 함수 선언과 문법적으로 동일하다.

const f = function() {}

화살표 표기법

화살표 함수에는 세가지 단축 문법이 있다.

  1. function 을 생략할 수 있다.
  2. 함수에 매개변수가 단 하나 뿐이라면 괄호(())를 생략할 수 있다.
  3. 함수 바디가 표현식 하나라면 중괄호와 return 을 생략할 수 있다.
const f1 = function() {
  return 'hello!'
}
// or
const f1 = () => 'hello!'

const f2 = function(name) {
  return `Hello ${name}!`
}
// or
const f2 = name => `Hello ${name}!`

const f3 = function(a, b) {
  return a + b
}
// or
const f3 = (a, b) => a + b

TIP

화살표 함수에서는 this 가 다른 변수와 마찬가지로, 정적으로 묶인다. 화살표 함수를 사용하면 내부 함수 안에서 this 를 사용할 수 있다.

const o = {
  name: 'Sun',
  greetBackWards: function() {
    const getReverseName = () => {
      let nameBackWards = ''
      for (let i = this.name.length - 1; i >= 0; i--) {
        nameBackWards += this.name[i]
      }
      return nameBackWards
    }
    return `${getReverseName()} si eman ym, olleH`
  }
}
o.greetBackWards() // "nuS si eman ym, olleH"

call 과 apply, bind

call 메서드는 모든 함수에서 사용할 수 있으며, this 를 특정 값으로 지정할 수 있다.

const sun = { name: 'Sun' }
const lee = { name: 'Lee' }

function greet() {
  return `Hello, I'm ${this.name}`
}

greet() // "Hello, I'm "
greet.call(sun) // "Hello, I'm Sun"
greet.call(lee) // "Hello, I'm Lee"

함수를 호출하면서 call 을 사용하고 this 로 사용할 객체를 넘기면 해당 함수가 주어진 객체의 메서드인 것처럼 사용할 수 있다. call 의 첫 번째 매개변수는 this 로 사용할 값이고, 두 번째 부터는 호출하는 함수로 전달된다.

function update(birthYear, occupation) {
  this.birthYear = birthYear
  this.occupation = occupation
}

update.call(sun, 1992, 'programmer')
update.call(lee, 92, 'developer')

sun // {name: "Sun", birthYear: 1992, occupation: "programmer"}
lee // {name: "Lee", birthYear: 92, occupation: "developer"}

apply 는 함수 매개변수를 처리하는 방법을 제외하면 call 과 같다. call 은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply 는 매개변수를 배열로 받는다.

update.apply(sun, [1992, 'programmer'])
update.apply(lee, [92, 'developer'])

sun // {name: "Sun", birthYear: 1992, occupation: "programmer"}
lee // {name: "Lee", birthYear: 92, occupation: "developer"}

apply 는 배열 요소를 함수 매개변수로 사용해야 할 때 유용하다.

const arr = [2, 3, -5, 15, 7]
Math.min.apply(null, arr) // -5
Math.max.apply(null, arr) // 15

// 확산 연산자(...)를 사용할 수도 있다.
Math.min(...arr) // -5
Math.max(...arr) // 15

bind 를 사용하면 함수의 this 값을 영구히 바꿀 수 있다.

const updateSun = update.bind(sun)

updateSun(1992, 'nerd Developer')

sun // {name: "Sun", birthYear: 1992, occupation: "nerd Developer"}

updateSun.call('newSun', 1992, 'nerd Developer')

// name이 newSun으로 변하지 않음
sun // {name: "Sun", birthYear: 1992, occupation: "nerd Developer"}

WARNING

bind 는 함수의 동작을 영구적으로 바꾸므로 버그의 원인이 될 수 있다. 그러므로, 함수의 this 가 어디에 묶이는지 정확히 파악하고 사용해야 한다.