목차 읽기
클로저란 무엇인가요?
클로저의 특성
클로저의 기능:
클로저의 코드 예
참고
요약
클로저 자바스크립트의 중요한 개념이자, 일상 업무에서 자주 사용되는 기술이기도 합니다. 간단히 요약하자면
클로저란 무엇인가요?
공식 성명:
클로저는 다른 함수의 범위에 있는 변수에 접근할 수 있는 함수를 의미합니다. 클로저를 만드는 일반적인 방법은 함수 내부에 다른 함수를 만들고 다른 함수를 통해 이 함수의 지역 변수에 액세스하는 것입니다.
다음은 간단한 클로저입니다.
function A(){ var text="hello world"; function B(){ console.log(text); } return B; } var c=A(); c(); // hello world
문자 그대로의 의미는 다음과 같습니다. 함수 B는 함수 A의 범위에 있는 변수(텍스트)에 액세스할 수 있는 권한을 가지며, 다른 함수 C를 통해 이 함수의 로컬 변수 텍스트에 액세스할 수 있습니다. 따라서 함수 B는 클로저를 형성합니다. C는 실제로 함수 B를 실행하므로 C는 클로저라고 말할 수도 있습니다.
A()를 직접 실행하면 아무런 반응이 없다는 점에 유의하세요. return B는 return B()가 아니면 실행되지 않기 때문에;
클로저의 특징
클로저는 세 가지 특징을 가지고 있습니다:
1. 함수 중첩 함수
2. 외부 매개변수와 변수는 함수 내부에서 참조될 수 있습니다
3. 매개변수와 변수는 가비지 수집 메커니즘에 의해 재활용되지 않습니다
3번 항목에서 클로저와 Will의 매개변수가 사용되는 이유를 설명하세요. 변수는 가비지 수집 메커니즘에 의해 재활용되지 않습니까?
우선 자바스크립트의 가비지 컬렉션 원리를 이해해 봅시다:
(1) 자바스크립트에서는 객체가 더 이상 참조되지 않으면 해당 객체는 GC(가비지 컬렉션)가 됩니다. ) 재활용
(2) 두 개체가 서로를 참조하고 더 이상 제3자가 참조하지 않는 경우 서로를 참조하는 두 개체도 재활용됩니다.
위 예제 코드에서 A는 B의 상위 함수이고 B는 전역 변수 C에 할당됩니다(전역 변수의 수명 주기는 브라우저가 페이지를 언로드할 때까지 끝나지 않습니다). B는 항상 메모리에 있고 B의 존재는 A에 따라 달라지므로 A는 항상 메모리에 있으며 호출이 완료된 후 가비지 수집 메커니즘(가비지 수집)에 의해 재활용되지 않습니다.
클로저의 역할:
실제로 클로저의 특성에 따라 클로저의 역할도 결정됩니다. 위의 클로저 특성에 따르면 클로저의 역할은 다음과 같습니다.
1. 환경 오염을 피하기 위해 전역 변수를 정의하는 대신 함수 내부에서 변수를 읽을 수 있습니다.
2. 이러한 변수의 값을 메모리에 보관하세요.
클로저의 코드 예
다음은 주로 몇 가지 일반적인 클로저를 소개하고 분석합니다.
demo1 지역 변수의 축적.
function countFn(){ var count=1; return function(){ //函数嵌套函数 count++; console.log(count); } } var y = countFn(); //外部函数赋给变量y; y(); //2 //y函数调用一次,结果为2,相当于countFn()() y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加 y=null; //垃圾回收,释放内存 y(); // y is not a function
첫 번째 실행이 완료된 이후 변수 개수는 계속 메모리에 저장되어 재활용되지 않으므로 두 번째 실행 시 마지막 값이 누적될 수 있습니다. . y=null이 도입되면 참조가 삭제되고 메모리가 해제됩니다.
demo2는 루프에서 클로저를 사용합니다
코드는 다음과 같습니다(아래 세 가지 코드 예). 각 루프에 클로저 사용 호출 루프 시퀀스 번호:
demo2-1
for (var i = 0; i < 10; i++) { var a = function(){ console.log(i) } a() //依次为0--9 }
이 예제의 결과는 정확하며 0-9를 인쇄했습니다
순서대로 각 레벨의 익명 함수와 변수가 클로저를 형성하지만 루프 본문에서 함수가 즉시 실행되므로 루프에 문제가 없습니다.
demo2-2
하지만 setTimeout에서는 다릅니다
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); //10次10 }, 1000); }
우리가 기대하는 것은 0~10을 차례로 출력하는 것이지만 실제 상황은 10 곱하기 10을 출력하는 것입니다. setTimeout 시간을 0으로 변경해도 10 10초가 출력됩니다. 왜 이런가요?
이것은 setTimeout 메커니즘 때문입니다. setTimeout은 작업 대기열이 끝날 때 타이밍을 시작합니다. 끝나지 않은 프로세스가 앞에 있으면 타이밍을 시작하기 전에 끝날 때까지 기다립니다. 여기서 작업 대기열은 자체 루프입니다.
setTimeout은 루프가 끝날 때까지 타이밍을 시작하지 않으므로 무슨 일이 있어도 setTimeout의 i는 마지막 루프의 i입니다. 이 코드에서는 마지막 i가 10이므로 10개의 10이 인쇄됩니다.
이것이 setTimeout의 콜백이 반복할 때마다 값을 가져오는 것이 아니라 마지막 값을 가져오는 이유입니다.
데모2-3
위 setTimeout에서 루프를 순서대로 출력하지 못하는 문제 해결
for(var i=0;i<10;i++){ var a=function(e){ return function(){ console.log(e); //依次输入0--9 } } setTimeout(a(i),0); }
setTimeout의 첫 번째 매개변수에 함수가 필요하므로 함수를 반환합니다. 반환할 때 i를 매개변수로 전달하고 형식 매개변수 e를 통해 i를 캐시합니다. 즉, e 변수는 i의 복사본과 동일하며 반환된 함수로 가져옵니다.
setTimeout이 실행되면 e에 대한 참조가 있으며 이 값은 루프에 의해 변경되지 않습니다.
위와 유사한 다음 작성 방법을 사용할 수도 있습니다.
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); //依次打印出0-9 }, 0); })(i); }
demo3 루프에 이벤트 추가
일반적인 데모 보기
li를 클릭할 때마다 li의 인덱스 값이 알림을 받기를 바라며 다음 코드를 사용합니다.
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes = document.getElementsByTagName("li"); for(i = 0,len=nodes.length;i<len;i++){ nodes[i].onclick = function(){ alert(i); //值全是4 }; }
역효과, 아니 어떤 li를 클릭하더라도 경고 루프가 끝난 후의 인덱스 값인 경고(4)가 발생합니다. 왜 이런가요?
이벤트가 루프의 다른 요소에 바인딩되기 때문입니다. 이벤트 콜백 함수에서 루프와 관련된 변수가 호출되면 이 변수가 루프의 마지막 값을 가져옵니다.
由于绑定的回调函数是一个匿名函数,所以上面的代码中, 这个匿名函数是一个闭包,携带的作用域为外层作用域(也就是for里面的作用域),当事件触发的时候,作用域中的变量已经随着循环走到最后了。
还有一点就是,事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。
要实现点击li,alert出li的索引值,需要将上面的代码进行以下的修改:
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes=document.getElementsByTagName("li"); for(var i=0;i<nodes.length;i++){ (function(e){ nodes[i].onclick=function(){ alert(e); }; })(i) }
解决思路: 增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标)。
当立即执行函数执行的时候,e 值不会被销毁,因为它的里面有个匿名函数(也可以说是因为闭包的存在,所以变量不会被销毁)。执行后,e 值 与全局变量 i 的联系就切断了,
也就是说,执行的时候,传进的 i 是多少,立即执行函数的 e 就是多少,但是 e 值不会消失,因为匿名函数的存在。
也可以用下面的解法,原理是一样的:
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes=document.getElementsByTagName('li'); for(var i = 0; i<nodes.length;i++){ (function(){ var temp = i; nodes[i].onclick = function () { alert(temp); } })(); }
注意事项
1、造成内存泄露
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以只有在绝对必要时再考虑使用闭包。
2、在闭包中使用this也可能会导致一些问题。
其实我们的目的是想alert出object里面的name
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ return function(){ return this.name; } } } alert(object.getNameFunc()()); // The Window
因为在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。
每个函数在被调用时,都会自动取的两个特殊变量:this和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止。也就是说,里面的return function只会搜索
到全局的this就停止继续搜索了。因为它永远不可能直接访问外部函数中的这两个变量。
稍作修改,把外部作用域中的this对象保存在一个闭包能够访问的变量里。这样就可以让闭包访问该对象了。
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ var that=this; return function(){ return that.name; } } } alert(object.getNameFunc()()); // My Object
我们把this对象赋值给了that变量。定义了闭包之后闭包也可以访问这个变量。因此,即使在函数返回之后,that也仍引用这object,所以调用object.getNameFunc()()就返回 “My Object”了。
总结
当在函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量。
闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域。
当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。
使用闭包必须维护额外的作用域,所有过度使用它们可能会占用大量的内存
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!
更多理解javascript中的闭包相关文章请关注PHP中文网!