闭包
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数中的变量(函数)时,就产生了闭包。
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.内部函数引用了外部函数的数据(变量/函数)
我们执行了函数的定义就可以产生闭包,而不非得调用函数才能产生闭包。
为什么会产生闭包?
我们要了解为什么产生闭包首先要了解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执行过程:
const a=_fun_(0)
调用最外层的函数,只传入n没有传入o,所以打印出来的是undefined- a.fun(1),调用了fun(1)时传入的m为1,此时fun闭包了外层函数的n,n还是第一次调用的n也就是0.即m=1,n=0。再调用
—fun(1,0)—
打印出来0 - a.fun(2);调用 fun(2) 时 m 为 2 ,但依然是调用 a.fun,所以还是闭包了第一次调用时的 n ,所以内部调用第一层的 fun(2,0);所以 o 为 0
- 同上
b执行过程:
- 第一次调用第一层
_fun_(0)
时,o 为 undefined - 第二次调用 .fun(1) 时 m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的_fun_(1,0);所以 o 为 0;
- 第二次调用 .fun(1) 时 m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的_fun_(1,0);所以 o 为 0;
- 第四次调用 .fun(3) 时 m=3,闭包了第三次的 n ,同理,最终调用第一层 fun(3,2);所以 o 为 2
c执行过程:
- 在第一次调用第一层 fun(0) 时,o 为 undefined
- 在第二次调用, .fun(1) 时,m 为 1,此时 fun 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层 fun(1,0);所以 o 为 0
- 第三次调用, .fun(2) 时 m=2,此时 fun 闭包的是第二次调用的 n=1,即 m=2,n=1,并在内部调用第一层 fun(2,1);所以 o 为 1
- 第四次 .fun(3) 时同理,但依然时调用第二次的返回值,所以最终调用第一层的 fun(3,1),所以 o 为 1