闭包

一、经典面试题

function outer(){
    var num = 0;
    return function add(){
        num++;
        console.log(num);
    }
}
var func1 = outer();
func1();   //1
func1();   //2
var func2 = outer();
func2();   //1
func2();   //2复制代码

图解:


二、什么是闭包

函数中返回了另一个函数,闭包就是能够读取其他函数内部变量的函数。

1、没有形成闭包的情况下,函数外部是不能访问函数内部的局部变量的。

function f1(){
    var n = 1;
}
console.log(n);复制代码

结果:


2、形成闭包时,函数外部是可以访问函数内部的局部变量的。

function f1(){
    var n = 1;
    return function f2(){
        console.log(n);
    }
}
var result = f1();
result();复制代码


f2在f1的内部,它是可以返问f1中的变量的,那我们只要把f2作为返回值,就可以在f1外部读取它的内部变量了。

闭包形成的条件

1、函数嵌套

2、内部函数引用外部函数的局部变量

三、闭包的特性

能够记忆自己定义时所处的作用域环境。

例1

var inner;
function outer(){
    var a = 1;
    inner=function(){
        console.log(a);//这个函数虽然在外面执行,但能够记忆住定义时的那个作用域,a是1
    }
}
outer();
var a = 2;
inner(); //一个函数在执行的时候,找闭包里面的变量,不会理会当前作用域。复制代码

结果:


例2

function outer(x){
    function inner(y){
        console.log(x+y);
    }
    return inner;
}
var inn=outer(3);//数字3传入outer函数后,inner函数中x便会记住这个值
inn(5);//当inner函数再传入5的时候,只会对y赋值,所以最后弹出8复制代码


四、闭包的作用

1、使用闭包可以返回函数中的变量。

2、可以使变量长期保存在内存中,生命周期比较长。(原因:内部函数会把包含函数的活动对象填到它的作用域链中。)

3、可以用来实现js模块化。

JS模块:具有特定功能的js文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包信n个方法的对象或函数,模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。

例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script src="myModule.js"></script>
<script>
    myModule.eat();
    myModule.sleep()
</script>
</html>复制代码
myModule函数
(function(){
    //私有数据
    var eating = "eat";
    var sleeping = "sleep";
    //内部方法
    function eat(){
        console.log('eat()' + eating);
    }
    function sleep(){
        console.log('sleep()' + sleeping);
    }
    //搭建与外部联系的桥梁
    window.myModule = {
        eat:eat,
        sleep:sleep
    }
})();复制代码


加分项:

闭包不能滥用,否则会导致内存泄漏。影响页面性能。闭包使用完毕后,要立即释放资源,将引用变量指向null。

五、闭包的内存泄漏

栈内存提供一个执行环境,即作用域,包括全局作用域和私有作用域,那他们什么时候释放内存的?

全局作用域----只有当页面关闭的时候全局作用域才会销毁

私有的作用域----只有函数执行才会产生

一般情况下,函数执行会形成一个新的私有的作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁。但当遇到函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁。

例:

function f1(){
    var n = 1;
    return function f2(){
        console.log(n);
    }
}
var result = f1();
result();复制代码

f1函数内部的私有作用域会被一直占用的,发生了内存泄漏。所谓内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。所以闭包不能滥用,否则会导致内存泄漏。影响页面性能。闭包使用完毕后,要立即释放资源,将引用变量指向null。

六、闭包中的this

var name = "The Window";
var object = {
    name: "My object",
    getNameFunc: function() {
        return function() {
            return this.name;
        };
    }
}
console.log(object.getNameFunc()()); // "The Window"复制代码

为什么最后的结果是"The Window"而不是object里面的name"My object"呢?

 首先,要理解函数作为函数调用和函数作为方法调用。

我们把最后的一句拆成两个步骤执行:

var first = object.getNameFunc();
var second = first();复制代码

其中第一步,获得的first为返回的匿名函数,此时的getNameFunc()作为object的方法调用,如果在getNameFunc()中使用this,此时的this指向的是object对象。

第二部,调用first函数,可以很清楚的发现,此时调用first函数,first函数没有在对象中调用,因此是作为函数调用的,是在全局作用域下,因此first函数中的this指向的是window

为什么匿名函数没有取得其包含作用域(外部作用域)的this对象呢?

每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。 《Javascript高级程序设计》

那么,如何获得外部作用域中的this呢?

可以把外部作用域中的this保存在闭包可以访问到的变量里。如下:

var name = "The Window";
var object = {
    name: "My object",
    getNameFunc: function() {
        var that = this;   // 将getNameFunc()的this保存在that变量中
        var age = 15;
        return function() {
            return that.name;
        };
    }
};
console.log(object.getNameFunc()());   // "My object"复制代码

其中,getNameFunc()执行时的活动对象有:that/age/匿名函数,在执行匿名函数时,同时引用了getNameFunc()中的活动对象,因此可以获取that和age的值。但是由于是在全局环境中调用的匿名函数,因此匿名函数内部的this还是指向window。

var name = "The Window";
var object = {
    name: "My object",
    getNameFunc: function() {
        var that = this;   // 将getNameFunc()的this保存在that变量中
        var age = 15;
        return function() {
            console.log(this.name);  //匿名函数的this   The Window
            return that.name;        //外部函数的this   My object

        };
    }
};
console.log(object.getNameFunc()());   // "My object"复制代码


七、闭包的应用

我们要实现这样的一个需求: 点击某个按钮, 提示"点击的是第n个按钮"。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
    var btns = document.getElementsByTagName('button');
    //遍历加监听
    var btns = document.getElementsByTagName('button');
    for (var i = 0; i < btns.length; i++) {
        btns[i].onclick = function () {
            console.log('第' + (i + 1) + '个')
        }
    }
</script>
</body>
</html>复制代码


但是,当点击任意一个按钮,后台都是弹出“第四个”,这是因为i是全局变量,点击事件是异步操作,需要等同步事件执行完才能执行,所以执行到点击事件时i的值为3。

修改方法1:使用let声明i,因为let不会变量提升

for (let i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
        console.log('第' + (i + 1) + '个')
    }
}复制代码


方法2;使用闭包

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
    var btns = document.getElementsByTagName('button');
    //遍历加监听
    var btns = document.getElementsByTagName('button');
    for (var i = 0; i < btns.length; i++) {
        (function(j){
            btns[j].onclick = function () {
                console.log('第' + (j + 1) + '个')
            }
        })(i)

    }
</script>
</body>
</html>复制代码


解析:在执行for循环同步代码的时候,把i的值赋值给j,这样内部的匿名函数就记录下了每一次for循环的i(也就是j)的值,然后在执行点击事件时就用自己记录下来的j的值,达到想要的效果。

参考:深入浅出javascript闭包


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值