likes
comments
collection
share

什么是闭包?它到底用来干什么?.md

作者站长头像
站长
· 阅读数 12
什么是闭包

很多小伙伴对闭包的概念不够清晰,觉得知道一点,但是又不是很对或者不够全面,其实在平常的开发中很多时候都用到了闭包,只是剥离出他们的本质,导致经常一知半解。

闭包的本质就是在一个函数内部创建另一个函数,比较经典的就是一个函数可以访问其外部函数作用域中的变量。闭包是将函数内部和函数外部连接起来的桥梁!!! 在 js 中,函数是一等公民,因此函数可以作为参数传递给其他函数,也可以从其他函数中返回。当一个函数返回另一个函数时,返回的函数可以访问其父函数的作用域中的变量,这就是闭包的实现。

我的理解是:跨作用域访问变量就形成了闭包

闭包的常用用途
  • 闭包的一个常见用途是创建私有变量。
  • 创建一个一直存在的引用
  • 作为参数被传入或者是作为返回值被返回 下面是一个简单的闭包示例:
function calculate() {
  var count = 0;  // 私有化了
  return {  // 返回函数
    add: function() {
      count++;
    },
    sub: function() {
      count--;
    },
    getCount: function() {
      return count;
    }
  };
}

var counter = calculate(); // 一直存在的引用
counter.add();
counter.add();
console.log(counter.getCount()); // 输出 2

在这个例子中,calculate 函数返回一个对象,该对象具有 addsubgetCount 方法。这些方法都可以访问 count 变量,但是外部时无法直接访问 count 变量。因此, count 变量是私有的,只能通过返回的对象方法来访问和修改。这就是 私有变量

而且,由于 counter 一直被引用,所以不会被释放,每次调用 add 方法都是基于上一次计算之后的 count 再去做计算,所以调用两次 addcount 就为 2

说到这儿,想起一个经典的面试题

for(var i = 1; i < 5; i++){
	setTimeout(() => {
		console.log(i)
	}, i * 1000)
}
// 输出45

要求把上面的代码改一下,让其输入1、2、3、4 一般的就是直接把 var 改成 let ,但是闭包也可以做到

for (var i = 1; i < 5; i++) {
  //闭包
  (function (i) {
    setTimeout(() => console.log(i), 1000 * i)
  })(i)
}
// 1234
闭包的特性
  • 函数套函数(这儿不是必须的,因为闭包就是跨作用域访问变量,外部访问内部或者是内部访问外部,正常情况下,从外部访问不了内部变量,那么就需要 return 一个函数出来,让你在外部可以使用这个函数和变量,不然你无法使用这个闭包!return 出一个函数跟闭包本身是无关的,因为只要跨作用域访问了变量,就已经形成闭包了,所以 return 出来仅仅是为了方便使用,也就是上面说的 闭包就是将函数内部和函数外部连接起来的一座桥梁
  • 内部函数可以直接使用外部函数的局部变量或参数
  • 变量或参数不会被垃圾回收机制回收(引用一直存在的情况下);
闭包的优点
  • 跨作用域访问变量
  • 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突(封闭性)
  • 一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在(持久性)
闭包的缺点
  • 被引用的私有变量不能被销毁,增大了内存消耗,在 IE 中可能导致内存泄露,解决方法是在退出函数之前,将不使用的局部变量全部删除或者是设置为 null
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值

再来看看常见的一些闭包案例

function foo() {
  var local = 1
  function bar() {
    local++ // 内部访问外部的 local
    return local
  }
  return bar  // return bar 函数 方便闭包被使用
}

var func = foo()
console.log('func()',func()); // 2
function fn() {
  var i = 10;
  return function (n) {
    console.log(n + (++i));
  }
}
var f = fn(); //首先把fn执行,然后把执行的结果赋值给f,函数执行只要看函数里面有没有return
f(15);    //26  //引用,执行完不会立即释放
f(20);    //32  // 为什么是32 因为 f(20)表示这个f函数还在调用,那个 var f = fn()就还在用,就不会销毁,就形成闭包,缓存变量
fn()(15); //26  //没有赋值给其他变量,也就没有引用,每次执行完内存释放,作用域销毁
fn()(15); //26  // 为什么是正常的?因为这个是直接fn()()调用  这杨没有赋值,执行完就销毁了
fn()(20); //31
fn()(30); //41
f(30);    //43  // 原理一样 赋值给f,导致成为全局变量 有形成了闭包 
var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function () {
    return function () {
      return this.name;
    };
  }
};
console.log('object.getNameFunc()()', object.getNameFunc()()); // The Window
var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function () {
    var that = this;
    return function () {
      return that.name;
    };
  }
};
console.log('object.getNameFunc()()', object.getNameFunc()()); // My Object

拓展: 谷歌浏览器: 我们开辟一个内存,可能有一些其他的变量等占用了这个内存,谷歌浏览器都看这个内存还有没有被占用,如果发现有没有被占用的内存了,就自己帮我们回收了(内存释放) 火狐和IE: 我们开个一个内存,当我们引用了它,就在内存中记录一个数,增加一个引用浏览器就把这个数+1,减少一个引用,浏览器就把这个数-1...当减到零的时候浏览器就把这个内存释放了;但是有些情况下(尤其是 IE )记着记着就弄乱了,内存就不能释放了(浏览器的内存泄露)

var obj={} 当我们这个对象 obj 使用完成了,手动的搞一下 obj=null 内存就会释放掉