闭包从入门到放弃

44 阅读2分钟

概念

闭包:一个函数嵌套另一个函数,并且子函数可以访问外部的变量,就是闭包;


function fn() {
  var a = 1;
  return function fn1 () {
    console.log(a)
  }
}

var fn2 = fn(); // 返回fn1这个函数
fn2(); // a = 1

用途

计数器


function counter() {
  var count = 0;
  return function () {
    return count++;
  }
};

var fn = counter();
console.log(fn()) // 0
console.log(fn()) // 1
console.log(fn()) // 2
// 闭包使用完毕后,要进行释放,否则会造成内存泄漏
fn = null;

// 如果使用自调用,比如:fn()(),这样会导致每次调用都会被释放
// 就不会出现计数器

为什么是0,1,2,而不是1,1,1?

原因:fn 持有了 counter 函数的返回值,导致内部函数没有被释放,由于是闭包,并且 count 被引用,不满足垃圾回收机制,每次调用都使用的同一个 count ,因为存在引用,不会被垃圾回收掉,就造成每次调用的时候,保留了执行上下文中的变量,所以计数器会正常加1,而不是每次都是从0开始➕1

注意:一般使用完闭包后,必须要进行释放,否则就会引起内存泄漏,因为不释放的话,如果你反复调用 counter 函数创建多个闭包,就会造成内存累加,就会造成内存泄漏。

闭包可以封装对象的私有属性和方法


function Person() {
  var age;

  function setAge(n) {
    age = n;
  }

  function getAge() {
    return age;
  }
  
  return {
    setAge,
    getAge
  }
}

var p1 = Person();
p1.setAge(18);
console.log(p1.getAge()) // 18

循环+闭包的用法


function fn() {
  var arr = [];
  for(var i = 0; i < 10; i++) {
    arr[i] = function () {
      return i;
    }
  }

  return arr;
}

var fn1 = fn();
console.log(fn1[0]()) // 10
console.log(fn1[1]()) // 10

由于作用域和闭包的问题,当 fn1 已经创建完成的时候,内部函数使用的都是同一个 i ,并且在调用的时候 i 已经等于 10 了。

如果想要获取 0 1 2 3 这样的返回;

解决方案:

  • 立即执行函数(IIFE)

function fn() {
  var arr = [];
  for(var i = 0; i < 10; i++) {
    (function (n) {
      arr[n] = function () {
      return n;
    }
    })(i)
  }

  return arr;
}

var fn1 = fn();
console.log(fn1[0]()) // 0
console.log(fn1[1]()) // 1

通过自执行函数,这样每次循环都形成了一个独立的作用域,每次的 i 当作变量传入自执行函数中,内部函数会接收一个形参,这个形参的值就是档次循环的 i ;

  • let 块级作用域

function fn() {
  var arr = [];
  for(let i = 0; i < 10; i++) {
    arr[i] = function () {
      return i;
    }
  }

  return arr;
}

var fn1 = fn();
console.log(fn1[0]()) // 0
console.log(fn1[1]()) // 1

块级作用域,在for循环 + let 的时候,每次循环都是一个独立的块级作用域,并且把 i 值存储,所以执行的时候获取的都是当次执行的 i ;