Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

闭包 #26

Open
wqhui opened this issue Apr 18, 2022 · 0 comments
Open

闭包 #26

wqhui opened this issue Apr 18, 2022 · 0 comments

Comments

@wqhui
Copy link
Owner

wqhui commented Apr 18, 2022

定义

一个函数和对其周围状态(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
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant