闭包

108 阅读3分钟

概念

函数运行的一种机制(不是某种代码形式)。

函数执行会形成一个私有上下文,如果上下文中的某些内容(一般指的是堆内存地址)被上下文以外的一些事物(例如变量、事件绑定等)所占用,则当前上下文不能被出栈释放(根据浏览器的垃圾回收机制GC所决定的,东西被引用着所以不能被释放), 这就是闭包的机制,形成一个不被释放的上下文。有的理解是函数形成就是闭包,不被释放的情况闭包存在的时间比较长而已,我比较认同这个观点。

保护:保护私有上下文中的 私有变量和外界互不影响。形成的保护机制,

保存:上下文不被释放,那上下文中的 私有变量和值 都被保存起来。可以供其下级上下文中使用。

弊端

如果大量使用闭包,会导致栈内存太大,不利于页面渲染,渲染变慢,性能受到影响。所以真实项目中,需要 合理使用闭包。某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的。死递归会导致内存溢出。

应用

需求:想要给页面中的所有的button加点击事件,然后输出索引值。(HTML代码就省略了哈)

var buttons = document.querySelectorAll('button')  // Nodelist 类数组集合
for(var i=0;i<buttons.length;i++){
 console.log(  buttons[i])
 buttons[i].onclick = function(){
     console.log(`当前点击所以:${i}`)
 } 
}

此代码实现不了,原因是当点击事件执行的时候 循环已经结束 此时i=3 执行点击事件代码 其中没有i变量 从全局变量找此时i=3。所以每次输出都是3

方案一:基于闭包的机制完成

每一轮循环都产生一个闭包,存储对应的索引,点击事件触发,执行对应的函数,让其上级上下文是闭包即可

for(var i=0;i<buttons.length;i++){
    // 每一轮循环都会形成一个闭包,存储一个私有变量i的值(当前循环传递的i的值)
    // 自执行函数执行,产生一个上下文EC(A) 私有形参变量i=0/1/2
    //  EC(A)上下文中,创建一个小函数,并且让全局buttons中的某一项占用创建的函数
  (function(i){ 
        buttons[i].onclick = function(){
            console.log(`当前点击所以:${i}`)
        } 
  })(i)
}

另一种写法:

for(var i=0;i<buttons.length;i++){ 
  buttons[i].onclick = (function(i){
    return function(){
        console.log(`当前点击所以:${i}`)
    }
  })(i)
}

说明:

var obj={
    // 把自执行函数执行的返回值(小函数)赋值给fn
    fn:(function(){
        console.log('大函数)
        return function(){
            console.log('小函数)
        }
    })()
}
obj.fn(); // 执行的是返回的小函数

另另一种写法,块级作用域,也是闭包

// 浏览器在每一轮循环的时候,帮助我们形成的“闭包” ,比我们自己写的性能好,但是都是闭包,好不到太多的哪里去。人家底层C++写的。
let buttons = document.querySelectorAll('button')  // Nodelist 类数组集合
for(let i=0;i<buttons.length;i++){
    // 父级 块级上下文:控制循环
    // 第一轮循环 私有的块级上下文EC(B1)  私有的i=0 执行以下代码
    // -> 当前上下文中创建一个小函数,被全局的按钮的Click占用了,EC(B1)不会被释放, “闭包”
    // 以下循环以此类推
    buttons[i].onclick = function(){
        console.log(`当前点击所以:${i}`)
    } 
}

性能不好,循环多少次产生多少次闭包,所以这种方案不太好。真实项目中减少这种应用。

方案二:自定义属性

var buttons = document.querySelectorAll('button')  // Nodelist 类数组集合
for(var i=0;i<buttons.length;i++){
    // 每一轮循环都给当前按钮(对象)设置一个自定义属性:存储它的索引
    buttons[i].myIndex = i
    buttons[i].onclick = function(){
        console.log(`当前点击所以:${this.myIndex}`)
    } 
}

方案三:事件委托

性能目前当中是最好滴,提高40%-60%,循环次数越多,性能越体现出来。

无论点击body上的谁,都会触发body的点击事件,ev.target是事件源:具体点击的是谁。后续事件相关中会具体说明。

//在html代码的结构上 给按钮设定自定义属性index,存储按钮的索引
document.body.onclick = function(ev){
    var target = ev.target,
        targetTag = target.tagName
    if(targetTag === 'BUTTON'){
        var index = target.getAttribute('index')
        console.log(`当前点击所以:${index}`)
    }
}

闭包作用域练习题

var a = 9;
function fn() {
    a = 0;
    return function (b) {
        return b + a++;
    }
}
var f = fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);

面试题:简述你对闭包的理解,以及其优缺点?

浏览器执行代码的时候,计算机会在内存中分配出一块栈内存供代码进栈执行,函数执行会形成一个私有上下文,如果上下文中的某些内容(一般指的是堆内存地址)被上下文以外的一些事物(例如变量、事件绑定等)所占用,根据浏览器的垃圾回收机制GC所决定的,则当前上下文不能被出栈释放的,就会形成一个不被释放的上下文。这就是闭包。其实我认为函数执行就会形成闭包,不被释放的情况闭包存在的时间比较长而已。

其实闭包这种机制,有很多作用: 第一:保护:保护私有上下文中的 私有变量和外界互不影响。形成的保护机制 第二:保存:上下文不被释放,那上下文中的 私有变量和值 都被保存起来。可以供其下级上下文中使用。 项目中经常这样用。

弊端: 如果大量使用闭包,会导致栈内存太大,不利于页面渲染,渲染变慢,性能受到影响。所以真实项目中,需要 合理使用闭包。 某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的

应用场景: 循环事件绑定会用到闭包,不过后来发现性能更好的是用事件委托; 包括一些进阶函数,柯里化函数都是拿闭包做的。 也包括自己封装的一些东西。