闭包的知识

闭包


如何产生闭包?

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数中的变量(函数)时,就产生了闭包。

function done(){
 var a = 100
 funciton fun(){
 	console.log(a)  //这里引用了外部函数的变量,就有了闭包。
 }
 return fun  //返回一个函数
}

闭包到底是什么?

维基百科中的描述太长,我们看个简单易懂的。

闭包在JavaScript高级程序设计(第3版)中是这样描述:闭包是指有权访问另一个函数作用域中的变量的函数。

换言之,闭包就是嵌套函数中的那个内函数。在上边那串代码中就是function fun(){console.log(a)}

再深入一点来理解呢就可以理解为:闭包是可以在另一个函数的外部访问到其作用域中的变量的函数。而被访问的变量可以和函数一同存在。即使另一个函数已经运行结束,导致创建变量的环境销毁,也依然会存在,直到访问变量的那个函数被销毁。

就如下面代码所示:

function add(){
    var sum = 0
    function operation(){
        return sum = sum+1
    }
    return operation
}
var a = add();
console.log(a());//1
console.log(a());//2
console.log(a());//3
a = null  //清空,闭包死亡,包含闭包的函数成为了垃圾对象。
a = add();
console.log(a())//1

闭包有什么用呢?

根据我们之前对于闭包的定义:闭包是指有权访问另一个函数作用域中的变量的函数

那我们也就能知道闭包的作用了:

  1. 可以让函数的外部访问到函数内部的局部变量(但是对这个局部变量的操作是闭包早就设定好的)
  2. 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。

注意:闭包只能取得包括函数中任何变量的最后一个值,因为别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。

在确定绝对需要使用闭包的地方才使用闭包。因为闭包存在变量不会自动回收这一特性,可能造成内存泄漏。

###产生闭包的条件是什么?

大致条件有二:1.函数进行了嵌套 2.内部函数引用了外部函数的数据(变量/函数)

我们执行了函数的定义就可以产生闭包,而不非得调用函数才能产生闭包。

为什么会产生闭包?

我们要了解为什么产生闭包首先要了解JavaScript中的自动回收机制:

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采取什么策略。垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
————————————————
上面一段是JavaScript高级程序设计(第3版)中对于JavaScript的自动回收机制的描述。其中有这么一段**从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。**所以当函数A的内部变量被内部函数B访问时,此时函数A的内部变量就进入了函数B的环境中。而函数C是外部函数,函数A的结束执行并不会影响到函数A外部的变量C,也就是内部函数B。所以内部函数B的环境会一直存在,B函数中引用的A函数的变量就会一直被标记为进入环境,直到变量C被注销。因而函数A的内部变量也会一直存在到变量C的注销为止。


闭包的应用

向外部暴露出一个或者多个方法:

