JavaScript의 다른 함수 내에서 함수 정의


82
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

위와 비슷한 구조의 기능이 있습니다. 작업 3 을 함수로 추상화하고 싶지만 bar()이 함수의 액세스를 범위 내로 만 제한하고 싶습니다 foo(a).

내가 원하는 것을 달성하기 위해 다음으로 변경하는 것이 옳습니까?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

위의 내용이 맞으면 호출 bar()될 때마다 get 재정의 foo(a)됩니까? (여기서 CPU 리소스 낭비가 걱정됩니다.)


1
가치가 있는지 테스트하십시오 : jsperf.com 나는 그것이 task3에 달려 있다고 생각합니다.
tomByrer

1
@tomByer - +1 도구 제안에 대한
tamakisquare

답변:


121

네, 당신이 가지고있는 것이 옳습니다. 몇 가지 참고 사항 :

  • bar함수를 호출 할 때마다 생성 foo되지만 다음과 같습니다.
    • 최신 브라우저에서 이것은 매우 빠른 프로세스입니다. (일부 엔진은 코드 를 한 번만 컴파일 한 다음 매번 다른 컨텍스트로 해당 코드를 재사용 할 수 있습니다. 대부분의 경우 Google의 V8 엔진 (Chrome 및 기타)에서 수행합니다.)
    • 그리고 무엇을하는지에 따라 bar일부 엔진은이를 "인라인"하여 함수 호출을 완전히 제거 할 수 있다고 결정할 수 있습니다. V8이이 작업을 수행하며, 이것이 수행하는 유일한 엔진은 아닙니다. 당연히 코드의 동작을 변경하지 않는 경우에만이를 수행 할 수 있습니다.
  • bar매번 생성 되는 성능 영향 은 JavaScript 엔진마다 크게 다릅니다. bar사소한 경우 감지 할 수없는 것부터 매우 작은 것까지 다양합니다. foo(예를 들어 mousemove핸들러에서) 연속으로 수천 번 호출 하지 않는다면 걱정하지 않을 것입니다. 당신이 그렇더라도 느린 엔진에서 문제를 본 경우에만 걱정할 것입니다. 다음은 DOM 작업과 관련된 테스트 케이스 입니다. 이는 영향이 있음을 시사하지만 사소한 것입니다 (아마도 DOM에 의해 씻겨 나옴). 여기에 순수 계산하고 테스트 케이스의 어떤 쇼 훨씬 더 높은 영향을하지만, 솔직히 심지어 우리의 차이를 얘기 마이크로 무언가에 심지어 92 % 증가합니다 그 때문에 초 미세발생하는 시간은 여전히 ​​매우, 매우 빠릅니다. 실제 영향을보기 전까지는 걱정할 필요가 없습니다.
  • bar함수 내에서만 액세스 할 수 있으며 해당 함수 호출에 대한 모든 변수 및 인수에 액세스 할 수 있습니다. 이것은 매우 편리한 패턴을 만듭니다.
  • 함수 선언을 사용했기 때문에 선언 위치 (위쪽, 아래쪽 또는 중간)는 흐름 제어 문 내부가 아니라 함수의 최상위 수준에있는 한 중요하지 않습니다. 구문 오류), 단계별 코드의 첫 번째 줄이 실행되기 전에 정의됩니다.

귀하의 답변에 대한 Thx. 그래서 이것이 무시할만한 성능 저하라는 말입니까? (사본 주어진 bar모든 호출이 생성된다 foo)
tamakisquare

2
@ahmoo : JavaScript 성능에 대한 대답은 거의 항상입니다. :-) 그것은 어떤 엔진이 그것을 실행하고 얼마나 자주 호출 할 것인지에 달려 있습니다 foo. foo예를 들어 mousemove핸들러가 아닌 연속으로 수천 번 호출 하지 않으면 전혀 걱정하지 않을 것입니다. 그리고 일부 엔진 (예 : V8)은 어쨌든 코드를 인라인하여 함수 호출을 완전히 제거합니다. 단, 그렇게해도 외부에서 감지 할 수있는 방식으로 일어나는 일이 변경되지는 않습니다.
TJ Crowder

@TJCrowder : robrich의 대답에 대해 언급 할 수 있습니까? 이 솔루션이 bar()각 통화 에서 재생성을 방지 합니까? 또한 foo.prototype.bar함수를 정의하는 데 도움이 될까요?
rkw

4
@rkw : robrich의 답변처럼 함수를 한 번 생성하는 것은 모든 호출에서 생성하는 비용을 피하는 유용한 방법입니다. bar호출에 대한 변수 및 인수에 대한 액세스 권한 이 있다는 사실을 잃어 버립니다 foo(작동하려는 모든 항목을 전달해야 함). 이는 작업을 약간 복잡하게 만들 수 있지만 성능이 중요한 상황에서 실제 문제를 본다면 문제가 해결되는지 확인하기 위해 그렇게 리팩토링 할 수 있습니다. 아니요, 사용 foo.prototype은 실제로 도움이되지 않습니다 (한 가지 이유 bar는 더 이상 비공개가 아닙니다).
TJ Crowder

