JavaScript 함수 선언 및 평가 순서


80

이 예제 중 첫 번째 예제는 작동하지 않지만 다른 모든 예제는 작동하는 이유는 무엇입니까?

// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();

// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();

// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();

// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();

답변:


182

이것은 범위 문제도 종료 문제도 아닙니다. 문제는 선언표현 사이의 이해에 있습니다.

Netscape의 첫 번째 버전의 JavaScript와 Microsoft의 첫 번째 사본조차도 JavaScript 코드는 두 단계로 처리됩니다.

1 단계 : 컴파일-이 단계에서는 코드가 구문 트리 (및 엔진에 따라 바이트 코드 또는 바이너리)로 컴파일됩니다.

2 단계 : 실행-파싱 된 코드가 해석됩니다.

함수 선언 구문 은 다음과 같습니다.

function name (arguments) {code}

인수는 물론 선택 사항입니다 (코드도 선택 사항이지만 그 요점은 무엇입니까?).

그러나 JavaScript를 사용하면 표현식을 사용하여 함수를 만들 수도 있습니다 . 함수 표현식의 구문은 표현식 컨텍스트로 작성된다는 점을 제외하면 함수 선언과 유사합니다. 그리고 표현은 다음과 같습니다.

  1. =기호 (또는 :객체 리터럴) 오른쪽에있는 모든 것 .
  2. 괄호 안의 모든 것 ().
  3. 함수에 대한 매개 변수 (실제로 이미 2에서 다룹니다).

선언 과 다른 식은 컴파일 단계가 아닌 실행 단계에서 처리됩니다. 그리고이 때문에 표현의 순서가 중요합니다.

따라서 명확히하기 위해 :


// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();

1 단계 : 컴파일. 컴파일러는 변수 someFunction가 정의되어 있음을 확인하여 생성합니다. 기본적으로 생성 된 모든 변수는 undefined 값을 갖습니다. 컴파일러는 아직 값을 할당 할 수 없습니다. 값은 할당 할 값을 반환하기 위해 일부 코드를 실행하기 위해 인터프리터가 필요할 수 있기 때문입니다. 그리고이 단계에서 우리는 아직 코드를 실행하지 않습니다.

2 단계 : 실행. 인터프리터는 변수 someFunction를 setTimeout 에 전달하려는 것을 확인합니다 . 그리고 그렇습니다. 불행히도의 현재 값 someFunction은 정의되지 않았습니다.


// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();

1 단계 : 컴파일. 컴파일러는 someFunction이라는 이름으로 함수를 선언하고 있음을 확인하고이를 생성합니다.

2 단계 : 인터프리터는 사용자 someFunction가 setTimeout 에 전달하려는 것을 확인합니다 . 그리고 그렇습니다. 의 현재 값은 someFunction컴파일 된 함수 선언입니다.


// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();

1 단계 : 컴파일. 컴파일러는 변수를 선언 한 것을보고 someFunction생성합니다. 이전과 마찬가지로 그 값은 정의되지 않았습니다.

2 단계 : 실행. 인터프리터는 익명 함수를 setTimeout에 전달하여 나중에 실행합니다. 이 함수에서 변수를 사용하고 있음 someFunction을 확인하여 변수에 대한 클로저를 만듭니다. 이 시점에서의 값 someFunction은 아직 정의되지 않았습니다. 그런 다음에 기능을 할당하는 것을 확인합니다 someFunction. 이 시점에서의 값 someFunction은 더 이상 정의되지 않습니다. 1/100 초 후에 setTimeout이 트리거되고 someFunction이 호출됩니다. 그 값이 더 이상 정의되지 않았기 때문에 작동합니다.


케이스 4는 실제로 케이스 3의 비트가 던져진 케이스 2의 또 다른 버전입니다.이 시점 someFunction에서 setTimeout에 전달되어 이미 선언되어 있기 때문에 존재합니다.


추가 설명 :

setTimeout(someFunction, 10)someFunction의 로컬 복사본과 setTimeout에 전달 된 복사본 사이에 클로저가 생성되지 않는 이유 가 궁금 할 수 있습니다 . 이에 대한 대답은 JavaScript의 함수 인수가 항상 숫자 나 문자열 인 경우 항상 값으로 전달되거나 다른 모든 항목에 대한 참조로 전달된다는 것입니다. 따라서 setTimeout은 실제로 변수 someFunction이 전달되는 것이 아니라 (이는 클로저가 생성됨을 의미 함) someFunction이 참조하는 객체 (이 경우에는 함수) 만 가져옵니다. 이것은 폐쇄를 차단하기 위해 JavaScript에서 가장 널리 사용되는 메커니즘입니다 (예 : 루프).


7
정말 대단한 대답이었습니다.
Matt Briggs

1
@Matt : 나는 이것을 다른 곳에서 (여러 번) 설명했습니다. 내가 가장 좋아하는 설명 : stackoverflow.com/questions/3572480/…
slebetman


