了解下闭包?

代码已经关联到github: 链接地址 文章有更新也会优先在这,觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:

定义

一个函数和对其周围状态(Lexical Environment,词法环境,详细可看《执行上下文、作用域到底是什么?二者有什么关系》) 的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说

举个例子:

function bar(){
  let a = 1
  return function foo(){
    let b = 1
    return a + b
  }
}

bar()()//2

在上述的例子中,其作用域伪代码如下:

GlobalEnvironment = {
  EnvironmentRecord: { 
    // 标识符
  },
  outer: null
};

barEnvironment = {
  EnvironmentRecord: {
    a: 1,
    bar: '<fun>'
  }
  outer: GlobalEnvironment
};

fooEnvironment = {
  EnvironmentRecord: {
     b: 1,
  }
  outer: barEnvironment
};

因为静态作用域的特点,foo函数会从bar 中获取a的值,但是这里的bar 已经执行完毕,执行上下文已经出栈了,a 变量肯定也不存在了,这就与我们闭包的定义矛盾了。所以这里闭包函数对外层函数的变量必定会特殊处理,以保证闭包函数可以使用,我们可以认为闭包就是将引用的外部函数的变量中记住了,类似于引用变量,存储在了“堆”中。

用途

创建私有变量

闭包让函数以外也可以访问到函数内部的变量,这样可以用来创建私有变量,比如:

//简单闭包
function demo(){
  var a = 1
   return function(){
  	return a
  }
}	
var t = demo()
t()//1

常见的用法就是防抖和节流函数:

// 防抖
function debounce(fn,wait){
  	let timer  
    let that
    let arg	
    const delayFn = function(){
					const _that = that
          const _arg = arg
			    that = arg = undefined
      		timer = null
					return fn.apply(_that,_arg)
		}
		return function(){
      that = this
      arg = arguments
    	timer = setTimeout(()=>{
         if(timer){
           clearTimeout(timer);       
         }
      	 timer = setTimeout(delayFn, wait);  					
      })
    } 
}


// 节流
function throttle(fn,wait){
	let timer
  let that
  let arg
  const delayFn = function(){
    const _that = that
    const _arg = arg
    that = arg = undefined
    timer = null
    return fn.apply(_that,_arg)
  }
  return function(){
		that = this
		args = arguments
		if(!timer){
			 timer = setTimeout(delayFn, wait);  	
		}
	}
}

模块化

通过匿名立即执行函数,单独开辟一个作用域,使得只暴露相关接口函数,达到模块化。

//仅调用一次的设计模式——单体模式
var module = (function(global){
  var text = '1'
  var fn1 = function (){
    // ...
		console.log(text,1)
  }
  var fn2 = function fn2(){
    // ...
    console.log(text,2)
  }
  return {
    fn1: fn1,
    fn2: fn2
  }
  //也可以直接用暴露到window上
  //global.myModule={
  //  fn1: fn1,
  //  fn2: fn2
  // }
})(window)
module.fn1()//1 1
module.text //undefined
// myModule 暴露到window上
解决for循环中定时器函数取值异常问题

下文的代码中,我们期望在延迟函数中输出的是1,2,3,4,5,但实际上却并非如此:


//异常情况
for(var i = 1; i <= 5; i++) {
   setTimeout(function timer() {
         console.log(i);//6 6 6 6 6 6
    });        
}




这是由于setTimeout内的函数发现自身i这个变量未定义时,会作用域链去查找i,而这里的ivar声明的,只会声明一次,也就是说后续i++都会修改这个值,最终i变成了6,等到setTimeout函数执行时,输出的自然都是6
我们可以采用闭包的方式,单独开辟一个作用域给每次循环的setTimeout,这样输出就会跟我们所想的一致了。

//正常情况
for(var i = 1; i <= 5; i++) {
  (function(j){
    //这里是块级作用域 var j = i 
    setTimeout(function timer() {
         console.log(j);//1 2 3 4 5 6
    });  
	})(i);      
}


缺点

  • 错误的使用可能导致内存泄漏。闭包函数对象会一直引用它被创建时的作用域对象,由于这个引用一直存在,所以不会被浏览器的垃圾回收机制给回收。
  • 变量可能发生改变
//变量可能发生改变
function test() {
     let i = 1
     var result = function(){
        console.info(i) //2
     }
     i = 2
     return result
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值