Javascript에서 함수 범위, 객체 호출 및 클로저 간의 관계는 매우 미묘합니다. 이에 대한 기사는 많이 있었지만 왜 많은 초보자가 이를 이해하는 데 어려움을 겪는지 모르겠습니다. 나는 더 대중적인 언어로 내 자신의 이해를 표현하려고 노력할 것입니다.
범위 범위
자바스크립트의 함수는 어휘 범위에 속합니다. 즉, 함수가 실행될 때가 아니라 정의된 범위에서 함수가 실행된다는 의미입니다. 이것이 코뿔소 책에 나오는 내용입니다. 그러나 어떤 사람들은 "정의될 때"와 "실행될 때(호출될 때)"라는 두 가지에 대해 혼동합니다. 간단히 말하면, 함수 A가 "정의"되면 함수 A(){}입니다. 명령문이 실행되면 함수가 정의되는 경우이고, A가 호출되는 경우는 명령문 A(입니다. )이 실행됩니다. 이 두 개념은 명확하게 구별되어야 합니다.
어휘적 범위(이하 별도로 지정하지 않는 한 '범위'라고 함)란 정확히 무엇인가요? 직설적으로 말하면 "scope"이고, 스코프(scope)는 영어로 범위(scope)를 의미합니다. 함수의 범위는 함수가 정의된 시점의 "범위"입니다. 즉, 외부 "범위"에는 외부 변수 속성이 포함됩니다. 이 "범위"는 함수의 내부 상태로 설정됩니다. 전역 함수가 정의되면 전역 함수(함수의 외부 계층)의 "범위"가 전역 함수의 내부 상태로 설정됩니다. 중첩 함수가 정의되면 중첩 함수(외부 함수)의 "범위"가 중첩 함수의 내부 상태로 설정됩니다. 이 "내부 상태"는 실제로 범위 체인으로 이해될 수 있습니다. 아래를 참조하세요.
위의 설명에 따르면 함수의 범위는 함수가 정의된 "범위"입니다. 그러면 Javascript의 함수 범위는 함수가 정의될 때 결정되므로 정적 범위입니다. 정적 범위라고도 합니다.
호출 개체
함수의 호출 개체는 동적이며 함수가 호출될 때 인스턴스화됩니다. 우리는 함수가 정의되면 범위 체인이 결정된다는 것을 이미 알고 있습니다. Javascript 인터프리터가 함수를 호출하면 범위 체인 앞에 새 개체(호출 개체)가 추가됩니다. 호출 객체의 속성은 함수의 실제 매개변수인 함수의 Arguments 객체를 참조하는 인수라는 속성으로 초기화됩니다. var 문으로 선언된 모든 지역 변수도 이 호출 개체에 정의됩니다. 이때 호출 객체는 스코프 체인의 선두에 있고 지역 변수, 함수 형식 매개변수, Arguments 객체는 모두 이 함수의 스코프 내에 있습니다. 물론 이때 지역 변수, 함수 형식 매개변수, Arguments 객체는 스코프 체인에서 같은 이름의 속성을 덮어씁니다.
범위, 범위 체인 및 호출 개체 간의 관계
내가 이해한 바에 따르면 범위는 추상적이고 호출 개체는 인스턴스화됩니다.
실제로 외부 함수가 실행될 때 함수가 정의되면 함수가 호출될 때 실제로 외부 함수의 호출 개체 체인이 결정되는 범위 체인은 범위를 기반으로 합니다. 정의 시 결정된 체인(외부 함수의 호출 개체 체인)과 인스턴스화된 호출 개체. 따라서 함수의 범위 체인은 실제로 호출 개체 체인입니다. 함수가 호출되면 해당 범위 체인(또는 호출 개체 체인)은 실제로 함수가 정의될 때 결정된 범위 체인의 상위 집합입니다.
그들 사이의 관계는 범위? 범위 체인? 호출 개체로 표현될 수 있습니다.
너무 복잡합니다. 예를 들어 보겠습니다.
function f(x) {
var g = function () { return x; }
return g
}
var g1 = f(1); g1()) ; //출력 1
전역 상황을 다음과 유사한 대규모 익명 함수로 간주한다고 가정합니다.
(function() {
//전역 범위입니다
} )();
그러면 예제는 다음과 같습니다:
(function() {
function f(x) {
var g = function () { return x; }
return g;
}
var g1 = f(1);
alert(g1()) //출력 1
})();
전역 대형 익명 함수를 정의하면 외부 레이어가 없으므로 범위 체인이 비어 있습니다.
전역 대형 익명 함수가 직접 실행되며 전역 스코프 체인에는 '전역 호출 개체'가 하나만 있습니다.
함수 f가 정의됩니다. 이때 함수 f의 범위 체인은 외부 범위 체인, 즉 '전역 호출 개체'입니다.
함수 f(1)이 실행되고, 그 스코프 체인은 새로운 f(1) 호출 객체와 함수 f가 정의되었을 때의 스코프 체인, 즉 'f(1) 호출 객체 -> 전역 호출 객체'입니다. .
함수 g(g1으로 반환됩니다. 이름을 g1로 지정하겠습니다)는 f(1)에 정의되어 있으며 해당 범위 체인은 외부 함수 f(1)의 범위 체인입니다. 즉, ' f(1 ) 호출 객체 -> 전역 호출 객체'.
함수 f(1)은 함수 g의 정의를 g1에 반환합니다.
함수 g1이 실행되고, 그 범위 체인은 새로운 g(1) 호출 객체에 외부 f(1)의 범위 체인을 더한 것입니다. 즉, 'g1 호출 객체->f(1) 호출 객체- >글로벌 호출 객체'.
이렇게 보면 아주 명확해집니다.
Closuer 클로저를 간단히 말하면 중첩 함수 외부에서 중첩 함수를 호출하면 클로저가 형성된다는 것입니다.
이전 예는 실제로 클로저입니다. g1은 f(1) 내부에 정의되어 있지만 f(1)이 반환된 후에 실행됩니다. 클로저의 효과 중 하나는 중첩 함수 f가 반환된 후 내부 리소스가 해제되지 않는다는 것입니다. g 함수가 외부에서 호출되면 g는 f의 내부 변수에 액세스할 수 있습니다. 이 기능을 바탕으로 우아한 코드를 많이 작성할 수 있습니다.
예를 들어 페이지에 통합 카운터를 만들고 싶은 경우 클로저를 사용하면 다음과 같이 작성할 수 있습니다.
var counter = (function() {
var i = 0;
var fns = {"get": function( ) {return i;},
"inc": function() {return i;}};
return fns;
})(); inc();
//다른 작업 수행
counter.inc();
var c_value = counter.get(); //이제 c_value는 2입니다.
방식으로, 하나는 메모리 변수 i에 유지되며, i의 값은 카운터의 두 가지 작업을 통해서만 전체 프로그램의 다른 곳에서 직접 조작될 수 없습니다.
setTimeout(fn, 지연) 중에는 함수 핸들 fn에 매개변수를 전달할 수 없지만 클로저 메소드를 통해 필요한 매개변수를 fn 내부에 바인딩할 수 있습니다.
for(var i=0,delay= 1000; isetTimeout(function() {
console.log('i:' i " 지연:" 지연);
}
이렇게 하면 인쇄된 값이 모두
i:5 지연:6000
i:5 지연:6000
i:5 지연:6000이 됩니다.
i:5 지연:6000
i:5 지연:6000
대신 클로저를 사용하면 전달할 매개변수를 쉽게 바인딩할 수 있습니다.
복사 code
코드는 다음과 같습니다: for(var i=0, Delay=1000; i < 5; i , Delay = 1000) {
(function(a , _delay) {
setTimeout(function() {
console.log('i:' a " Delay:" _delay);
}, _delay);
}) (i, 지연)
}
출력:
i:0 지연:1000
i:1 지연:2000
i:2 지연:3000
i:3 지연:4000
i:4 지연:5000
클로저는 이벤트 콜백 함수를 바인딩할 때도 일반적으로 사용됩니다. 같은 이유로 바인딩된 함수 핸들은 매개변수로 사용할 수 없지만 매개변수는 클로저 형태로 바인딩될 수 있습니다.
요약
함수의 어휘 범위와 범위 체인은 서로 다릅니다. 어휘 범위는 추상적인 개념이고 범위 체인은 인스턴스화된 호출 개체의 체인입니다.
함수가 정의되면 외부 함수가 실행될 때도 마찬가지입니다. 함수의 어휘 범위는 정의할 때 결정되지만 여전히 추상적인 개념이므로 인스턴스화할 수 없고 인스턴스화할 수도 없습니다.
함수가 정의되면 인스턴스화되는 외부 함수의 범위 체인도 결정됩니다.
함수를 여러 번 호출하면 범위 체인이 달라집니다.
폐쇄는 강력합니다. Rhinoceros 책이 맞습니다. 이러한 내용을 이해한다면 스스로를 고급 Javascript 프로그래머라고 부를 수 있습니다. 이러한 개념을 잘 활용하면 Javascript의 다양한 디자인 패턴을 다룰 수 있기 때문입니다.