함수와 추상적 사고

Grace·2022년 6월 17일
0

JavaScript

목록 보기
6/11
post-thumbnail

함수는 자신이 존재하는 컨텍스트에 따라 다양한 모습을 취합니다. 우리가 가장 먼저 되짚어 볼, 가장 간단한 일면은 코드를 재사용하는 수단이라는 측면입니다.

서브루틴으로서의 함수

서브루틴은 아주 오래 된 개념이며 복잡한 코드를 간단하게 만드는 기초적인 수단입니다. 서브루틴은 반복되는 작업의 일부를 떼어내서 이름을 붙이고, 언제든 그 이름을 부르기만 하면 실행합니다. 서브루틴은 대개 어떤 알고리즘을 나타내는 형태입니다. 알고리즘이란, 주어진 작업을 수행하는 방법이죠.

const year = new Date().getFullYear()
if(year%4 !== 0) console.log(`${year} is NOT a leap year.`)
else if(year%100 !== 0) console.log(`${year} IS a leap year.`)
else if(year%400 !== 0) console.log(`${year} is NOT a leap year.`)
else console.log(`${year} IS a leap year`)

프로그램 안에서 이 코드를 10번, 100번 실행해야 한다고 생각해 봅시다. 그런데 콘솔이 기록하는 메시지를 바꿔야 한다면? 이 코드를 쓰는 부분을 일일이 찾아다니며 바꿔야 합니다. 서브 루틴은 바로 그런 문제를 해결합니다. 자바스크립트에서는 함수가 해결합니다.

function printLeapYearStatus() {
  const year = new Date().getFullYear()
  if(year%4 !== 0) console.log(`${year} is NOT a leap year.`)
  else if(year%100 !== 0) console.log(`${year} IS a leap year.`)
  else if(year%400 !== 0) console.log(`${year} is NOT a leap year.`)
  else console.log(`${year} IS a leap year`)
}

이제 우리는 재사용할 수 있는 서브루틴(함수) printLeapYearStatus를 만들었습니다. 너무 익숙한 얘기죠. 이제 함수의 이름 printLeapYearStatus에 대해 생각해 봅시다. getLeapYearStatus나, 간결한 leapYearStatus, 또는 더 간단한 leapYear 같은 이름을 쓰지 않은 이유는 무엇일까요? 그런 이름은 더 짧기는 하지만, 중요한 사실을 놓칩니다. 이 함수가 오늘이 윤년인지를 콘솔에 기록하는 함수라는 사실 말입니다.

함수의 이름을 정하는 건 아주 중요한 일입니다. 함수의 이름은 자바스크립트를 위한 것이 아닙니다. 함수의 이름은 다른 사람, 또는 나중에 이 코드를 다시 볼 당신을 위해 정하는 겁니다. 함수의 이름을 정할 때는 다른 사람이 함수 이름만 봐도 함수에 대해 이해할 수 있도록 주의 깊게 생각하세요.

이상적인 경우는 함수가 하는 일을 이름만으로 완벽하게 나타내는 것이겠지만, 그렇게하면 지나치게 번거로울 수 있습니다. 예를 들어 오늘이윤년인지계산해서콘솔에기록한다 같은 이름을 쓴다면 더 많은 정보를 얻을 순 있겠지만, 너무 길어서 의미가 없습니다.

값을 반환하는 서브루틴으로서의 함수

이전 예제의 printLeapYearStatus는 재사용하기 편리하도록 어떤 동작을 하나로 묶었고, 그 이상은 아무 것도 하지 않았으니 서브루틴이라는 용어를 잘 반영했다고 할 수 있습니다. 하지만 이렇게 단순한 함수를 사용하는 일은 별로 없을 테고, 복잡하고 추상적인 문제에 도전하게 되면 더 줄어들 겁니다. 한 단계 더 추상화해서 함수를 값을 반환하는 서브루틴으로 생각해 봅시다.

printLeapYearStatus 함수는 잘 작동하지만, 프로그램이 커지면 콘솔에 기록하는 것은 곧 쓸모없어집니다. 이제 HTML에 결과를 출력하거나, 파일에 저장하거나, 다른 계산에 사용해야 할 수도 있는데 지금 가진 서브루틴은 그렇게 활용할 수 없습니다. 하지만 지금이 윤년인지 아닌지를 알아야 할 때마다 그에 맞게 알고리즘을 고칠 수도 없습니다.

