앞서 스코프 체인과 변수 객체에 대해 소개했는데 이제는 클로저에 관해 이야기하면 이해하기 쉽습니다. 사실, 모두가 이미 폐쇄에 관해 이야기했습니다. 그럼에도 불구하고 여기서는 이론적 관점에서 클로저에 대해 논의하고 ECMAScript의 클로저가 실제로 내부적으로 어떻게 작동하는지 살펴보겠습니다.
ECMAScript 클로저에 대해 직접 논의하기 전에 함수형 프로그래밍의 몇 가지 기본 정의를 살펴볼 필요가 있습니다.
우리 모두 알고 있듯이 함수형 언어(ECMAScript도 이 스타일을 지원함)에서 함수는 데이터입니다. 예를 들어, 함수를 변수에 할당하고, 다른 함수에 매개변수로 전달하고, 함수에서 반환하는 등의 작업을 수행할 수 있습니다. 이러한 함수에는 특별한 이름과 구조가 있습니다.
정의
함수 인수("Funarg") — 값이 함수의 매개변수인 인수입니다.
예:
위 예에서 funarg의 실제 매개변수는 실제로 exampleFunc에 전달된 익명 함수입니다.function exampleFunc(funArg) { funArg(); } exampleFunc(function () { alert('funArg'); });
반대로 기능적 매개변수를 받는 함수를 고차 함수(줄여서 HOF)라고 합니다. 함수형 함수 또는 부분적인 수학 이론 또는 연산자라고도 합니다. 위의 예에서 exampleFunc는 그러한 함수입니다.
앞서 언급했듯이 함수는 매개변수뿐만 아니라 반환값으로도 사용될 수 있습니다. 함수를 반환하는 이러한 함수를 함수 값이 있는 함수 또는 함수 값 함수라고 합니다.
일반 데이터의 형태로 존재할 수 있는 함수(예: 매개변수가 전달될 때 함수 매개변수를 받아들이거나 함수 값을 반환하는 경우)를 일급 함수(일반적으로 일급 객체)라고 합니다. ECMAScript에서 모든 함수는 일급 객체입니다.(function functionValued() { return function () { alert('returned function is called'); }; })()();
일반 데이터로 존재할 수 있는 함수(예: 매개변수가 전달될 때, 기능적 매개변수를 받아들이거나 함수 값을 반환할 때)를 일급 함수(일반적으로 일급 객체)라고 합니다.
ECMAScript에서는 모든 함수가 일급 객체입니다.
자신을 매개변수로 받아들이는 함수를 자동 적용 함수(자동 적용 함수 또는 자체 적용 함수)라고 합니다.
자신을 반환으로 받아들이는 함수 값을 자기 복제 기능(자동 복제 기능 또는 자기 복제 기능)이라고 합니다. 일반적으로 "자기 복제"라는 용어는 문헌에서 사용됩니다.(function selfApplicative(funArg) { if (funArg && funArg === selfApplicative) { alert('self-applicative'); return; } selfApplicative(selfApplicative); })();
(function selfReplicative() { return selfReplicative; })();
// 接受集合的函数 function registerModes(modes) { modes.forEach(registerMode, modes); } // 用法 registerModes(['roster', 'accounts', 'groups']); // 自复制函数的声明 function modes(mode) { registerMode(mode); // 注册一个mode return modes; // 返回函数自身 } // 用法,modes链式调用 modes('roster')('accounts')('groups') //有点类似:jQueryObject.addClass("a").toggle().removClass("b")
함수 매개변수에 정의된 변수는 "funarg"가 활성화되어야 접근 가능합니다. (컨텍스트가 입력될 때마다 컨텍스트 데이터를 저장하는 변수 객체가 생성되기 때문입니다.)
단, in ECMAScript, 함수는 상위 함수에 캡슐화될 수 있으며 상위 함수 컨텍스트의 변수를 사용할 수 있습니다. 이 기능은 funarg 문제를 일으킬 수 있습니다.function testFn(funArg) { // funarg激活时, 局部变量localVar可以访问了 funArg(10); // 20 funArg(20); // 30 } testFn(function (arg) { var localVar = 10; alert(arg + localVar); });
Funarg 문제
스택 지향 프로그래밍 언어에서 함수의 지역 변수는 함수가 활성화될 때마다 스택에 저장됩니다. 스택.
함수가 반환되면 이러한 매개변수는 스택에서 제거됩니다. 이 모델은 함수를 기능적 값(예: 상위 함수의 반환 값)으로 사용하는 데 상당한 제한을 둡니다. 대부분의 경우 함수에 자유 변수가 있을 때 문제가 발생합니다.
자유변수는 함수에서 사용되지만 함수 매개변수도 아니고 함수의 지역변수도 아닌 변수를 말한다.
예:
위의 예에서, innerFn 함수의 경우 localVar는 자유 변수입니다.function testFn() { var localVar = 10; function innerFn(innerParam) { alert(innerParam + localVar); } return innerFn; } var someFn = testFn(); someFn(20); // 30
스택 지향 모델을 사용하여 로컬 변수를 저장하는 시스템의 경우 이는 testFn 함수 호출이 끝나면 해당 로컬 변수가 스택에서 제거된다는 의미입니다. 이렇게 외부에서 innerFn에 대한 함수 호출이 이루어지면 (localVar 변수가 더 이상 존재하지 않기 때문에) 오류가 발생하게 됩니다.
게다가 위의 예에서 스택 지향 구현 모델에서는 innerFn을 반환 값으로 반환하는 것이 단순히 불가능합니다. testFn 함수의 지역 변수이기도 하므로 testFn이 반환될 때 제거됩니다.
또 다른 문제는 시스템이 동적 범위를 사용하고 함수를 함수 매개변수로 사용하는 경우입니다.
다음 예(의사 코드)를 살펴보세요.
동적 범위를 사용하면 변수 시스템(식별자)이 동적 변수 스택을 통해 관리된다는 것을 알 수 있습니다. 따라서 자유 변수는 함수가 생성될 때 저장된 정적 범위 체인이 아닌 현재 활성화된 동적 체인에서 쿼리됩니다.var z = 10; function foo() { alert(z); } foo(); // 10 – 使用静态和动态作用域的时候 (function () { var z = 20; foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域 })(); // 将foo作为参数的时候是一样的 (function (funArg) { var z = 30; funArg(); // 10 – 静态作用域, 30 – 动态作用域 })(foo);
这样就会产生冲突。比方说,即使Z仍然存在(与之前从栈中移除变量的例子相反),还是会有这样一个问题: 在不同的函数调用中,Z的值到底取哪个呢(从哪个上下文,哪个作用域中查询)?
上述描述的就是两类funarg问题 —— 取决于是否将函数以返回值返回(第一类问题)以及是否将函数当函数参数使用(第二类问题)。
为了解决上述问题,就引入了 闭包的概念。
闭包
闭包是代码块和创建该代码块的上下文中数据的结合。
让我们来看下面这个例子(伪代码):
var x = 20; function foo() { alert(x); // 自由变量"x" == 20 } // 为foo闭包 fooClosure = { call: foo // 引用到function lexicalEnvironment: {x: 20} // 搜索上下文的上下文 };
上述例子中,“fooClosure”部分是伪代码。对应的,在ECMAScript中,“foo”函数已经有了一个内部属性——创建该函数上下文的作用域链。
“lexical”通常是省略的。上述例子中是为了强调在闭包创建的同时,上下文的数据就会保存起来。当下次调用该函数的时候,自由变量就可以在保存的(闭包)上下文中找到了,正如上述代码所示,变量“z”的值总是10。
定义中我们使用的比较广义的词 —— “代码块”,然而,通常(在ECMAScript中)会使用我们经常用到的函数。当然了,并不是所有对闭包的实现都会将闭包和函数绑在一起,比方说,在Ruby语言中,闭包就有可能是: 一个过程对象(procedure object), 一个lambda表达式或者是代码块。
对于要实现将局部变量在上下文销毁后仍然保存下来,基于栈的实现显然是不适用的(因为与基于栈的结构相矛盾)。因此在这种情况下,上层作用域的闭包数据是通过 动态分配内存的方式来实现的(基于“堆”的实现),配合使用垃圾回收器(garbage collector简称GC)和 引用计数(reference counting)。这种实现方式比基于栈的实现性能要低,然而,任何一种实现总是可以优化的: 可以分析函数是否使用了自由变量,函数式参数或者函数式值,然后根据情况来决定 —— 是将数据存放在堆栈中还是堆中。
以上就是JavaScript闭包其一:闭包概论的内容,更多相关内容请关注PHP中文网(www.php.cn)!