클로저 - 모든 곳에서
프론트 엔드 프로그래밍에서는 클로저를 사용하는 것이 매우 일반적입니다. 의도적이든 무의식적이든, 직접적이든 간접적이든 클로저를 사용하는 경우가 많습니다. 클로저는 데이터 전송을 보다 유연하게 만들 수 있습니다(예: 일부 클릭 이벤트 처리)
!function() { var localData = "localData here"; document.addEventListener('click', //处理点击事件时用到了外部局部变量,比如这里的localData function(){ console.log(localData); }); }();
또 다른 예는 다음과 같습니다. (매우 친숙하지 않나요~~)
!function() { var localData = "localData here"; var url = "http://www.baidu.com/"; $.ajax({ url : url, success : function() { // do sth... console.log(localData); } }); }();
자 또 다른 예를 보세요~~이런 상황이 우리가 보통 클로저라고 부르는 상황입니다
function outer() { var localVal = 30; return function(){ return localVal; } } var func = outer(); func(); // 30
이 예에서는 외부()가 호출되어 익명 함수 function()을 반환합니다. external()의 localVal에 접근할 수 있으며, external() 호출이 완료된 후 func()를 다시 호출하면 external()의 로컬 변수 localVal에 계속 접근할 수 있습니다
클로저의 개념
클로저는 일반 클로저와 다릅니다. 직접적인 어휘 범위 외부에서 호출될 때 함수가 지역 변수가 아닌 변수에 계속 액세스할 수 있도록 허용하는 함수입니다. --Wikipedia
클로저는 다른 함수의 내부 변수를 읽을 수 있는 함수입니다. --Ruan Yifeng
Javascript 언어에서는 함수 내부의 하위 함수만 지역 변수를 읽을 수 있으므로 클로저는 간단히 "함수 내부에 정의된 함수"로 이해하면 됩니다.
그래서 클로저란 본질적으로 함수 내부와 함수 외부를 연결하는 다리입니다.
클로저의 목적
이 부분은 이 블로그에서 재현했습니다. post
클로저는 다양한 곳에서 사용될 수 있습니다. 가장 큰 용도는 두 가지입니다. 하나는 앞서 언급한 것처럼 함수 내부의 변수를 읽는 것이고, 다른 하나는 이러한 변수의 값을 메모리에 유지하는 것입니다.
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
이 코드에서 결과는 실제로 클로저 f2 함수입니다. 두 번 실행했는데, 처음에는 값이 999였고, 두 번째에는 값이 1000이었습니다. 이는 함수 f1의 지역 변수 n이 항상 메모리에 저장되며 f1이 호출된 후에 자동으로 지워지지 않음을 증명합니다.
왜 이런 일이 일어나는 걸까요? 그 이유는 f1이 f2의 상위 함수이고 f2가 전역 변수에 할당되어 f2가 항상 메모리에 있고 f2의 존재가 f1에 따라 달라지므로 f1은 항상 메모리에 있고 삭제되지 않기 때문입니다. 호출이 완료된 후 가비지 수집 메커니즘(가비지 수집)에 의해 재활용됩니다.
이 코드에서 주목할 만한 또 다른 점은 "nAdd=function(){n+=1}" 행입니다. 우선 var 키워드는 nAdd 이전에 사용되지 않으므로 nAdd는 오히려 전역 변수입니다. 지역변수보다 둘째, nAdd의 값은 익명 함수이고 이 익명 함수 자체도 클로저이므로 nAdd는 함수 외부의 함수 내부 지역 변수에 대해 연산을 수행할 수 있는 setter와 동일합니다.
클로저 캡슐화
(function() { var _userId = 23492; var _typeId = 'item'; var export = {}; function converter(userId) { return +userId; } export.getUserId = function() { return converter(_userId); } export.getTypeId = function() { return _typeId; } window.export = export; //通过此方式输出 }()); export.getUserId(); // 23492 export.getTypeId(); // item export._userId; // undefined export._typeId; // undefined export.converter; // undefined
클로저의 특성을 사용하면 일부 복잡한 함수 논리를 캡슐화할 수 있습니다. 이 예에서는 간접 액세스를 위해 내보내기 메소드(getUserId, getTypeId)를 호출합니다. 함수의 개인 변수이지만, import._userId를 직접 호출하여 _userId를 얻을 수는 없습니다. 이것도 Node에서 흔히 사용되는 기능입니다~
흔히 저지르는 실수: 루프 폐쇄
다음의 경우 aaa, bbb, ccc 값을 가진 3개의 div를 추가합니다. aaa를 클릭하면 1이 출력되고, bbb를 클릭하면 2가 출력되고, ccc를 클릭하면 3이 출력됩니다.
document.body.innerHTML = "<div id=div1>aaa</div>" + "<div id=div2>bbb</div><div id=div3>ccc</div>"; for (var i = 1; i < 4; i++) { document.getElementById('div' + i). addEventListener('click', function() { alert(i); // all are 4! }); }
결과적으로 aaa, bbb 또는 ccc를 클릭하면 됩니다. 출력 경고(4)~~
문제는 초기화가 완료되었을 때 i의 값이 이미 4라는 것입니다
원하는 결과를 얻으려면 aaa를 클릭하여 1을 출력하고 bbb를 클릭하여 2를 출력하고 ccc를 클릭하여 3을 출력합니다. 클로저 기술을 사용하려면 각 루프 동안 즉시 실행되는 익명 함수로 래핑합니다. 이런 방식으로 클로저 환경의 i에서 경고(i)의 값을 가져옵니다. . 이것은 각 루프의 할당 i가 1, 2, 3을 출력할 수 있습니다
document.body.innerHTML = "<div id=div1>aaa</div>" + "<div id=div2>bbb</div>" + "<div id=div3>ccc</div>"; for (var i = 1; i < 4; i++) { !function(i){ //②再用这个参数i,到getElementById()中引用 document.getElementById('div' + i). addEventListener('click', function() { alert(i); // 1,2,3 }); }(i); //①把遍历的1,2,3的值传到匿名函数里面 }
생각하는 질문
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。(来自阮老师)这题目总结得真秒~~
代码片段一。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
代码片段二。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());