3
@Matt : 기술적으로 클로저는 범위가 아니라 스택 프레임 (활성화 레코드라고도 함)을 포함합니다. 클로저는 스택 프레임간에 공유되는 변수입니다. 스택 프레임은 객체의 범위를 분류하는 것입니다. 즉, 범위는 프로그래머가 코드 구조에서 인식하는 것입니다. 스택 프레임은 메모리에서 런타임에 생성되는 것입니다. 정말 그런 건 아니지만 충분히 가까워요. 런타임 동작에 대해 생각할 때 범위 기반 이해만으로는 충분하지 않을 수 있습니다.
slebetman

3
@slebetman 예제 3에 대한 설명을 위해 setTimeout 내의 익명 함수가 someFunction 변수에 대한 클로저를 생성하고이 시점에서 someFunction이 아직 정의되지 않았다고 언급했습니다. 예제 3이 undefined를 반환하지 않는 유일한 이유는 setTimeout 함수 때문인 것 같습니다 (10 밀리 초의 지연으로 JavaScript가 someFunction에 대한 다음 할당 문을 실행할 수 있으므로 정의 됨).
wmock

2

Javascript의 범위는 엄격하게 어휘 범위가 아닌 함수 기반입니다. 그 의미

  • Somefunction1은 둘러싸는 함수의 시작부터 정의되지만 할당 될 때까지 내용은 정의되지 않습니다.

  • 두 번째 예에서 할당은 선언의 일부이므로 맨 위로 '이동'합니다.

  • 세 번째 예제에서 변수는 익명 내부 클로저가 정의 될 때 존재하지만 10 초 후 값이 할당 될 때까지 사용되지 않습니다.

  • 네 번째 예에는 두 번째와 세 번째 이유가 모두 있습니다.


1

때문에 someFunction1아직 시간에 전화를 할당되지 않은하기 위해 setTimeout()실행됩니다.

someFunction3은 비슷한 경우처럼 보일 수 있지만 이 경우 에는 함수 래핑 someFunction3()을 전달하므로 setTimeout()에 대한 호출 someFunction3()은 나중에 평가되지 않습니다.


그러나 someFunction2호출 setTimeout()이 실행될 때 아직 할당되지 않았습니다 ...?
우리는 모든 모니카 있습니까

1
@jnylen : function키워드 로 함수를 선언하는 것은 변수에 익명 함수를 할당하는 것과 정확히 동일하지 않습니다. 로 선언 된 함수는 function foo()현재 범위의 시작 부분에 "게재"되는 반면 변수 할당은 작성된 지점에서 발생합니다.
Chuck

1
특별한 기능에 +1. 그러나 그것이 작동 할 있다고해서 그것이 이루어져야한다는 것을 의미하지는 않습니다. 사용하기 전에 항상 선언하십시오.
mway 2010 년

@mway : 제 경우에는 "클래스"내의 코드를 개인 변수, 이벤트 핸들러, 개인 함수, 공용 함수의 섹션으로 구성했습니다. 내 개인 함수 중 하나를 호출하려면 이벤트 처리기 중 하나가 필요합니다. 나에게 이런 식으로 코드를 구성하는 것이 어휘 적으로 선언을 정렬하는 것보다 유리합니다.
우리는 모든 모니카 있습니까

1

이것은 문제를 피하기 위해 좋은 절차를 따르는 기본적인 경우처럼 들립니다. 변수와 함수를 사용하기 전에 선언하고 다음과 같이 함수를 선언하십시오.

function name (arguments) {code}

var로 선언하지 마십시오. 이것은 엉성하고 문제로 이어집니다. 사용하기 전에 모든 것을 선언하는 습관을 들이면 대부분의 문제는 급히 사라질 것입니다. 변수를 선언 할 때 정의되지 않은 변수가 없도록 즉시 유효한 값으로 초기화합니다. 또한 함수가 사용하기 전에 전역 변수의 유효한 값을 확인하는 코드를 포함하는 경향이 있습니다. 이는 오류에 대한 추가 보호 수단입니다.

이 모든 것이 어떻게 작동하는지에 대한 기술적 세부 사항은 수류탄을 가지고 놀 때 작동하는 방식에 대한 물리학과 같습니다. 제 간단한 조언은 애초에 수류탄을 가지고 놀지 말라는 것입니다.

코드 시작 부분에있는 몇 가지 간단한 선언으로 대부분의 이러한 종류의 문제를 해결할 수 있지만 코드를 일부 정리해야 할 수도 있습니다.

추가 참고 사항 :
몇 가지 실험을 실행 한 결과 여기에 설명 된 방식으로 모든 함수를 선언하면 순서가 무엇인지는 중요하지 않습니다. 함수 A가 함수 B를 사용하는 경우 함수 B는 그럴 필요가 없습니다. 함수 A 앞에 선언되어야합니다.

따라서 먼저 모든 함수를 선언하고 다음으로 전역 변수를 선언 한 다음 다른 코드를 마지막에 넣으십시오. 이 경험 법칙을 따르고 잘못 갈 수 없습니다. 이러한 규칙의 시행을 보장하기 위해 웹 페이지 헤드에 선언을 배치하고 본문에 다른 코드를 배치하는 것이 가장 좋습니다.

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