다행히 함수가 값을 반환하는 서브루틴이 되도록 고쳐 쓰기는 쉽습니다.

function printLeapYearStatus() {
  const year = new Date().getFullYear()
  if(year%4 !== 0) return false
  else if(year%100 !== 0) return true
  else if(year%400 !== 0) return false
  else return true
}

이제 새로 만든 함수의 반환값을 활용하는 예제를 생각해 봅시다.

const daysInMonth= [31, isCurrentYearLeapYear() ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

if(isCurrentYearLeapYear()) console.log('It is a leap year.')

더 진행하기 전에 이 함수의 이름에 대해 생각해 봅시다. 불리언을 반환하거나, 불리언이 필요한 컨텍스트에서 사용하도록 만든 함수는 is로 시작하는 이름을 붙이는 게 일반적입니다. 함수 이름에는 current도 들어있습니다. 이건 왜일까요? 이 함수는 항상 현재 날짜를 기준으로 하기 때문입니다. 달리 말해, 이 함수는 2016년 12월 31일에 호출했을 때와 다음날인 2017년 1월 1일에 호출했을 때 결과가 다릅니다.

함수로서의 함수

입력은 모두 어떤 결과와 관계되어 있습니다. 프로그래머들은 이렇게 함수의 수학적인 정의에 충실한 함수를 순수한 함수라고 부릅니다.
순수한 함수에서는 입력이 같으면 결과도 반드시 같습니다. isCurrentYearLeapYear는 언제 호출하느냐에 따라서 true를 반환하기도 하고 false를 반환하기도 하므로 순수한 함수라고 할 수는 없습니다.
순수한 함수에는 부수 효과가 없어야 합니다. 즉, 함수를 호출한다고 해서 프로그램의 상태가 바뀌어서는 안된다는 뜻입니다. 지금까지 살펴본 함수에는 부수 효과가 없었습니다. 콘솔에 기록하는 결과라고 봐야 합니다.

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
let colorIndex = -1
function getNextRainbowColor() {
  if(++colorIndex>=colors.length) colorIndex = 0
  return colors[colorIndex]
}

getNextRainbowColor 함수는 호출할 때마다 무지개의 일곱 가지 색깔을 하나씩 반환합니다. 이 함수는 순수한 함수의 두 가지 정의를 모두 어깁니다. 입력이 같아도(매개변수가 없다는 말과 같습니다) 결과가 항상 다르고, 변수 colorIndex를 바꾸는 부수 효과가 있습니다. colorIndex 변수는 getNextRainbowColor 함수에 속하지 않는데도 함수를 호출하면 변수가 바뀝니다. 이것은 부수 효과입니다.

잠시 윤년 문제로 돌아가서, 이 함수를 순수한 함수로 고치려면 어떻게 해야 할까요?

function isLeapYear(year) {
  if(year%4 !== 0) return false
  else if(year%100 !== 0) return true
  else if(year%400 !== 0) return false
  else return true
}

새로운 함수는 입력이 같으면 결과도 항상 같고, 다른 효과는 전혀 없으므로 순수한 함수라고 볼 수 있습니다.

getNextRainbowColor함수를 순수한 함수로 고치는 건 손이 조금 더 갑니다. 먼저 외부 변수를 클로저로 감싸는 방법을 봅시다.

const getNextRainbowColor = (function() {
  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
  let colorIndex = -1
  return function() {
    if(++colorIndex >= colors.length) colorIndex = 0
    return colors[colorIndex]
  }
})()

이제 부수 효과는 없어졌지만, 아직은 입력이 같아도 결과가 다를 수 있으므로 순수한 함수라고 볼 수는 없습니다. 이 문제를 해결하려면 이 함수를 어떻게 사용할 것인지 주의 깊게 생각해야 합니다. 아마 이 함수는 반복적으로 호출할 겁니다. 문제는, 만약 프로그램의 다른 부분에서 getNextRainbowColor() 를 호출한다면 이 코드도 그 영향을 받는다는 겁니다. 이제 부수 효과가 있는, 다시 말해 외부에 영향을 주는 함수가 과연 좋은 것인지 생각해 볼 만한 시기입니다. 여기서는 이터레이터를 사용하는 것이 더 나은 방법입니다.

function getRainbowIterator(){
  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
  let colorIndex = -1
  return {
    next() {
      if(++colorIndex >= colors.length) colorIndex = 0
      return {value: color[colorInex], done: false}
    }
  }
}

이제 getRainbowIterator는 순수한 함수입니다. 이 함수는 항상 같은 것(이터레이터)를 반환하며 외부에 아무 영향도 주지 않습니다. 사용법이 바뀌긴 했지만, 훨씬 안전합니다.

const rainbowIterator = getRainbowIterator()
setInterval(function(){
  document.querySelector('.rainbow').style['background-color'] = rainbowIterator.next().value
}, 500)

결국 next() 메서드는 매번 다른 값을 반환할 테니, 문제를 뒤로 미뤘을 뿐 아니냐고 생각할 수도 있습니다. 틀린 말은 아니지만, next()는 함수가 아니라 메서드라는 점에 주목할 필요가 있습니다. 메서드는 자신이 속한 객체라는 컨텍스트 안에서 동작하므로 메서드의 동작은 그 객체에 의해 좌우됩니다. 프로그램의 다른 부분에서 getRainbowIterator를 호출하더라도 독립적인 이터레이터가 생성되므로 다른 이터레이터를 간섭하지 않습니다.

그래서?

왜 함수를 사용할까요? 함수를 서브루틴이라는 관점에서 보면 그 의문에 대한 한 가지 답을 얻습니다. 반복을 없애는 거죠. 서브루틴을 쓰면 자주 사용하는 동작을 하나로 묶을 수 있다는 매우 분명한 장점이 있습니다.

순수한 함수를 쓰면 코드를 테스트하기 쉽고, 이해하기 쉽고, 재사용하기도 더 쉽습니다.
함수가 상황에 따라 다른 값을 반환하거나 부작용이 있다면 그 함수는 컨텍스트에 좌우되는 함수입니다. 어떤 함수가 정말 유용하더라도 부수 효과가 있다면, 그리고 그 함수가 쓰이던 프로그램이 아닌 프로그램에서 사용하려 한다면 문제를 일으킬 수 있습니다. 99%는 제대로 동작하다가 1%의 상황에서 버그를 일으키는 상황은 더 심각합니다. 프로그래머라면 누구나 깨닫고 있겠지만, 가장 악질적인 버그는 숨어 있는 버그입니다. 이런 버그는 오랫동안 발견되지 않기 때문에, 문제가 있다는 걸 알게 되더라도 찾기가 정말 어렵습니다.

그렇기 때문에 항상 순수한 함수를 쓰는 습관을 들이는 편이 좋습니다.

함수도 객체다

자바스크립트 함수는 Function 객체의 인스턴스입니다. 현실적으로 이 사실은 함수를 사용하는 데 아무 영향도 없습니다.
typeof v를 사용하면 v가 함수일 때 "function"이 반환됩니다. v가 배열일 때는 "object"가 반환된다는 것과 비교하면 매우 상식적입니다. 하지만 v가 함수라고 하더라도 v instanceof Object는 여전히 true를 반환하므로 변수가 함수인지 아닌지 확인하고 싶다면 먼저 typeof를 써보는 것이 좋습니다.

IIFE와 비동기적 코드

IIFE(즉시 호출하는 함수 표현식)를 이용해서 클로저를 만들 수 있다고 했습니다.
비동기적 코드가 정확히 동작할 수 있도록 새 변수를 새 스코프에 만드는 겁니다. 5초에서 시작하고 카운트다운이 끝나면 "go"를 표시하는 고전적 타이머 예제에서 자바스크립트의 내장 함수 setTimeout을 사용합니다. setTimeout은 첫 번째 매개변수인 함수를 두 번쨰 매개변수인 밀리초만큼 기다렸다가 실행합니다.

var i;
for(let i=5; i>=0; i--){
  setTimeout(function(){
    console.log(i===0? "go!" : i)
  }, (5-i)*1000)
}

여기서 let 대신 var를 쓴 이유는 IIFE가 중요하던 시점으로 돌아가서 왜 중요했는지 이해하기 위해서입니다. 5,4,3,2,1, go!가 출력될 거라 예상했다면, 아쉽지만 틀렸습니다. -1이 여섯 번 출력될 뿐입니다. setTimeout에 전달된 함수가 루프 안에서 실행되지 않고 루프가 종료된 뒤에 실행됐기 때문입니다. 따라서 루프는 i가 5에서 시작해 -1로 끝납니다. 그리고 -1이 되기 전에는 콜백 함수는 전혀 호출되지 않습니다. 따라서 콜백 ㅏㅁ수가 호출되는 시점에서 i의 값은 -1입니다.

let을 사용해 블록 수준 스코프를 만들면 이 문제는 해결되지만, 비동기적 프로그래밍에 익숙하지 않다면 아 되는구나, 하고 넘어가지 말고 정확히 이해해야 합니다.
블록 스코프 변수가 도입되기 전에는 이런 문제를 해결하기 위해 함수를 하나 더 썼습니다. 함수를 하나 더 쓰면 스코프가 새로 만들어지고 각 단계에서 i의 값이 클로저에 캡처됩니다.

function loopBody(i){
  setTimeout(function(){
    console.log(i===0?"go!":i)
  }, (5-i)*1000)
}

var i
for(i=5; i>=0; i--){
  loopBody(i)
}

루프의 각 단계에서 loopBody 함수가 호출됩니다. 자바스크립트는 매개변수를 값으로 넘깁니다. 따라서 루프의 각 단계에서 함수에 전달되는 것은 변수 i가 아니라 i의 값입니다. 즉 처음에는 5가, 두 번째에는 4가 전달됩니다. 같은 변수 이름 i를 썼지만 이게 중요한 건 아닙니다. 중요한 것은 스코프 일곱 개가 만들어졌고 변수도 일곱 개 만들어졌다는 겁니다(하나는 외부 스코프, 나머지 여섯 개는 loopBody를 호출할 때마다).

하지만 루프에 한 번 쓰고 말 함수에 일일이 이름을 붙이는 건 성가신 일입니다. 익명 함수를 만들어 즉시 호출하는 IIFE를 사용하는 게 더 낫습니다. 이전 예제를 IIFE를 써서 고쳐 쓰면 다음과 같습니다.

var i;
for(let i=5; i>=0; i--){
  (function(i) {
    setTimeout(function(){
      console.log(i===0? "go!" : i)
  	}, (5-i)*1000)
  })(i)
}

매개 변수 하나를 받는 함수를 만들어서 루프의 각 단계에서 호출한 것과 완전히 똑같음을 알 수 있습니다.
블록 스코프 변수를 사용하면 스코프 하나 때문에 함수를 새로 만드는 번거로운 일을 하지 않아도 됩니다.

for(let i=5; i>=0; i==){
  setTimeout(function(){
    console.log(i===0?"go!":i)
  }, (5-i)*1000)
}

이번에는 for 루프 안에 let 키워드를 썼습니다. let 키워드를 for 루프 바깥에 썼다면 똑같은 문제가 발생했을 겁니다. let 키워드를 이런식으로 사용하면 자바스크립트는 루프의 단계마다 변수 i의 복사본을 새로 만듭니다. 따라서 setTimeout에 전달한 함수가 실행될 때는 독립 스코프에서 변수를 받습니다.

변수로서의 함수

숫자나 문자열, 배열은 변수라고 생각해도 별 거부감이 없습니다. 변수는 데이터라는 생각, 배열이나 객체는 데이터의 모임이라는 생각은 익숙합니다. 하지만 변수를 이렇게 이해하면 함수의 잠재력을 깨닫기 어렵습니다. 함수도 다른 변수와 마찬가지로 이리저리 전달할 수 있다는 사실을 떠올리기 어렵기 때문입니다. 함수는 능동적인 것이므로, 우리가 보통 수동적이라고 생각하는 데이터와 연결이 잘 되지 않을 수 있습니다. 물론 함수는 호출되었을 때는 능동적입니다. 하지만 호출하기 전에는 다른 변수와 마찬가지로 수동적입니다.

변수가 있을 수 있는 곳에는 함수도 있을 수 있습니다.

  • 함수를 가리키는 변수를 만들어 별명을 정할 수 있습니다.
  • 배열에 함수를 넣을 수 있습니다. 물론 다른 타입의 데이터와 섞일 수 있습니다.
  • 함수를 객체의 프로퍼티로 사용할 수 있습니다.
  • 함수를 함수에 전달할 수 있습니다.
  • 함수가 함수를 반환할 수 있습니다.
  • 함수를 매개변수로 받는 함수를 반환하는 것도 물론 가능합니다.

이런 유연성은 정말 강력한 특징이고, 매우 자주 사용되기도 합니다.
우선 함수에 별명을 붙이는 것부터 생각해 봅시다. 짧은 코드 안에서 여러 번 호출해야 하는 함수가 있습니다. 그런데 이 함수는 이름이 너무 길어서 타이핑하기 번거로울 뿐 아니라, 코드를 읽기도 무척 어려울 것 같습니다. 함수도 데이터이므로 짧은 이름의 변수에 저장할 수 있습니다.

function addThreeSquareAddFiveTakeSquareRoot(x){
  // 이런 이름을 짓지는 않겠지만...
  return Math.sqrt(Math.pow(x+3, 2)+5)
}

// 별명을 쓰기 전
const answer = (addThreeSquareAddFiveTakeSquareRoot(5) + addThreeSquareAddFiveTakeSquareRoot(2)) / addThreeSquareAddFiveTakeSquareRoot(7)

// 별명을 쓰면 이렇게 바뀝니다
const f = addThreeSquareAddFiveTakeSquareRoot;
const answer = (f(5)+f(2))/f(7)

별명을 지을 때 괄호를 붙이지 않았다는 점에 주목해야 합니다. 괄호를 붙이면 함수를 호출하고, f에는 호출 결과가 저장됩니다. 그러면 f를 f(5)처럼 함수로 사용하려 하면 에러가 일어납니다.

노드 개발에서 자주 쓰이는 네임스페이스에서는 계속 쓰는 패턴입니다.

const Money = requrie('math-money') // require는 라이브러리를 불러오는 노드 함수입니다.

const oneDollar = Money.Dollar(1)

// Money.Dollar도 길게 느껴지면 이렇게 해도 됩니다.
const Dollar = Money.Dollar
const twoDollar = Dollar(2)
// oneDollar와 twoDollar는 같은 타입의 인스턴스입니다.

배열 안의 함수

배열 안에 함수를 쓰는 패턴은 그리 오래되지 않았지만 점점 늘어나고 있고, 특정 상황에서는 대단히 유용합니다. 자주 하는 일을 한 셋으로 묶는 파이프라인이 좋은 예입니다. 배열을 사용하면 작업 단게를 언제든 쉽게 바꿀 수 있다는 장점이 있습니다. 어떤 작업을 빼야 한다면 배열에서 제거하기만 하면 되고, 추가할 작업이 있다면 배열에 추가하기만 하면 됩니다.

const sin = Math.sin
const cos = Math.cos
const theta = Math.PI/4
const zoom = 2
const offset = [1, -3]

const pipeline = [
  function rotate(p){
    return {
      x: p.x*cos(theta)-p.y*sin(theta),
      y: p.x*sin(theta)-p.y*cos(theta),
    }
  },
  function scale(p){
    return { x: p.x*zoom, y: p.y*zoom }
  },
  function translate(p){
    return { x: p.x + offeset[0], y: p.y + offset[1] }
  }
 ]

// 이제 pipeline은 2D 변형에 필요한 함수의 배열입니다.
// 점 하나를 변형해 봅시다.
const p = {x:1, y:1}
let p2 = p
for(let i=0; i<pipeline.length; i++) {
  p2 = pipeline[i](p2)
}

// p2는 이제 p1을 좌표 원점 기준으로 45도 회전하고
// 원점에서 2 단위만큼 떨어뜨린 후
// 1단위 오른쪽, 3단위 아래쪽으로 움직인 점입니다.

이 예제는 아주 기본적인 것이지만, 함수를 배열에 저장하는 장점을 알아보기에 충분합니다.
파이프라인은 그래픽 애플리케이션에만 쓰이는 건 아닙니다. 오디오 처리와 과학 및 공학 애플리케이션에서도 자주 사용합니다. 일정한 순서에 따라 함수를 실앻해야 한다면 파이프라인을 써서 효율적으로 일할 수 있습니다.

함수에 함수 전달

함수에 함수를 전달하는 예제는 setTimeout과 forEach에서 이미 봤습니다. 함수에 함수를 전달하는 다른 용도는 비동기적 프로그래밍입니다. 이런 용도로 전달하는 함수를 보통 콜백이라 부르며, 약자로 cb를 쓸 때가 많습니다. 콜백 함수는 자신을 감싼 함수가 실행을 마쳤을 때 호출됩니다.

함수에 함수를 전달하는 대표적인 사례가 콜백이긴 하지만, 그게 전부는 아닙니다. 함수는 동작이고, 함수를 전달받은 함수는 그 동작을 활용할 수 있습니다. 배열에 들어있는 숫자를 모두 더하는 단순한 함수 sum이 필요하다고 합시다(배열에 숫자가 아닌 것이 들어있을 때를 대비한 체크나 에러 처리는 생략합니다). 그런 함수는 쉽게 만들 수 있습니다. 그런데 숫자의 제곱을 합해서 반환하는 함수가 필요하다면? 물론 새 합수를 만들어도 됩니다. 그런데, 세제곱을 합해서 반환하는 함수도 필요하다면? 이런 상황에서 함수에 함수를 전달한다는 발상이 필요합니다.

function sum(arr, f){
  // 함수가 전달되지 않았으면 매개변수를 그대로 반환하는 null 함수를 씁니다.
  if(typeof f !== 'function') f=x => x
  
  return arr.reduce((a,x)=> a+= f(x), 0)
}

sum([1,2,3]) // 6
sum([1,2,3], x => x*x) // 14
sum([1,2,3], x=> Math.pow(x,3)) // 36

임의의 함수를 sum에 전달하면 원하는 일을 거의 모두 할 수 있습니다.

함수에 반환하는 함수

함수를 반환하는 함수는 아마 함수의 가장 난해한 사용법이겠지만, 그만큼 유용하기도 합니다. 어떤 기능이 있는 것을 만든다는 점에서, 함수를 반환하는 함수를 일종의 3D 프린터라고 생각할 수 있을겁니다. 3D 프린터에 입력하는 설계도를 바꾸는 것과 마찬가지로, 반환받는 함수 역시 마음대로 바꿀 수 있습니다.

배열과 함수를 받는 함수로는 만족스러운 결과를 얻을 수 없고, 배열 하나만 받아서 제곱의 합을 반환하는 함수가 필요합니다.

function sumOfSquares(arr){
  return sum(arr, x => x*x)
}

물론 이렇게 해도 됩니다. 필요한 것이 함수 하나라면 가장 간단한 해결책이 될 수 있습니다. 하지만 패턴이 계속 바뀐다면 어떻게 해야 할까요? 필요한 함수를 반환하는 함수를 만들어 문제를 해결할 수 있습니다.

function newSummer(f){
  return(arr => sum(arr,f))
}

새 함수 newSummer가 반환하는 함수는 단 하나의 매개변수만 받으면서도, 우리가 원하는 중간 함수를 마음대로 쓸 수 있습니다.

const sumOfSquares = newSummer(x=>x*x)
const sumOfCubes = newSummer(x=>Math.pow(x,3))
sumOfSquares([1,2,3]) // return 14
sumOfCubes([1,2,3]) // return 36

함수가 함수를 반환하는 패턴은 좀 복잡한 편입니다. 함수를 반환하는 함수의 예제를 더 보고싶다면 자바스크립트 웹 개발 프레임워크로 널리 쓰이는 익스프레스나 Koa 같은 미들웨어 패키지를 살펴보세요. 미들웨어는 대개 함수를 반환하는 함수 형태로 만들어집니다.

재귀

재귀 역시 널리 쓰이며 함수를 활용하는 중요한 패턴입니다. 재귀란 자기 자신을 호출하는 함수입니다. 같은 일을 반복하면서 그 대상이 점차 줄어드는 상황에서 재귀를 유용하게 활용할 수 있습니다.

funcfion findNeedle(haystack){
  if(haystack.length===0) return "no haystack here!"
  if(haystack.shift() === 'needle') return "found it!"
  return findNeedle(haystack)
}

findNeedle(['hay', 'hay', 'hay', 'hay', 'needle', 'hay', 'hay'])

재귀 함수에는 종료 조건이 있어야 합니다. 종료 조건이 없다면 자바스크립트 인터프리터에서 스택이 너무 깊다고 판단할 때까지 재귀 호출을 계속하다가 프로그램이 멈춥니다.

function fact(n){
  if(n===1) return 1
  return n*fact(n-1)
}
profile
기술블로그 이전:: https://meercat.tistory.com/

0개의 댓글