function myModule(){
    var msg = 'My'
    function doSomething(){
        console.log('doSomething()'+msg.toUpperCase())
    }
    function doOtherthing(){
        console.log('doOtherthing()'+msg.toLowerCase)
    }
    return{
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
}

var my = myModule()
my()

匿名函数向外暴露:

(function(){
    var msg = 'MY'
    function doSomething(){
        console.log('doSomething()'+msg.toUpperCase())
    }
    function doOtherthing(){
        console.log('doOtherthing()'+msg.toLowerCase)
    } 
    window.myModule = {
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
})()   //这种写法不需要再去执行函数引入js文件之后直接可以调用。
myModule.doSomething()
myModule.doOtherthing()

内存溢出和内存泄漏

内存溢出:

​ 一种程序上出现的错误,当程序运行需要的内存超过了剩余的内存时,就会出现内存溢出的错误

这种情况如果出现,像浏览器就会直接崩溃。

var obj = {}
for(var i = 0;i<10000;i++){
    obj[i] = new Array(10000)
}

内存泄漏:

​ ==占用的内存没有及时释放。==内存泄漏积累的过多就会导致内存溢出的发生

​ 可能导致内存泄露的情况:1. 意外的全局变量 2.启动循环定时器之后不清理 3.闭包

function fn(){
    a = 3  
    conosole.log(a)
}
fn()  //执行完了,a的内存也不会被释放。 这个a就是以外的全局变量
var intervaLId = setInterval(function(){
    console.log('---')
})
//启动循环定时器之后不清理
//clearInterval(intervaLId)
function fn(){
    var a = 4
    function fn2(){
        console.log(a)
    }
    return fn2
}
var f = fn()
f()

面试题

var name = 'The Window'
var object = {
    name = "My Object",
    getNameFun:function(){
        return function(){
            return this.name;
        }
    }
}
alert(object.getNameFun())

问:最后输出什么?

答:输出“My Object”

object.getNameFun()这串代码其实就是在执行函数function(){return this.name}这个this指的是调用方法的object。所以输出的是My Object

var name = 'The Window'
var object = {
    name = "My Object",
    getNameFun:function(){
        return function(){
            return this.name;
        }
    }
}
alert(object.getNameFun()())

问:输出什么?

答:输出"The Window"

我们看object.getNameFun()()这串代码时要把它拆开来看,拆成两部分object.getNameFun()()

那么拆完之后我们就可以把前一部分看作var getname = object.getNameFun()而后边这个括号就可以跟前边的联系起来再看成getname()。这时再去执行这个函数它的this就不是object了,而是window,因为它相当于前边没有对象而直接调用一个函数。

var name = 'The Window'
var object = {
    name = "My Object",
    getNameFun:function(){
        var that = this
        return function(){
            return that.name;
        }
    }
}
alert(object.getNameFun()())

问:输出什么?

答:输出My Object

这串代码跟之前不同的地方在于var that = this,而后面在内函数中用了这个that,就形成了闭包函数。而之前两串代码其实都不算是闭包函数。这样无论我们怎么调用object.getNameFun()它其中的that都是this代指的object

function fun(n,o){
    console.log(o)
    return{
        fun:function(m){
            return fun(m,n)
        }
    }
}
var a = fun(0); a.fun(1) a.fun(2) a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1); c.fun(2); c.fun(3) 

问:上边的代码分别输出什么?
答:

undefined 0 0 0
undefined 0 1 2
undefined 0 1 1

关键点:理清哪个是fun函数,哪个是fun属性

我们把上边代码等价转换一下:

function _fun_(n,o){
    console.log(o);
    return {
        fun:function(m){
            return _fun_(m,n)
        }
    }
}
 
const a=_fun_(0);a.fun(1);a.fun(2);a.fun(3);
const b=_fun_(0).fun(1).fun(2).fun(3);
const c=_fun_(0).fun(1);c.fun(2);c.fun(3);

a执行过程:

  1. const a=_fun_(0)调用最外层的函数,只传入n没有传入o,所以打印出来的是undefined
  2. a.fun(1),调用了fun(1)时传入的m为1,此时fun闭包了外层函数的n,n还是第一次调用的n也就是0.即m=1,n=0。再调用—fun(1,0)—打印出来0
  3. a.fun(2);调用 fun(2) 时 m 为 2 ,但依然是调用 a.fun,所以还是闭包了第一次调用时的 n ,所以内部调用第一层的 fun(2,0);所以 o 为 0
  4. 同上

b执行过程:

  1. 第一次调用第一层_fun_(0) 时,o 为 undefined
  2. 第二次调用 .fun(1) 时 m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的_fun_(1,0);所以 o 为 0;
  3. 第二次调用 .fun(1) 时 m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的_fun_(1,0);所以 o 为 0;
  4. 第四次调用 .fun(3) 时 m=3,闭包了第三次的 n ,同理,最终调用第一层 fun(3,2);所以 o 为 2

c执行过程:

  1. 在第一次调用第一层 fun(0) 时,o 为 undefined
  2. 在第二次调用, .fun(1) 时,m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层 fun(1,0);所以 o 为 0
  3. 第三次调用, .fun(2) 时 m=2,此时 fun 闭包的是第二次调用的 n=1,即 m=2,n=1,并在内部调用第一层 fun(2,1);所以 o 为 1
  4. 第四次 .fun(3) 时同理,但依然时调用第二次的返回值,所以最终调用第一层的 fun(3,1),所以 o 为 1
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值