@ahmoo : 테스트 케이스를 추가했습니다. 흥미롭게도 Guffa의 테스트 케이스와 다른 결과를 얻었습니다. 그의 기능이 너무 단순 할 수 있다고 생각 합니다. 하지만 여전히 성능이 문제가 될 필요는 없다고 생각합니다.
TJ Crowder

15

이것이 클로저의 목적입니다.

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (클로저)는 bar에 대한 참조를 보유하며, 클로저를 생성하기 위해 한 번만 호출되는 익명 함수에서 innerfoo에 대한 참조 만 반환됩니다.

바는 외부에서이 방법으로 접근 할 수 없습니다.


1
흥미 롭군. 나는 자바 스크립트에 대한 노출이 제한되어 있으므로 폐쇄는 나에게 새로운 것입니다. 그러나 당신은 제가 종결을 연구 할 시작점을 표시했습니다. 감사.
tamakisquare

변수 / 함수 범위를 처리하기 위해 얼마나 자주 클로저를 사용합니까? 예를 들어 동일한 3 개의 변수에 액세스해야하는 2 개의 함수가있는 경우 2 개의 함수와 함께 3 개의 변수를 클로저에 선언 한 다음 2 개의 함수를 반환합니까?
doubleOrt

8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

클로저는 bar()포함 된 범위를 유지하고 자체 실행 익명 함수에서 새 함수를 반환하면보다 가시적 인 범위를 foo(). 익명의 자체 실행 기능은 정확히 한 번만 실행되므로 bar()인스턴스 가 하나 뿐이며 모든 실행이 foo()이를 사용합니다.


흥미 롭군. 그럼 클로저를 찾아봐야 겠어 감사.
tamakisquare

변수 / 함수 범위를 처리하기 위해 얼마나 자주 클로저를 사용합니까? 예를 들어 동일한 3 개의 변수에 액세스해야하는 2 개의 함수가있는 경우 2 개의 함수와 함께 3 개의 변수를 클로저에 선언 한 다음 2 개의 함수를 반환합니까?
doubleOrt

클로저는 얼마나 자주 사용합니까? 항상. 2 개의 함수에 필요한 3 개의 변수가있는 경우 : 1. (가장 좋은) 3 개의 변수를 두 함수에 전달합니다. 함수는 한 번만 정의 할 수 있습니다. 2. (좋음) 변수가 둘 다의 범위 밖에있는 두 개의 함수를 만듭니다. 이것은 폐쇄입니다. (기본적으로 여기에 대한 답입니다.) 슬프게도 변수를 새로 사용할 때마다 함수가 재정의됩니다. 3. (나쁜) 함수를 사용하지 말고 두 가지를 모두 수행하는 하나의 큰 긴 방법을 사용하십시오.
robrich

5

예, 잘 작동합니다.

내부 기능은 외부 기능을 입력 할 때마다 다시 생성되지 않지만 다시 할당됩니다.

이 코드를 테스트하는 경우 :

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

이 표시됩니다 1, 2, 1, 2,하지 1, 2, 2, 2.


답변 해 주셔서 감사합니다. 호출 할 demo()때마다 재 할당이 test()성능 문제가되어야합니까? 의 복잡성에 달려 demo()있습니까?
tamakisquare

1
성능 테스트를했습니다. jsperf.com/inner-function-vs-global-function 결론은 일반적으로 성능 문제가 아니라는 것입니다 (함수에 입력하는 코드는 함수를 만드는 것보다 실행하는 데 훨씬 더 오래 걸립니다. 그 자체로),하지만 추가 성능 에지가 필요하다면 브라우저마다 다른 코드를 작성해야합니다.
Guffa

테스트를 만드는 데 시간을 할애하고 성능에 대한 포인트를 공유해 주셔서 감사합니다. 매우 감사.
tamakisquare

"내부 기능은 매번 재생산되지 않는다"고 자신있게 말씀하셨습니다. 사양 에 따르면 다음과 같습니다. 엔진이 최적화하는지 여부는 엔진에 따라 다릅니다. (대부분 그럴 것으로 예상합니다.) 귀하의 테스트 케이스와 내 결과가 매우 다양하다는 사실에 흥미를 느낍니다 . jsperf.com/cost-of-creating-inner-function 성능이 문제라고 생각하지는 않습니다.
TJ Crowder

@TJCrowder : 네, 구현 세부 사항이지만 최신 자바 스크립트 엔진이 코드를 컴파일하기 때문에 할당 될 때마다 함수를 다시 컴파일하지 않습니다. 성능 테스트 결과가 다른 이유는 서로 다른 것을 테스트하기 때문입니다. 내 테스트는 전역 함수를 로컬 함수와 비교하는 반면 테스트는 로컬 함수를 인라인 코드와 비교합니다. 물론 코드를 인라인하는 것이 함수를 호출하는 것보다 빠르며 일반적인 최적화 기술입니다.
Guffa

0

중첩 및 비 중첩 및 함수 표현식과 함수 선언을 테스트하기 위해 jsperf를 만들었는데 중첩 테스트 케이스가 비 중첩보다 20 배 더 빠르게 수행된다는 사실에 놀랐습니다. (나는 그 반대 또는 무시할만한 차이를 예상했습니다).

https://jsperf.com/nested-functions-vs-not-nested-2/1

이것은 Chrome 76, macOS에 있습니다.

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