闭包的理解

闭包

转载:原文链接

正常情况下,定义一个函数后,会产生一个函数作用域,函数体内的局部变量只能在函数作用域中使用。当函数执行完成,函数所占的空间将会被回收,此时存在函数中的局部变量同样会被回收,便无法被访问到。倘若我们希望函数中的局部变量仍然可以被访问到,这时候就需要通过闭包了。

先看一个经典使用闭包的例子:

// 以下代码例子,无论我们点击哪个div,输出的都是doms.length的值。
// 这是因为在我们触发click事件之前,for循环已经结束了,此时i的值为doms.length。
var doms = document.querySelectorAll('div')
for(var i=0;i<doms.length;i++){
	doms[i].onclick = function(){
  	console.log(i)
  }
}

// 解决方案:文章将闭包,这里用闭包方式解决
var doms = document.querySelectorAll('div')
for(var i=0;i<doms.length;i++){
  (function(i){
  	doms[i].onclick = function(){
  		console.log(i)
  	}
  })(i)
} // 点击任何div,均输出对应的i值。

聊闭包之前,我们先看下执行上下文环境。

执行上下文

JS中每段代码都会存在一个执行上下文环境中,而任何一个执行上下文都会存在于整体的执行上下文中。根据栈先进后出的特点,全局环境产生的执行上下文会最先入栈,存在于栈底。当新的函数调用时,变会产生新的执行上下文环境,压入栈中。当函数调用完成后,这个上下文环境及其中的数据都会被销毁,并弹出栈,从而进入之前的执行上下文环境中。

需要注意的是,处于活跃的执行上下文只能通过有一个。通过代码查看执行上下文的变化过程:

var a = 1     // 1. 进入全局执行上下文
var  foo = function(y){
  console.log(y)
}
var boo = function(x){
  var y = 10
  foo(x+y)  // 3. 执行foo执行上下文
}
boo(10)  // 2. 进入boo执行上下文

从进入第1行代码,进入全局执行上下文环境,此时执行上下文环境只存在全局执行上下文,推入栈底。

执行到第9行代码,调用boo()函数,进入boo()函数执行上下文,为当前活跃的执行上下文,加入栈中,

执行到第7行代码,调用foo()函数,进入fo0()函数执行上下文,为当前活跃的执行上下文,加入栈中,当foo()执行完毕,则foo执行上下文出栈销毁。回到bar()函数执行上下文,继续执行代码,执行完毕则出栈销毁。最后全局上下文执行完毕,栈被清空,流程执行结束。

倘若当代码执行完毕,执行上下文环境却无法干净的销毁,这就是我们要说的闭包。

闭包

官方对闭包有一个通用解释:

一个拥有许多变量和绑定了这些变量执行环境的表达式,通常是一个函数。

闭包有两个明显的特点:

  1. 函数拥有的外部变量的引用,在函数返回时,该变量仍处于活跃状态。
  2. 闭包作为一个函数返回时,其执行上下文不会被销毁,仍处于执行上下文中。

看个例子:

function fn(){
  var max = 1
  return function bn(x){
  	if(x>max){
    	console.log(x)
    }
  }
}
var f1 = fn()
f1(2)

代码开始执行时,生成全局执行上下文环境,并压入栈底;

当执行到第9行代码时,调用fn()函数,生成fn()函数上下文环境,并压入栈中,返回bn()函数,赋值给f1。

当执行到第10行代码时,调用f1()函数,因为f1函数中包含了对max的引用,而max变量是存在fn中的,因此fn函数执行上下文环境并不会被直接销毁,依然存在于执行上下文中。

当第10行代码执行结束,bn函数执行上下文环境才会被销毁,同时max变量的引用会被释放,fn函数的执行环境一同被销毁。

最后全局上下文环境执行完毕,栈被清空,流程执行结束。

从例子我们看出,闭包所存在的最大的问题就是消耗内存!

根据闭包的特点,我们可以利用闭包的特性来实现一些特性:

一、结果缓存

如果函数调用处理耗时,我们可以将结果在内存中缓存起来,下次执行时,若存在内存中,则直接返回,提升执行效率。

let cacheObj = function(){
  let cache = {} // 缓存对象
  return {
    search:function(key){
      if(key in cache){
         // 存在缓存中,直接返回
        return cache[key]
      }
      // 耗时的函数处理
      let result = dealFn(key) 
      // 更新缓存结果
      cache[key] = result 
      return result
    }
  }
}
let cacheBox = cacheObj()
cacheBox.search(1)
二、封装

模块化思想中是将具有一定特定的属性封装到一起,只需对外暴露对应的函数,并不关心内部实现。

let stack = function(){
  let stack = []
  return {
     push(val){
       stack.push(val)
    },
    pop(){
      return stack.pop()
    }
  }
}

小结

闭包使用合理,一定程度上能提高代码执行效率;如果使用不合理,则会造成内存浪费,性能下降。总结下闭包的优缺点。

优点:

  1. 包含函数内变量的安全,实现封装,防止变量流入其他环境发生命名冲突,造成环境污染。
  2. 在适当的时候,可以在内存中维护变量并缓存,提高执行效率。

缺点:

消耗内存:通常来说,函数的活动对象会随着上下文环境一起被销毁,但是由于闭包引用的是外部函数的活动对象,因此这个活动对象无法被销毁,因为闭包比一般函数消耗更多内存。

至此我们学习了闭包的特性、优缺点以及用法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值