JavaScript에서 클로저는 범위가 아니라 지속될 수 있는 함수 컨텍스트 활성 개체입니다. 이는 함수 개체와 범위 개체에 대한 참조를 모두 포함하는 개체입니다. 클로저는 주로 스코프 체인이나 프로토타입 체인에서 변수나 값을 얻는 데 사용됩니다.
이 튜토리얼의 운영 환경: Windows 7 시스템, JavaScript 버전 1.8.5, Dell G3 컴퓨터.
스코프 체인에서 식별자를 검색하는 순서는 현재 범위에서 한 수준 위로 검색하는 것임을 알고 있습니다. 따라서 범위 체인을 통해 JavaScript 함수 내부의 변수는 함수 외부의 변수를 읽을 수 있지만, 반대로 함수 외부의 변수는 일반적으로 함수 내부의 변수를 읽을 수 없습니다. 실제 응용에서는 함수 외부에서 함수의 지역 변수에 액세스해야 하는 경우가 있습니다. 이 경우 가장 일반적인 방법은 클로저를 사용하는 것입니다.
클로저는 JavaScript의 중요한 기능 중 하나이며 함수형 프로그래밍에서 중요한 역할을 합니다. 이 섹션에서는 클로저의 구조와 기본 사용법을 소개합니다.
그래서클로저란 무엇인가요?
클로저는 지속될 수 있는 함수 컨텍스트 활성 개체입니다. 이는 함수 개체와 범위 개체에 대한 참조를 모두 포함하는 개체입니다. 클로저는 주로 스코프 체인이나 프로토타입 체인에서 변수나 값을 얻는 데 사용됩니다. 클로저를 생성하는 가장 일반적인 방법은 함수 내에서 내부 함수(중첩 함수라고도 함)를 선언하고 내부 함수를 반환하는 것입니다.
이때 함수 외부에서 함수를 호출하여 내부 함수를 얻은 후, 내부 함수를 호출하여 함수의 지역 변수에 접근할 수 있습니다. 이때 내부 기능은 클로저입니다. 클로저의 개념에 따르면 외부 변수에 액세스하는 모든 JavaScript 함수는 클로저이지만, 우리가 일반적으로 클로저라고 부르는 대부분의 경우 실제로는 내부 함수 클로저를 나타냅니다.
클로저는 일부 데이터를 개인 속성으로 캡슐화하여 이러한 변수에 대한 안전한 액세스를 보장할 수 있습니다. 이 기능은 애플리케이션에 큰 이점을 제공합니다. 클로저를 부적절하게 사용하면 예상치 못한 문제가 발생할 수도 있다는 점에 유의해야 합니다. 아래에서는 클로저의 생성, 사용, 가능한 문제 및 해결 방법을 보여주기 위해 몇 가지 예를 사용합니다.
생성 원리
함수가 호출되면 임시 컨텍스트 활동 개체가 생성됩니다. 이는 함수 범위의 최상위 개체입니다. 범위의 모든 개인 메서드, 변수, 매개 변수, 개인 함수 등은 컨텍스트 활성 개체의 속성으로 존재합니다.
함수가 호출된 후 기본적으로 컨텍스트 활성 개체는 시스템 리소스 점유를 피하기 위해 즉시 해제됩니다. 그러나 함수 내의 개인 변수, 매개변수, 개인 함수 등이 외부 세계에서 참조되는 경우 모든 외부 참조가 취소될 때까지 컨텍스트 활성 객체는 일시적으로 계속 존재합니다.
단, 기능 범위가 닫혀 있어 외부 세계에서는 접근할 수 없습니다. 그렇다면 어떤 상황에서 외부 세계가 함수 내 비공개 멤버에 접근할 수 있나요?
스코프 체인에 따르면 내부 함수는 외부 함수의 전용 멤버에 액세스할 수 있습니다. 내부 함수가 외부 함수의 전용 멤버를 참조하고 내부 함수가 외부 세계로 전달되거나 외부 세계에 열려 있는 경우 클로저 본문이 형성됩니다. 이 외부 함수는 호출된 후 활성 개체가 일시적으로 취소되지 않으며 해당 속성은 계속해서 내부 함수를 통해 읽고 쓸 수 있습니다.
클로저 구조
일반적인 클로저 본체는 중첩 구조의 기능입니다. 내부 함수는 외부 함수의 전용 멤버를 참조하는 동시에 외부 함수가 호출되면 클로저가 형성됩니다. 이 함수를 클로저 함수라고도 합니다.
다음은 일반적인 클로저 구조입니다.
function f(x) { //外部函数 return function (y) { //内部函数,通过返回内部函数,实现外部引用 return x + y; //访问外部函数的参数 }; } var c = f(5); //调用外部函数,获取引用内部函数 console.log(c(6)); //调用内部函数,原外部函数的参数继续存在
파싱 프로세스는 다음과 같이 간략하게 설명됩니다.
JavaScript 스크립트 사전 컴파일 기간 동안 선언된 함수 f와 변수 c가 먼저 사전 구문 분석됩니다.
JavaScript 실행 중에 함수 f를 호출하고 값 5를 전달합니다.
함수 f를 구문 분석하면 실행 환경(함수 범위)과 활성 개체가 생성되고 매개 변수, 전용 변수, 내부 함수가 활성 개체의 속성에 매핑됩니다.
매개변수 x의 값은 5이며 이는 활성 객체의 x 속성에 매핑됩니다.
내부 함수는 범위 체인을 통해 매개변수 x를 참조하지만 아직 실행되지 않았습니다.
외부 함수가 호출된 후 내부 함수가 반환되어 내부 함수가 외부 변수 c에 의해 참조됩니다.
JavaScript 파서는 외부 함수의 활성 개체 속성이 외부 세계에서 참조되는 것을 감지하고 활성 개체를 등록 취소할 수 없으므로 메모리에 해당 개체의 존재를 계속 유지합니다.
c가 호출될 때, 즉 내부 함수가 호출될 때 외부 함수의 매개변수 x에 저장된 값이 계속 존재하는 것을 볼 수 있습니다. 이러한 방식으로 후속 작업을 구현할 수 있으며 x+y=5=6=11이 반환됩니다.
다음 구조 형태도 클로저를 형성할 수 있습니다. 전역 변수를 통해 내부 기능을 참조하여 내부 기능을 외부 세계에 공개할 수 있습니다.
var c; //声明全局变量 function f(x) { //外部函数 c = function (y) { //内部函数,通过向全局变量开放实现外部引用 return x + y; //访问外部函数的参数 }; } f(5); //调用外部函数 console.log(c(6)); //使用全局变量c调用内部函数,返回11
클로저 변형
중첩된 함수 외에도 비공개 배열이나 함수 내부 개체에 대한 외부 참조가 만들어지면 클로저도 쉽게 형성됩니다.
var add; //全局变量 function f() { //外部函数 var a = [1,2,3]; //私有变量,引用型数组 add = function (x) { //测试函数,对外开放 a[0] = x * x; //修改私有数组的元素值 } return a; //返回私有数组的引用 } var c = f(); console.log(c[0]); //读取闭包内数组,返回1 add(5); //测试修改数组 console.log(c[0]); //读取闭包内数组,返回25 add(10); //测试修改数组 console.log(c[0]); //读取闭包内数组,返回100
与函数相同,对象和数组也是引用型数据。调用函数 f,返回私有数组 a 的引用,即传值给局部变量 c,而 a 是函数 f 的私有变量,当被调用后,活动对象继续存在,这样就形成了闭包。
这种特殊形式的闭包没有实际应用价值,因为其功能单一,只能作为一个静态的、单向的闭包。而闭包函数可以设计各种复杂的运算表达式,它是函数式变成的基础。
反之,如果返回的是一个简单的值,就无法形成闭包,值传递是直接复制。外部变量 c 得到的仅是一个值,而不是对函数内部变量的引用。这样当函数调用后,将直接注销对象。
function f(x) { //外部函数 var a = 1; //私有变量 return a; } var c = f(5); console.log(c); //仅是一个值,返回1
使用闭包
下面结合示例介绍闭包的简单使用,以加深对闭包的理解。
示例1
使用闭包实现优雅的打包,定义存储器。
var f = function () { //外部函数 var a = []; //私有数组初始化 return function (x) { //返回内部函数 a.push(x); //添加元素 return a; //返回私有数组 }; } () //直接调用函数,生成执行环境 var a = f(1); //添加值 console.log(a); //返回1 var b = f(2); //添加值 console.log(b); //返回1,2
在上面示例中,通过外部函数设计一个闭包,定义一个永久的存储器。当调用外部函数生成执行环境之后,就可以利用返回的匿名函数不断地的向闭包体内的数组 a 传入新值,传入的值会持续存在。
示例2
在网页中事件处理函数很容易形成闭包。
<script> function f() { var a = 1; b = function () { console.log("a =" + a); } c = function () { a ++; } d = function () { a --; } } </script> <button onclick="f()">生成闭包</button> <button onclick="b()">查看 a 的值</button> <button onclick="c()">递增</button> <button onclick="d()">递减</button>
在浏览器中浏览时,首先点击“生成闭包”按钮,生成一个闭包;点击“查看 a 的值”按钮,可以随时查看闭包内私有变量 a 的值;点击“递增”“递减”按钮时,可以动态修改闭包内变量 a 的值,效果如图所示。
闭包的局限性
闭包的价值是方便在表达式运算过程中存储数据。但是,它的缺点也不容忽视。
由于函数调用后,无法注销调用对象,会占用系统资源,在脚本中大量使用闭包,容易导致内存泄漏。解决方法:慎用闭包,不要滥用。
由于闭包的作用,其保存的值是动态,如果处理不当容易出现异常或错误。
示例
设计一个简单的选项卡效果。HTML 结构如下:
<div class="tab_wrap"> <ul class="tab" id="tab"> <li id="tab_1" class="hover">Tab1</li> <li id="tab_2" class="normal">Tab2</li> <li id="tab_3" class="normal">Tab3</li> </ul> <div class="content" id="content"> <div id="content_1" class="show"><img scr="image/1.jpg" style="max-width:90%" / alt="JavaScript 클로저 범위인가요?" ></div> <div id="content_2" class="show"><img scr="image/2.jpg" style="max-width:90%" / alt="JavaScript 클로저 범위인가요?" ></div> <div id="content_3" class="show"><img scr="image/3.jpg" style="max-width:90%" / alt="JavaScript 클로저 범위인가요?" ></div> </div> </div>
下面请看 JavaScript 脚本。
window.onload = function () { var tab = document.getElementById("tab").getElementsByTagName("li"), content = document.getElementById("content").getElementByTagName("div"); for (var i = 0; i < tab.length;i ++) { tab[i].addEventListener("mouseover"), function () { for (var n = 0; n < tab.length; n ++) { tab[n].className = "normal"; content[n].className = "none"; } tab[i].className = "hover"; content[i].className = "show"; }); } }
在 load 事件处理函数中,使用 for 语句为每个 li 属性元素绑定 mouseover 事件;在 mouseover 事件处理函数中重置所有选项卡 li 的类样式,然后设置当前 li 选项卡高亮显示,同时显示对应的内容容器。
但是在浏览器中预览时,会发现浏览器抛出异常。
SCRIPT5007:无法设置未定义或 null 引用的属性"className"
在 mouseover 事件处理函数中跟踪变量 i 的值,i 的值都变为了 3,tab[3] 自然是一个 null,所以也不能够读取 className 属性。
【原因分析】
上面 JavaScript 代码是一个典型的嵌套函数结构。外部函数为 load 事件处理函数,内部函数为 mouseover 事件处理函数,变量 i 为外部函数的私有变量。
通过事件绑定,mouseover 事件处理函数被外界引用(li 元素),这样就形成了一个闭包体。虽然在 for 语句中为每个选项卡 li 分别绑定事件处理函数,但是这个操作是动态的,因此 tab[i] 中 i 的值也是动态的,所以就出现了上述异常。
【解决方法】
解决闭包的缺陷,最简单的方法是阻断内部函数对外部函数的变量引用,这样就形成了闭包体。针对本示例,我们可以在内部函数(mouseover 事件处理函数)外边增加一层防火墙,不让其直接引用外部变量。
window.load = function () { var tab = document.getElementById("tab").getElementsByTagName("li"), content = document.getElementById("content").getElementsByTagName("div"); for (var i = 0; i < tab.length; i ++ ) { (function (j) { tab[j].addEventListener("number", function () { for (var n = 0; n < tab.length; n ++) { tab[n].className = "normal"; content[n].className = "none"; } tab[j].className = "hover"; conteng[j].className = "show"; }); }) (i); } }
在 for 语句中,直接调用匿名函数,把外部函数的 i 变量传给调用函数,在调用函数中接收这个值,而不是引用外部变量 i,规避了闭包体带来的困惑。
【推荐学习:javascript高级教程】
위 내용은 JavaScript 클로저 범위인가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!