详解js中的闭包(closure)以及闭包的使用

作用域链

我们知道在js中作用域分为全局作用域与局部作用域,作用域链的访问规则为从内到外,也就是说如果当前的作用域中没有该变量或者方法,则会在包含该作用域的外层作用域中,一层一层的向上找,直到window作用域,并且外层不能访问内层的作用域,不同的局部作用域之间也是不能互相访问的,如下样例:

var a=1
function fn(){
    a=2
    console.log(a)
}
fn() //2

以上的代码为什么会输出2,就是跟作用域链的访问规则相关

什么是闭包?

上面说到由于作用域链的访问规则,我们是无法访问到一个函数内部的变量的,为了实现这样的访问,可以利用闭包,闭包是指有权访问另一个函数作用域中的变量,变量可以反复使用,也不会污染全局

闭包创建的三个步骤:

  1. 在一个函数里面创建另一个函数
  2. 内部的函数可以引用外部的变量
  3. 内层函数作为外层函数的返回值
function A() {
     var x = 1
     function B() {
         return ++x
     }
     return B
 }
 var m=A()
console.log(m()) //2
console.log(m()) //3

为什么上面的B()在调用的时候,值会递增,这就是闭包的特点之一,参数和变量不会被垃圾回收机制回收,也就是说外层的活动对象不能被垃圾回收机制回收,因为内层函数引用外层函数的活动对象。

闭包的原理

下面以图例说明代码片段的整个执行过程:

在这里插入图片描述
函数在创建的时候会创建两个对象,一个是函数本身,一个是作用域链对象,该作用域链对象为一个栈结构,栈的底端放的是window对象,栈的顶端放的是函数调用时产生的活动对象,如果函数调用的过程中需要某一个变量或者方法,会从栈的顶端向下找,直到window对象。

一般函数执行完毕之后,产生的活动对象会被垃圾回收机制回收,也就是说A函数在执行完毕之后产生的活动对象会被销毁,但是这里A产生的活动对象与函数B还存在引用关系,因此不会被销毁。

闭包的优点

我们知道全局变量,可以在任意一个函数中去修改,变量可能被污染;局部变量只能在函数体内部使用,不能全局使用。

综上原理,我们知道如果想让一个变量长期驻扎在内存中重复使用,并且不希望被全局变量污染,使用闭包就再合适不过了。

闭包的优点总结为以下几点:

  1. 变量可以重复使用

  2. 避免全局变量的污染

  3. 拥有私有成员

闭包的缺点

function A() {
    var x = 1;
    return function () {
        console.log(++x);
    }
}
var B= A()
B()//2
B()//3
var C = A()
C()//2
C()//3

如上面这段代码,第一次执行A函数时,x从1开始,第二次执行A函数时,x依旧从1开始,因为两次执行分别产生两个不同的地址B和C,两者之间是相互独立的,B和C在调用时,产生的活动对象是不一样的。

因此如果滥用闭包,就会造成一些性能的问题,也可能导致内存泄漏

什么是内存泄漏?

js自身都有一个内存回收机制,当分配出去的内存不使用,便会回收,内存泄漏的根本原因就是,代码中分配了一些顽固的内存,无法回收,如果这样的顽固内存还在一直不停的分配,就会导致内存不足,造成泄漏,因此,尽量少使用闭包。

闭包中的难点

function A(){
    var B=[]
    for(var i=0;i<10;i++){
        B[i]=function(){
            return i
        }
    }
    return B
}
for(var i=0;i<10;i++){
    var C=A()
    console.log(C[i]())  //10
}

根据代码的我们猜测输出的结构为0-9,但是实际上输出全都是10,A函数在执行的时候,创建了活动变量B和 i ,并且给B数组中的每一项赋值为一个地址,当执行C函数时,才会产生他的活动变量,但是此时变量 i 已经循环完成,变为10,因此要知道活动变量产生的时机,只有在函数被调用时产生。

闭包的使用

设计模式中的单例模式

**单例模式的思想在于 单例对象的类必须保证只有一个实例存在 。**也就说如果第一次new,则产生一个,如果第二次new,不会产生新的实例,而是返回第一次new的实例,因此我们就需要将产生的实例存储在变量中,在以后每次new一个实例的时候,都需要去判断这个变量是否存在,但是又不能将这个变量放在全局,并且需要它长期驻扎在内存中,因此我们使用闭包实现。

 function A() {
     function ClassName(name) {
         this.name=name
     }
     var a = null   //存储className类的实例
     return function (name) {
         if (!a) {
             a = new ClassName(name)
             return a
         }
         return a
     }
 }
var B = A()//第一次调用A函数
console.log(B("1"))  //name :"1"
console.log(B("2"))  //name :"1"
var C = A()//第二次调用A函数
console.log(C("2"))  //name :"2"
console.log(B===C)   //false

我们可以通过以上这样的闭包来实现单例模式,但是如果我们多次调用A函数,就产生多个闭包,可以产生多个实例,因此这里希望A函数不能多次被调用,只能被调用一次,我们就可以把外层函数放在一个自调用函数中。

var B = (function A() {
    function ClassName(name) {
        this.name = name
    }
    var a = null   //存储className类的实例
    return function (name) {
        if (!a) {
            a = new ClassName(name)
            return a
        }
        return a
    }
})()
console.log(B("1")) //name :"1"
console.log(B("2")) //name :"1"

这样就能严格的实现单例模式了

函数防抖

在开发的过程中,用户频繁的触发向后台发送数据,会对服务器造成压力,可以使用函数防抖来解决这个问题

原理:在一件事情结束或者给定的时间内没有执行,再去执行回调函数

例如注册时,用户名唯一的情况下,用户在输入用户名时,我们需要实时的向后端发送请求,判断是否存在该用户名,但是如果用户每输入一个字符都去执行这个过程的话,一是浪费资源,二是给服务器造成压力,解决方案就是如果用户在给定的时间范围内没有输入,则去发送请求。

$(".userName").keyup(debounce(inputChange,1000))) //监听用户名输入框
function debounce(inputChange,time){
	var timer=null
	return function(){
		if(timer){
			clearTimeout(timer)
			timer=null
		}
		timer=setTimeout(function(){
			inputChange()
		},time)
	}
}
function inputChange(){   //事件处理函数
    //请求后台的逻辑
}

每输入一个字符,都会调用debounce()函数,传入的参数为事件处理函数与间隔的时间,debounce()为一个闭包,内层函数使用外层函数的timer变量,如果闭包被频繁的调用,每一次都会在内层函数中先将该定时器清除,再赋值为延迟定时器,如果在1s内没有继续输入,timer就不会被清除,1s之后就会调用inputChange()函数,向后台发送请求。

函数防抖也就是利用了闭包的内层函数可以使用外层函数中的变量,并且外层变量不会被释放的原理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值