第3章 第3节 闭包(理解)

在这里插入图片描述

一、什么是闭包

在高级程序设计中对闭包定义是这样的:“闭包是指有权限访问另一个函数作用域中的变量的函数。“

那么能读取另一个函数里的变量,无非两种情况:内部函数读取外部函数的;外部函数读取内部函数的。

内部函数读取外部函数的;
这种JavaScript本身就支持。

外部函数读取内部函数的
这个JavaScript本身支持。

二、如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。并且还要把内部函数return回外部函数,或者想办法调用到外部函数,这样外部函数就是内部函数的子函数了。

外部函数 内部函数 包含 调用 内部函数return回外部函数,或者想办法调用到外部函数,这样外部函数就是内部函数的子函数了. 外部函数 内部函数
function f1(){

  var n=999;

  function f2(){
    alert(n); // 999
  }

}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

可以理解f2返回到f1,f1也就是f2的子函数了,有点自己调用自己的,字头咬字尾的感觉。

案例解释

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JavaScript闭包属性详解</title>
    <style type="text/css">
        p {
            background: gold;
        }
    </style>
    <script type="text/javascript">
        function init() {
            var pAry = document.getElementsByTagName('p');
            for(var i=0; i<pAry.length; i++) {
                 // 每次点击都是5
                pAry[i].onclick = function() {
                    alert(i);
                }
            }
        }
    </script>
</head>
<body onload="init();">
<p>产品0</p>
<p>产品1</p>
<p>产品2</p>
<p>产品3</p>
<p>产品4</p>
</body>
</html>

在这里插入图片描述

这里每次单击的时候才调用子函数function();调用父函数init()的变量i;单击事件的发生在初始化函数执行之后,才发生,所以i早已运行到i=5,元素长度了。
如何解决这个问题了,在单击function()函数外面加一层函数。让这层函数把i传过来的值保存住,例如保持到j中,然后单击function函数调用外面这一层函数的变量j值。

使用立即执行函数

立即执行函数
很多函数只会用一次,之后一直在等待执行浪费内存空间,而立即执行函数执行完就会把函数立即删除,之后调用也找不到。
主要用于初始化功能的函数。
基本形式:(function (){}())
其它方面与普通函数无任何区别,有执行期上下文,要预编译等等。

如果想打印0-9,的解决方法:

//立刻执行保存值(最常用)
function test() {
    var arr = [];

    for(var i = 0; i < 10; i++) {
        // 实际循环立刻执行函数:立刻执行函数保存i值
        (function(j){
            // 立即执行函数AO中的 j = 0, 1, 2, 3......
            // 每个立刻执行函数中都有一个唯一的j值
            arr[j] = function() {
                // 访问的是立刻执行函数AO中的j值:0, 1, 2.... 
                document.write(j + ' ');
            }
        })(i);
    }

    return arr;
}

var myArr = test();
console.log(myArr); // [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]

for(var j = 0; j < 10; j++) {
    myArr[j](); //0 1 2 3 4 5 6 7 8 9
}

在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数j。而在这个匿名函数内部,又创建并返回了一个访问j 的闭包。这样一来,myArr数组中的每个函数都有自己j 变量的一个副本,因此就可以返回各自不同的数值了。

案例解释

点击oli的数字打印出对应的数字值


<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JavaScript闭包属性详解</title>
    <style type="text/css">
        p {
            background: gold;
        }
    </style>
    <script type="text/javascript">
        function init() {
            var pAry = document.getElementsByTagName('p');
            for(var i=0; i<pAry.length; i++) {
             /*    // 每次点击都是5
                pAry[i].onclick = function() {
                    alert(i);
                    }
              */
              
              
              // 立刻执行函数包裹解决
                (function(j){
                     pAry[j].onclick = function() {
                     alert(j + 1);
                }
                })(i);
            
            }
        }
    </script>
</head>
<body onload="init();">
<p>产品0</p>
<p>产品1</p>
<p>产品2</p>
<p>产品3</p>
<p>产品4</p>
</body>
</html>

此题运行分析:
此题目的点击某行列表,显示序号。
如果直接在for循环中调用单击事件,由于单击事件发生在网页加载执行完for循环的时候,那时候i早已变成5。
如何解决:在单击事件函数外面加一个父函数,利用单击事件这个子函数的父函数的变量都要保存。让先在内存保存5个父函数作用域链。
同时利用立即执行函数:

补充:立即执行函数

立即执行函数案例

(function(a, b){ // 形参 
  console.log(a + b); // 3 
}(1, 2)); // 实参

此题对应

(function(j){//j是形参
               Li[j].onclick = function() {
                    console.log(j + 1);
                }
             })(i);//i是实参

所以利用立即执行函数,来实现外部调用内部变量。

还有参数:在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数j。而在这个匿名函数内部,又创建并返回了一个访问j 的闭包。这样一来,function(j)形成[function(0),function(1),function(2),function(3),function(4)]数组,数组项为函数的数组,数组中的每个函数都有自己j 变量的一个副本,因此就可以返回各自不同的数值了。

在这里插入图片描述
父函数的作用域都存在内存中,单击第几个项目列表,对应的父函数的j就会被调出来,子函数调用父函数的变量。

闭包

创建
在Javascript中闭包的创建过程,如以下程序所示
代码

function a(){
     var i=0;
     function b(){
         alert(++i);
     }
     return b;
 }
var c=a();
c();

特点

这段代码有两个特点:

1、函数b嵌套在函数a内部;

2、函数a返回函数b。

这样在执行完var c=a( )后,变量c实际上是指向了函数b,再执行c( )后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,这是因为函数a外的变量c引用了函数a内的函数b。也就是说,当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

作用

简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。

在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被回收。

微观世界

如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(execution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。

2、当函数a执行的时候,a会进入相应的执行环境(execution context)。

3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。

4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。

5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。

6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

szmtjs10

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值