JavaScript高级---(6)闭包

x33g5p2x  于2021-09-24 转载在 JavaScript  
字(2.7k)|赞(0)|评价(0)|浏览(468)

闭包是纯函数式编程语言的传统特性之一。通过将闭包视为核心语言构件的组成部分,JavaScript语言展示了其与函数式编程语言的紧密联系。由于能够简化复杂的操作,闭包在主流JavaScript库以及高水平产品代码中日益流行起来。

一、变量的作用域

在介绍闭包之前,我们先理解JavaScript的变量作用域。变量的作用域分为两种:全局变量和局部变量。

1、全局变量
var n = 999; //全局变量

        function f1() {
            a = 100; //在这里a也是全局变量
            alert(n);
        }
        console.log(a); //100

在这里,函数内外部可以直接取到变量的值——全局变量

2、局部变量
//局部变量
        function f2() {
            var b = 22;
        }
        console.log(b);   //报错

在这里,函数外部无法直接取到函数内部定义的值——局部变量

讲到这里,当我们想要从外部取到局部变量的值,这时候该怎么办呢?
请接着往下看:

二、如何从外部获取局部变量

接下来我们看一个例子:

var outer = 'Outer'; // 全局变量
var copy; 
function outerFn(){ // 全局函数
 var inner = 'Inner'; // 该变量只有函数作用域,无法从外部访问
 function innerFn(){ // outerFn()中的innerFn() 
 // 全局上下文和外围上下文都可以在这里使用,
 // 因此可以访问到outer和inner 
 console.log(outer); 
 console.log(inner); 
 } 
 copy=innerFn; // 保存innerFn()的引用
 // 因为copy是在全局上下文中声明的,所以在外部可以使用
} 
outerFn(); 
copy(); // 不能直接调用innerFn(),但是可以通过在全局作用域中声明的变量来调用

来分析一下上面的例子。在innerFn()中可以访问变量outer,因为它处于全局上下文中。
在执行完outerFn()之后,执行了innerFn(),这是通过将该函数的引用复制到一个全局变量
copy中来实现的。在利用变量copy调用函数innerFn()执行时,此刻已经不在outerFn()的作
用域中了。因此下面的代码不是应该失败吗?
console.log(inner);
变量inner的值应该是undefined吧?可是,上面代码片段的输出却是:
“Outer”
“Inner”

这就是JavaScript的链式作用域结构,子对象会一级一级的向上寻找所有父对象的变量。所以父对象的所有变量对子对象都是可见的,反之则不成立。

这样我们就可以获取到函数内部的局部变量了。

三、闭包的概念

上面代码块中的copy()函数就是闭包。在我的理解,闭包就是能够读取到函数内部变量的函数。
而在JavaScript中,可以通过函数内部的子函数获取到局部变量,因此可以把闭包理解为定义在函数内部的函数。
可以把它理解为一个将函数内部和外部连接起来的桥梁。

四、闭包的作用

在我看来,闭包的作用主要体现在两个方面:

1、可以读取函数内部的变量

这个作用在上个代码块已经表现得很清楚。

2、可以将局部变量的值一直保存在内存中

总所周知,局部变量只有当使用的时候才会在内存中开辟出暂时的存储空间,在函数运行结束后会自动释放空间。而闭包的出现可以使得局部变量可以像全局变量一样一致存储在内存中。

function c1() {
            var z = 9999;
            nAdd = function() {
                z += 1;
            }

            function c2() {
                console.log(z);
            }
            return c2;
        }
        var result = c1();
        result(); //9999
        nAdd();
        result(); //10000

在上述代码中,先执行一次c1(),此时z=9999;再执行一次nAdd(),使z+1;在执行一次c1()输出此时z的值,z=10000。说明z的值一直存储在内存中,并没有在第一次调用c1()后背自动消除。
此时就要注意,闭包的使用会消耗很大的内存,不要滥用闭包。在退出函数之前,将不使用的局部变量全部删除。

五、案例–点击li打印对应的索引

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li>榴莲</li>
        <li>榴莲</li>
        <li>榴莲</li>
        <li>榴莲</li>
    </ul>

    <script> var lis = document.querySelector("ul").querySelectorAll("li"); for (var i = 0; i < lis.length; i++) { // 立即执行函数 ()(这里回调函数,可以加参数) (function(i) { // console.log(i); lis[i].onclick = function() { console.log(i); } })(i) } </script>
</body>

</html>

这里在进行for循环的时候,我们为什么不直接对li添加绑定事件呢?这和JavaScript的异步处理机制有关。看下面的代码,不论点击哪一个,输出都为4.

var lis = document.querySelector("ul").querySelectorAll("li");
        for (var i = 0; i < lis.length; i++) {
            // 原始方式
            lis[i].onclick = function() {
				console.log(i);  // 4
            }
        }

原因是这样的,当执行for循环的时候,下面li的点击事件对应的代码还没有被执行,会放在内存队列等待执行,但是此时for已经执行完毕,不论点击哪一个li,结果输出都是一样的。

下一篇:JavaScript高级—(7)浅拷贝与深拷贝

相关文章