이 JavaScript 패턴은 무엇이며 왜 사용됩니까?


100

THREE.js를 공부하고 있는데 함수가 다음과 같이 정의되는 패턴을 발견했습니다.

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(예제는 여기에서 raycast 방법을 참조 하십시오 ).

이러한 방법 의 일반적인 변형은 다음과 같습니다.

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

첫 번째 버전을 일반 변형과 비교하면 첫 번째 버전은 다음 과 같은 점이 다릅니다.

  1. 자체 실행 기능의 결과를 할당합니다.
  2. 이 함수 내에서 지역 변수를 정의합니다.
  3. 로컬 변수를 사용하는 논리가 포함 된 실제 함수를 반환합니다 .

따라서 주요 차이점은 첫 번째 변형에서 막대는 초기화시 한 번만 할당되고 두 번째 변형은 호출 될 때마다이 임시 변수를 생성한다는 것입니다.

이것이 사용되는 이유에 대한 나의 가장 좋은 추측은 bar의 인스턴스 수를 제한하고 (하나만 있음) 메모리 관리 오버 헤드를 절약한다는 것입니다.

내 질문 :

  1. 이 가정이 맞습니까?
  2. 이 패턴의 이름이 있습니까?
  3. 이것이 왜 사용됩니까?

1
@ChrisHayes 충분히 공평합니다. 나는 THREE.js 기여자들이 이것에 답할 수있는 가장 자격이 있다고 생각했기 때문에 그것을 THREE.js로 태그를 붙였습니다.하지만 그렇습니다. 이것은 일반적인 JS 질문입니다.
Patrick Klug 2014 년

2
나는 그것이 클로저라고 믿습니다. 그들에 대해 읽을 수 있습니다.
StackFlowed

1
이것이 Bar가 인스턴스화되는 유일한 장소 인 경우 단일 패턴입니다.

8
반드시 메모리를 저장하지만, 호출을 통해 상태를 유지할 수 있습니다
후안 멘데스

2
@wrongAnswer : 정확하지 않습니다. 여기서 익명 함수 (클로저)가 즉시 실행됩니다.
njzk2 2014 년

답변:


100

귀하의 가정은 거의 정확합니다. 먼저 검토해 봅시다.

  1. 자체 실행 기능의 반환을 할당합니다.

이를 즉시 호출 함수 표현식 또는 IIFE라고합니다.

  1. 이 함수 내에서 지역 변수를 정의합니다.

이것은 private키워드 나 기능을 제공하지 않기 때문에 JavaScript에서 개인 개체 필드를 갖는 방법입니다 .

  1. 로컬 변수를 사용하는 논리가 포함 된 실제 함수를 반환합니다.

다시 말하지만, 요점은이 지역 변수가 private이라는 것 입니다.

이 패턴의 이름이 있습니까?

AFAIK이 패턴을 Module Pattern 이라고 부를 수 있습니다 . 인용 :

모듈 패턴은 클로저를 사용하여 "개인 정보 보호", 상태 및 조직을 캡슐화합니다. 퍼블릭 및 프라이빗 메서드와 변수의 혼합을 래핑하는 방법을 제공하여 조각이 전역 범위로 누출되고 실수로 다른 개발자의 인터페이스와 충돌하는 것을 방지합니다. 이 패턴을 사용하면 공개 API 만 반환되고 클로저 내의 다른 모든 것은 비공개로 유지됩니다.

이 두 가지 예를 비교하면 첫 번째 예가 사용되는 이유에 대한 가장 좋은 추측은 다음과 같습니다.

  1. Singleton 디자인 패턴을 구현하고 있습니다.
  2. 첫 번째 예제를 사용하여 특정 유형의 개체를 만드는 방법을 제어 할 수 있습니다. 이 점과 밀접하게 일치하는 하나 는 Effective Java에 설명 된 정적 팩토리 메소드 일 수 있습니다 .
  3. 그것은의 효율적인 같은 객체 상태마다 시간이 필요합니다.

그러나 매번 바닐라 오브젝트 만 필요하다면이 패턴은 아마도 값을 추가하지 않을 것입니다.


1
+1은 Addy Osmani의 책에서 패턴을 정확하게 식별합니다. 이름이 정확합니다. 이것은 실제로 모듈 패턴입니다. 그건 그렇고 드러나는 모듈 패턴입니다.
Benjamin Gruenbaum 2014 년

4
'기본 개인 변수 없음'부분을 제외하고는 귀하의 답변에 동의합니다. 모든 JS 변수는 "비공개"변수보다 더 강력하고 일반적인 메커니즘 인 '바로 사용 가능한'어휘 범위가 지정됩니다 (예 : Java에서 발견됨). 따라서 JS는 모든 변수를 처리하는 특별한 경우로 "개인"변수를 지원합니다.
Warbo 2014 년

"개인 변수"는 개인 "객체 필드"를 의미한다고 생각합니다
Ian Ringrose 2014-09-29


4
@LukaHorvat : 사실 자바 스크립트는 다른 언어보다 "강력한"것이 아닙니다 (표현 적이라는 용어를 선호합니다). 실제로 변수를 보호하는 유일한 방법은 변수 재사용 넌센스를 피하기 위해 변수를 함수에 묶는 것이므로 표현력이 떨어집니다. 모듈 패턴은 좋은 자바 스크립트 코드를 만들기위한 결정적인 필수 조건이지만, 언어의 기능이 아니며 언어의 약점에 물드는 것을 피하기위한 슬픈 해결 방법입니다.
Falanwe 2014 년

11

