Home > Article > Web Front-end > Are JavaScript closures scopes?
In JavaScript, a closure is not a scope, but a function context active object that can persist. It is an object that contains references to both the function object and the scope object. Closures are mainly used to obtain variables or values on the scope chain or prototype chain.
The operating environment of this tutorial: windows7 system, javascript version 1.8.5, Dell G3 computer.
We know that the order in which the scope chain searches for identifiers starts from the current scope and searches upward one level at a time. Therefore, through a scope chain, variables inside a JavaScript function can read variables outside the function, but conversely, variables outside the function generally cannot read variables inside the function. In practical applications, sometimes it is necessary to access the local variables of a function outside the function. In this case, the most common method is to use closures.
Closure is one of the important features of JavaScript and plays an important role in functional programming. This section introduces the structure and basic usage of closure.
SoWhat is a closure?
A closure is a function context active object that can persist. It is an object that contains references to both the function object and the scope object. Closures are mainly used to obtain variables or values on the scope chain or prototype chain. The most common way to create a closure is to declare an inner function (also called a nested function) within a function and return the inner function.
At this time, you can get the internal function by calling the function outside the function, and then call the internal function to access the function's local variables. The internal function at this time is a closure. Although according to the concept of closure, all JavaScript functions that access external variables are closures, but most of the time what we call closures actually refers to internal function closures.
Closures can encapsulate some data as private properties to ensure safe access to these variables. This feature brings great benefits to applications. It should be noted that if closures are used improperly, they can also cause some unexpected problems. Here are a few examples to demonstrate the creation, use, and possible problems of closures and their solutions.
Formation principle
When the function is called, a temporary context active object will be generated. It is the top-level object of the function scope. All private methods, variables, parameters, private functions, etc. in the scope will exist as attributes of the context active object.
After the function is called, by default the context active object will be released immediately to avoid occupying system resources. However, if the private variables, parameters, private functions, etc. within the function are referenced by the outside world, the context active object will continue to exist temporarily until all external references are cancelled.
However, the function scope is closed and cannot be accessed by the outside world. So under what circumstances can the outside world access private members within a function?
According to the scope chain, the inner function can access the private members of the outer function. If the inner function references the private members of the outer function, and the inner function is passed to the outside world, or is open to the outside world, then a closure body is formed. This external function is a closure body. After it is called, the active object will not be canceled temporarily, and its properties will continue to exist. The private members of the external function can be continuously read and written through the internal function.
Closure structure
A typical closure body is a function of a nested structure. The inner function references the private members of the outer function, and at the same time the inner function is referenced by the outside world. When the outer function is called, a closure is formed. This function is also called a closure function.
The following is a typical closure structure.
function f(x) { //外部函数 return function (y) { //内部函数,通过返回内部函数,实现外部引用 return x + y; //访问外部函数的参数 }; } var c = f(5); //调用外部函数,获取引用内部函数 console.log(c(6)); //调用内部函数,原外部函数的参数继续存在
The parsing process is briefly described as follows:
During the JavaScript script pre-compilation period, the declared function f and variable c are first lexically pre-parsed.
During JavaScript execution, call function f and pass in the value 5.
When parsing function f, the execution environment (function scope) and active object will be created, and parameters, private variables, and internal functions will be mapped to attributes of the active object.
The value of parameter x is 5, which is mapped to the x property of the active object.
The inner function references parameter x through the scope chain, but has not yet been executed.
After the external function is called, the internal function is returned, causing the internal function to be referenced by the external variable c.
The JavaScript parser detects that the properties of the active object of the external function are referenced by the outside world and cannot unregister the active object, so the object continues to exist in the memory.
When c is called, that is, when the internal function is called, you can see that the value stored in the parameter x of the external function continues to exist. In this way, subsequent operations can be implemented and x y=5=6=11 is returned.
The following structural form can also form a closure: refer to the internal function through the global variable, so that the internal function can be opened to the outside world.
var c; //声明全局变量 function f(x) { //外部函数 c = function (y) { //内部函数,通过向全局变量开放实现外部引用 return x + y; //访问外部函数的参数 }; } f(5); //调用外部函数 console.log(c(6)); //使用全局变量c调用内部函数,返回11
Closure variants
In addition to nested functions, closures are also easily formed if external references are made to private arrays or objects inside the function.
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="Are JavaScript closures scopes?" ></div> <div id="content_2" class="show"><img scr="image/2.jpg" style="max-width:90%" / alt="Are JavaScript closures scopes?" ></div> <div id="content_3" class="show"><img scr="image/3.jpg" style="max-width:90%" / alt="Are JavaScript closures scopes?" ></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高级教程】
The above is the detailed content of Are JavaScript closures scopes?. For more information, please follow other related articles on the PHP Chinese website!