JavaScript는 본질적으로 캐주얼하지만 브라우저가 점점 더 많은 일을 할 수 있게 되면서 언어는 점점 더 심각해지고 있습니다. 복잡한 논리 하에서 JavaScript는 모듈화되어야 하고 모듈은 캡슐화되어야 하며 외부 호출을 위한 인터페이스만 남겨 두어야 합니다. 클로저(Closure)는 자바스크립트에서 모듈 캡슐화의 핵심이며, 많은 초보자들이 이해하기 어려워하는 포인트이기도 합니다. 처음에는 혼란스러웠습니다. 이제 나는 이 개념을 더 깊이 이해하게 되었다고 확신합니다. 이해를 돕기 위해 기사에서는 비교적 간단한 개체를 캡슐화하려고 시도합니다.
우리는 페이지에서 n 값을 유지하는 카운터 개체 티커를 유지하려고 합니다. 사용자가 작업을 수행하면 카운트를 늘릴 수 있지만(n 값에 1을 더함) n을 줄이거나 n을 직접 변경할 수는 없습니다. 게다가 우리는 때때로 이 값을 쿼리해야 합니다.
문을 활짝 열어놓은 JSON 스타일 모듈화
문을 여는 방법은 다음과 같습니다:
var ticker = {
n:0,
Tick:function( ){
this.n++;
},
};
이러한 작성 방식은 자연스럽고 효과적입니다. 개수를 늘려야 할 경우 쿼리가 필요한 ticketer.tick() 메서드를 호출합니다. 여러 번,ticker.n 변수에 액세스하십시오. 그러나 단점도 분명합니다. 모듈 사용자는 Ticker.n-- 또는 Ticker.n=-1을 호출하는 등 자유롭게 n을 변경할 수 있습니다. 티커를 캡슐화하지 않았습니다. n 및 진드기()는 티커의 "구성원"으로 보이지만 해당 접근성은 티커와 동일하며 전역적입니다(티커가 전역 변수인 경우). 캡슐화 측면에서 이 모듈식 접근 방식은 다음 접근 방식보다 약간 더 우스꽝스럽습니다(일부 간단한 애플리케이션의 경우 이 정도만으로도 충분합니다).
varticer = {};
varticerN = 0;
varticerTick = function(){
tickerN++;
}
tickerTick();
tick()에서 this.n에 액세스한다는 점은 주목할 가치가 있습니다. 이는 n이 Ticker의 멤버이기 때문이 아니라 Tick()을 호출하는 Ticker이기 때문입니다. 실제로, 여기에ticker.n을 작성하는 것이 더 나을 것입니다. 왜냐하면 진드기()가 호출되면 이는 티커가 아니라 다음과 같은 다른 것이기 때문입니다:
var func = Ticker.tick;
func();
이때 실제로는 Tick()을 호출하는 것이 Window인데, 함수가 실행되면 window.n에 접근을 시도하여 오류가 발생하게 됩니다.
실제로 이러한 "개방형" 모듈식 접근 방식은 프로그램보다는 JSON 스타일의 데이터를 구성하는 데 자주 사용됩니다. 예를 들어, 다음 JSON 객체를 Ticker 함수에 전달하여 티커가 100부터 계산을 시작하고 매번 2씩 증가하는지 확인할 수 있습니다.
var config = {
nStart:100,
step:2
}
스코프 체인 및 클로저
다음 코드를 보면 이미 Pass가 구현되어 있습니다. 구성에서 티커를 사용자 정의합니다.
함수 티커(config){
var n = config.nStart;
함수 틱(){
n += config.step;
}
}
console.log(ticker.n); // ->undefine
티커가 왜 객체에서 함수로 바뀌었는지 궁금하실 겁니다. 이는 JavaScript에서는 함수에만 범위가 있고, 함수 내부 변수는 함수 본문 외부에서 액세스할 수 없기 때문입니다. Ticker() 외부에서 Ticker.n에 액세스하면 정의되지 않은 결과가 발생하지만, Tick() 내에서 n에 액세스하면 문제가 없습니다. Tick()에서 Ticker(), 전역에 이르기까지 이는 JavaScript의 "범위 체인"입니다.
하지만 여전히 문제가 있습니다. 바로 Tick()을 어떻게 호출할 것인가 하는 것입니다. Ticker()의 범위는 Tick()도 마스크합니다. 두 가지 해결책이 있습니다:
1) Ticker()의 반환 값으로 n을 증가시키는 메서드를 사용하는 것처럼 반환 값으로 메서드를 호출해야 합니다.
2) 변수를 설정합니다. 외부 범위의 경우, Ticker()에서 getN을 설정했습니다.
var getN;
함수 티커(config){
var n = config.nStart;
getN = function(){
return n;
return function(){
n += config.step;
};
}
tick() ;
console .log(getN()); // ->102
이때 변수 n은 "클로저"에 있으므로 Ticker() 외부에서 직접 액세스할 수 없습니다. 하지만 관찰하는 방법에는 두 가지가 있습니다. 아니면 조작해 보세요.
그런데 아직도 뭔가 잘못된 것 같다고요? 동일한 기능을 가진 두 개의 객체인 Ticker1과 Ticker2를 유지해야 하는 경우 어떻게 해야 합니까? Ticker()는 하나뿐이라 다시 쓸 수는 없겠죠?
new 연산자와 생성자
new 연산자를 통해 함수를 호출하면 새로운 개체가 생성되고 해당 개체를 사용하여 함수가 호출됩니다. 제가 이해한 바에 따르면, 다음 코드에서 t1과 t2의 구성 과정은 동일합니다.
function myClass(){}
var t1 = new myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = undefound;
t1과 t2는 모두 새로 생성된 객체이고 myClass()는 생성자입니다. 마찬가지로,ticker()는 다음과 같이 다시 작성할 수 있습니다.
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function(){
n += config.step;
}
}
var ticket1 = new TICKER({nStart:100,step:2});
ticker1 .tick();
console.log(ticker1.getN()); // ->102
var ticket2 = new TICKER({nStart:20,step:3});
ticker2. 진드기();
ticker2.tick();
console.log(ticker2.getN()); // ->26
일반적으로 생성자는 대문자로 표시됩니다. TICKER()는 여전히 순수 객체가 아닌 함수입니다(우리가 "순수"라고 말하는 이유는 함수가 실제로 객체이고 TICKER()가 함수 객체이기 때문입니다). 클로저는 여전히 유효하며 Ticker1에 액세스할 수 없습니다. .N .
프로토타입 프로토타입과 상속
위의 TICKER()에는 여전히 결함이 있습니다. 즉,ticer1.tick()과ticker2.tick()은 서로 독립적입니다! new 연산자를 사용하여 TICKER()를 호출할 때마다 새 객체가 생성되고 이 새 객체에 바인딩하기 위한 새 함수가 생성됩니다. 새 객체가 생성될 때마다 브라우저는 space.tick() 자체와 변수를 Tick() 내에 저장하는 것은 우리가 기대하는 것과 다릅니다. 우리는 Ticker1.tick과 Ticker2.tick이 동일한 함수 객체를 가리킬 것으로 예상합니다.
이를 위해서는 프로토타입 도입이 필요합니다.
JavaScript에서는 Object 객체를 제외한 다른 객체에는 다른 객체를 가리키는 프로토타입 속성이 있습니다. 이 "다른 객체"는 여전히 프로토타입 객체를 갖고 있으며 최종적으로 Object 객체를 가리키는 프로토타입 체인을 형성합니다. 객체에 대해 메서드를 호출할 때 객체에 지정된 메서드가 없는 것으로 확인되면 Object 객체가 나올 때까지 프로토타입 체인에서 이 메서드를 검색합니다.
함수도 객체이므로 함수에도 프로토타입 객체가 있습니다. 함수가 선언되면(즉, 함수 객체가 정의되면) 새 객체가 생성되고 이 객체의 프로토타입 속성은 Object 객체를 가리키며 이 객체의 constructor 속성은 함수 객체를 가리킵니다.
생성자를 통해 생성된 새 객체의 프로토타입은 생성자의 프로토타입 객체를 가리킵니다. 따라서 생성자의 프로토타입 객체에 함수를 추가할 수 있으며 이러한 함수는 Ticker1이나 Ticker2에 의존하지 않고 TICKER에 의존합니다.
다음과 같이 할 수 있습니다:
function TICKER(config){
var n = config.nStart;
}
TICKER.prototype.getN = function{
// 주의: 잘못된 구현
return n;
};
TICKER.prototype.tick = function{
// 주의: 잘못된 구현
n += config.step;
};
이것은 잘못된 구현입니다. 프로토타입 객체의 메서드는 클로저의 내용, 즉 변수 n에 접근할 수 없기 때문입니다. TICK() 메서드가 실행된 후에는 n에 더 이상 액세스할 수 없으며 브라우저는 n을 삭제합니다. 클로저의 콘텐츠에 액세스하려면 객체에 클로저의 콘텐츠에 액세스할 수 있는 간결한 인스턴스 종속 메서드가 있어야 하며 그런 다음 프로토타입에 복잡한 공용 메서드를 정의하여 논리를 구현해야 합니다. 실제로 예제의 Tick() 메서드는 충분히 간결하므로 TICKER에 다시 넣어 보겠습니다. 다음으로 호출자가 Tick()이 호출되는 횟수를 지정할 수 있도록 하는 더 복잡한 메서드인 TickTimes()를 구현합니다.
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function( ){
n += config.step;
};
}
TICKER.prototype.tickTimes = function(n){
while(n>0){
이. 진드기();
n--;
}
};
var ticket1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticket2 = new TICKER({nStart:20,step:3});
ticker2.tickTimes(2);
console.log(ticker2.getN()) // ->26
이 TICKER는 매우 좋습니다. n을 캡슐화하고 객체 외부에서 직접 변경할 수 없으며 복잡한 함수인 TickTimes()가 프로토타입에 정의되어 있습니다. 이 함수는 인스턴스의 작은 함수를 호출하여 객체의 데이터에 대해 작동합니다.
따라서 객체의 캡슐화를 유지하기 위해 제가 제안하는 것은 데이터 작업을 가능한 가장 작은 단위 함수로 분리하고 생성자에서 이를 인스턴스 종속으로 정의하는 것입니다(여러 곳에서 호출되기도 함). "private")이고 복잡한 로직이 프로토타입(즉, "public")에 구현됩니다.
마지막으로 상속에 대해 이야기해보겠습니다. 사실, 프로토타입에 함수를 정의할 때 이미 상속을 사용하고 있습니다! JavaScript의 상속은 C++보다 더...음...간단하거나 조잡합니다. C++에서는 동물을 나타내는 동물 클래스를 정의한 다음 새를 나타내는 동물 클래스를 상속하는 새 클래스를 정의할 수 있지만 제가 논의하고 싶은 것은 그러한 상속이 아닙니다(이러한 상속은 JavaScript에서도 구현될 수 있지만). ); C++에서 논의된 상속은 동물 클래스를 정의한 다음 myAnimal 객체를 인스턴스화하는 것입니다. 예, C++에서는 인스턴스화이지만 JavaScript에서는 상속으로 처리됩니다.
JavaScript는 클래스를 지원하지 않습니다. 브라우저는 현재 사용 가능한 객체에만 관심을 갖고 이러한 객체가 어떤 클래스인지, 어떤 구조를 가져야 하는지 걱정하지 않습니다. 이 예에서 TICKER()는 함수 개체입니다. 그러나 현재 new 연산자를 통해 TiCKER1과 Ticker2라는 두 개체가 있으므로 값을 할당하고(TICKER=1) 삭제할 수 있습니다. 호출되면 TICKER()가 생성자 역할을 하고 TICKER.prototype 객체가 클래스 역할을 합니다.