개체 초기화 비용을 제한하고 추가로 모든 함수 호출이 동일한 개체를 사용하도록 합니다. 예를 들어 향후 호출에서 사용할 수 있도록 상태를 객체에 저장할 수 있습니다.

메모리 사용량을 제한 할 수 있지만 일반적으로 GC는 어쨌든 사용하지 않는 개체를 수집하므로이 패턴은 그다지 도움이되지 않습니다.

이 패턴은 특정 형태의 폐쇄 입니다.


1
JS에서는 일반적으로 '모듈'로 reffers입니다
Lesha Ogonkov

2
나는 그것을 "특정 형태의 폐쇄"라고 부르지 않을 것이다. 클로저 를 사용 하는 패턴입니다 . 패턴의 이름은 여전히 ​​잡을 수 있습니다.
Chris Hayes

4
정말 이름이 필요합니까? 모든 것이 패턴이어야합니까? "모듈 패턴"변종의 끝없는 분류가 정말로 필요합니까? "함수를 반환하는 일부 지역 변수가있는 IIFE"일 수는 없습니까?
Dagg Nabbit 2014 년

3
@DaggNabbit 질문이 "이 패턴은 무엇이라고 부르나요?" 예, 이름이 필요하거나 이름이 없다는 설득력있는 주장이 필요합니다. 게다가 패턴은 이유가 있습니다. 왜 당신이 여기에서 그들을 괴롭히는 지 이해가 안 돼요.
Chris Hayes

4
@ChrisHayes 이름이 필요하다면 왜 이름이 없다고 주장해야합니까? 그건 말이 안 돼. 필요한 경우 하나가 없어야합니다. 나는 패턴에 문제가 없지만 모든 간단한 관용구를 패턴으로 분류 할 필요는 없다고 생각합니다. 그렇게하면 제한된 방식으로 생각하게됩니다 ( "이것이 모듈 패턴입니까? 모듈 패턴을 올바르게 사용하고 있습니까?"대 "함수를 반환하는 일부 지역 변수가있는 IIFE가 있습니다.이 디자인이 저에게 적합합니까?")
Dagg Nabbit 2014 년

8

이 패턴의 이름이 더 정확한지 확실하지 않지만 이것은 나에게 모듈처럼 보이며 사용되는 이유는 캡슐화하고 상태를 유지하기위한 것입니다.

클로저 (함수 내 함수로 식별 됨)는 내부 함수가 외부 함수 내 변수에 액세스 할 수 있도록합니다.

제공 한 예제에서 내부 함수는 foo외부 함수를 실행하여 반환되고에 할당됩니다. 즉 tmpObject, 클로저 내에서 계속 존재하고 내부 함수에 대한 여러 호출이 foo()의 동일한 인스턴스에서 작동합니다 tmpObject.


5

코드와 Three.js 코드의 주요 차이점은 Three.js 코드에서 변수 tmpObject는 한 번만 초기화 된 다음 반환 된 함수를 호출 할 때마다 공유된다는 것입니다.

이것은 staticC와 유사한 언어에서 변수가 사용되는 방식과 유사하게 호출 사이에 일부 상태를 유지하는 데 유용 합니다.

tmpObject 내부 함수에서만 볼 수있는 개인 변수입니다.

메모리 사용량을 변경하지만 메모리를 절약하도록 설계되지 않았습니다.


5

모든 메서드와 변수가 명시 적으로 노출 될 때까지 비공개로 유지되도록하는 공개 모듈 패턴의 개념으로 확장하여이 흥미로운 스레드에 기여하고 싶습니다.

여기에 이미지 설명 입력

후자의 경우 더하기 메서드는 Calculator.add ();


0

제공된 예제에서 첫 번째 스 니펫은 foo () 함수를 호출 할 때마다 동일한 tmpObject 인스턴스를 사용합니다. 두 번째 스 니펫에서와 마찬가지로 tmpObject는 매번 새 인스턴스가됩니다.

첫 번째 조각이 사용되었을 수있는 한 가지 이유는 변수 tmpObject가 foo ()가 선언 된 범위로 값이 유출되지 않고 foo () 호출간에 공유 될 수 있다는 것입니다.

첫 번째 스 니펫의 즉시 실행되지 않는 함수 버전은 실제로 다음과 같습니다.

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

그러나이 버전에는 foo ()와 동일한 범위에 tmpObject가 있으므로 나중에 조작 할 수 있습니다.

동일한 기능을 달성하는 더 좋은 방법은 별도의 모듈을 사용하는 것입니다.

모듈 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

모듈 2 :

var foo = require('./foo');

IEF와 명명 된 foo 생성자 함수의 성능 비교 : http://jsperf.com/ief-vs-named-function


3
'더 나은'예제는 NodeJS에서만 작동하며 더 나은 방법을 설명하지 않았습니다.
Benjamin Gruenbaum 2014 년

별도의 모듈을 만드는 것이 "더 나은"것이 아니라 단지 다릅니다. 특히 상위 함수를 1 차 객체로 축소하는 방법입니다. 1 차 코드는 단계적으로 진행하기가 더 쉬운 경향이 있지만 일반적으로 더 장황하고 중간 결과를 구체화해야합니다.
Warbo 2014 년

@BenjaminGruenbaum 모듈은 Node에만있는 것이 아니라 browserify와 같은 많은 클라이언트 측 모듈 솔루션이 있습니다. 모듈 솔루션이 더 읽기 쉽고 디버그하기 쉬우 며 범위와 위치에 대해 더 명확하기 때문에 "더 나은"모듈 솔루션이라고 생각합니다.
Kory Nunn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.