재귀 적으로 자바 스크립트 함수 호출


90

다음과 같이 변수에 재귀 함수를 만들 수 있습니다.

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

이와 함께, functionHolder(3);출력 할 것이다 3 2 1 0. 다음을 수행했다고 가정 해 보겠습니다.

var copyFunction = functionHolder;

copyFunction(3);3 2 1 0위와 같이 출력 됩니다. 그런 functionHolder다음 다음과 같이 변경 하면 :

functionHolder = function(whatever) {
    output("Stop counting!");

그런 functionHolder(3);줄 것 Stop counting!예상대로.

copyFunction(3);이제는 (자체가 가리키는) 함수가 아니라 3 Stop counting!참조하는대로 제공합니다 functionHolder. 이것은 어떤 상황에서는 바람직 할 수 있지만 함수를 보유하는 변수가 아닌 자체 호출하도록 함수를 작성하는 방법이 있습니까?

즉, 전화를 걸 때이 모든 단계를 거치면서 여전히 제공되도록 회선 변경할 있습니까? 시도 했지만 오류가 발생 합니다.functionHolder(counter-1);3 2 1 0copyFunction(3);this(counter-1);this is not a function


1
NB 함수 내부에서 이것은 함수 자체가 아니라 함수 실행 컨텍스트를 나타냅니다. 귀하의 경우 이것은 아마도 전역 창 개체를 가리 킵니다.
antoine

답변:


146

명명 된 함수 표현식 사용 :

함수 표현식에 실제로 비공개 이름을 지정할 수 있으며 자체적으로 만 함수 내부에서 볼 수 있습니다.

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

여기서 myself함수의 표시 만 내부 자체.

이 개인 이름을 사용하여 함수를 재귀 적으로 호출 할 수 있습니다.

13. Function DefinitionECMAScript 5 사양을 참조하십시오 .

FunctionExpression의 식별자는 FunctionExpression의 FunctionBody 내부에서 참조되어 함수가 자신을 재귀 적으로 호출 할 수 있습니다. 그러나 FunctionDeclaration과 달리 FunctionExpression의 식별자는 FunctionExpression을 포함하는 범위에서 참조 될 수 없으며 영향을주지 않습니다.

Internet Explorer 버전 8까지의 이름은 실제로 둘러싼 변수 환경에서 볼 수 있기 때문에 올바르게 작동하지 않으며 실제 함수의 복제본을 참조합니다 (아래 patrick dw 의 주석 참조).

arguments.callee 사용 :

또는 arguments.callee현재 함수를 참조하는 데 사용할 수 있습니다 .

var factorial = function (n) {
    if (n <= 1) {
        return 1;
    }
    return n * arguments.callee(n-1);
}

ECMAScript 5 판은 엄격 모드 에서 arguments.callee () 사용을 금지 합니다. 하지만 다음과 같습니다.

(From MDN ) : 일반 코드에서 arguments.callee는 둘러싸는 함수를 나타냅니다. 이 사용 사례는 약합니다. 단순히 둘러싸는 함수의 이름을 지정하십시오! 또한 arguments.callee에 액세스하면 인라인되지 않은 함수에 대한 참조를 제공 할 수 있어야하기 때문에 arguments.callee는 인라인 함수와 같은 최적화를 실질적으로 방해합니다. 엄격 모드 함수에 대한 arguments.callee는 설정 또는 검색 할 때 throw되는 삭제 불가능한 속성입니다.


4
+1 IE8 이하에서는 약간 버그가 있지만 myself둘러싼 변수 환경에서 실제로 볼 수 있으며 실제 함수 의 복제본 을 참조 myself합니다. null그래도 외부 참조를 설정할 수 있어야합니다 .
user113716

답변 해주셔서 감사합니다! 둘 다 도움이되었고 두 가지 방법으로 문제를 해결했습니다. 결국 나는 무작위로 수용하는 결정 : P
Samthere

내가 이해할 수 있도록. 각 수익에 함수를 곱한 이유는 무엇입니까? return n * myself(n-1);?
chitzui

함수가 이렇게 작동하는 이유는 무엇입니까? jsfiddle.net/jvL5euho/18 if if looping 4 회.
Prashant Tapase

일부 참조 인수에 따라 .callee는 엄격 모드에서 작동하지 않습니다.
Krunal Limbad

10

arguments.callee [MDN]을 사용하여 기능 자체에 액세스 할 수 있습니다 .

if (counter>0) {
    arguments.callee(counter-1);
}

그러나 엄격 모드에서는 중단됩니다.


6
나는 이것이 더 이상 사용되지 않는다고 믿습니다 (그리고 엄격 모드에서는 허용되지 않습니다)
Arnaud Le Blanc

@Felix : 예, "엄격 모드"는를 제공 TypeError하지만, 공식적 으로 "엄격 모드"외부에서 사용되지 않는다고 arguments.callee (또는 모든 엄격 모드 위반) 언급하는 것을 찾지 못했습니다 .
user113716

답변 해주셔서 감사합니다! 둘 다 도움이되었고 두 가지 방법으로 문제를 해결했습니다. 결국 나는 무작위로 수용하는 결정 : P
Samthere

6

Y-combinator를 사용할 수 있습니다 : ( Wikipedia )

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

그리고 다음과 같이 사용할 수 있습니다.

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});

5

나는 이것이 오래된 질문이라는 것을 알고 있지만 명명 된 함수 표현식을 사용하지 않으려면 사용할 수있는 솔루션을 하나 더 제시하겠다고 생각했습니다. (피해야하거나 피해서는 안된다고 말하는 것이 아니라 다른 해결책을 제시하는 것뿐입니다)

  var fn = (function() {
    var innerFn = function(counter) {
      console.log(counter);

      if(counter > 0) {
        innerFn(counter-1);
      }
    };

    return innerFn;
  })();

  console.log("running fn");
  fn(3);

  var copyFn = fn;

  console.log("running copyFn");
  copyFn(3);

  fn = function() { console.log("done"); };

  console.log("fn after reassignment");
  fn(3);

  console.log("copyFn after reassignment of fn");
  copyFn(3);

3

다음은 매우 간단한 예입니다.

var counter = 0;

function getSlug(tokens) {
    var slug = '';

    if (!!tokens.length) {
        slug = tokens.shift();
        slug = slug.toLowerCase();
        slug += getSlug(tokens);

        counter += 1;
        console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
    }

    return slug;
}

var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
console.log('THE SLUG IS: %s', mySlug);

공지 사항 그 counter무엇에 관해서 카운트 "뒤로" slug의 값입니다. 함수로이 때문에 우리는이 값을 기록하는되는 위치이다 재발 그래서, 우리는 본질적으로 점점 더 깊이 중첩 유지 - 로그인하기 전에이 호출 스택 전에 로깅이 일어난다.

재귀가 최종 호출 스택 항목을 충족하면 함수 호출을 "밖으로" 트램 폴링 하는 반면, 첫 번째 증가 counter는 마지막 중첩 호출 내부에서 발생합니다.

나는 이것이 질문자 코드의 "수정"이 아니라는 것을 알고 있지만 일반적으로 예시 할 것이라고 생각한 제목을 감안할 때 재귀 에 대한 더 나은 이해를 위해 재귀 를 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.