闭包总结(main)

313 阅读3分钟

1.如何理解闭包(closure)

  1. 定义:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的其他变量,如果返回的这个函数在外部被执行,就产生了闭包。

  2. 闭包就是能够读取其他函数内部变量的函数。它是定义在一个函数内部的函数。即在外面可以调用函数中的函数的变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包的本质还是函数,只不过这个函数绑定了上下文环境。闭包就是依赖于这种作用域链的机制,由于只有子函数作用域能访问父作用域中的变量, 所以闭包就像"定义在函数内部的函数"。

  3. 根据作用域链的规则,底层作用域没有声明的变量,会向上一级找。找到就返回,没找到就一直找,直到window变量,没有就返回undefined。

  4. 变量的作用域:
    要理解闭包,首先必须理解javascript的特殊的变量作用域。 变量的作用域分类: 全局变量和局部变量。
    特点:
    a. 函数内部可以读取函数外部的全局变量; 在函数外部无法读取函数内部的局部变量
    b.函数内部声明变量的时候, 一定要使用var命令, 如果不用的话, 你实际声明了一个全局变量。

  5. 使用闭包的注意点
    a. 滥用闭包,会造成内存泄漏。由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
    b. 会改变父函数内部变量的值。所以,如果你把父函数当做对象(object)使用,把闭包当做它的公共方法(Public Method), 把内部变量当做它的私有属性(Private value) , 这时一定要小心,不要随便改变父函数内部变量的值

  6. 闭包的三大特性:

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收
  1. 闭包最大用处有两个
  • 一是可以读取函数内部的变量
  • 二是让这些变量的值始终保持在内存中

2. 最基础的闭包函数

let count = 10;
let addFn = () => {
    let count = 0;
    return () => {
        count++;
        console.log(count);
        return count;
    }
}
let res = addFn();
res(); // 1
res(); // 2

3. 闭包处理数字的加减乘除

用法: 在闭包函数内部, 定义一个对象, 对象内部声明加减乘除的方法来操作函数的参数变量。 最后返回对象。

3.1基础的加减乘除

let closure = (n) => {
    let innerObj = {
        add5: () => {
            n += 5;
            return n;
        },
        delete8: () => {
            n -= 8;
            return n;
        },
        deleteDefine: (defineNum) => {
            n -= defineNum;
            return n;
        },
        divide2: () => {
            n /= 2;
            return n;
        }
    }
    return innerObj;
}
let middle = closure(100);
let res = middle.add5(); // 105
middle.delete8(); // 97
middle.deleteDefine(12); // 85
let res2 = middle.deleteDefine(20); // 65
let res3 = middle.divide2() // 32.5
console.log(res, res2, res3);

3.2 升级后的加减乘除

let closure = (n) => {
    let innerObj = {
        add: (defineNum) => {
            n += defineNum;
            return n;
        },
        delete: (defineNum) => {
            n -= defineNum;
            return n;
        },
        multiply: (defineNum) => {
            n *= defineNum;
            return n;
        },
        divide: (defineNum) => {
            n /= defineNum;
            return n;
        }
    }
    return innerObj;
}
let middle = closure(100);
let res = middle.add(5); // 105
middle.delete(8); // 97
middle.delete(12); // 85
let res2 = middle.multiply(2); // 170
let res3 = middle.divide(10) // 17
console.log(res, res2, res3);

4. 闭包相关面试题

let outFn = () => {
    let n = 999;
    nAdd = () => {
        n += 1;
        console.log(n);
    }

    let inFn = () => {
        n++;
        console.log(n);
    }
    return inFn;
}
let n = 668;
console.log(n); //668
let res = outFn();
res();  //1000
nAdd(); // 1001
res(); // 1002

5. 模块中的单例模式

  1. 下面我们将模块函数转换成了IIFE, 立即调用这个函数并将返回值直接赋值给单例的模块标识符 foo
let foo = (function CoolModule(){
  let something = "cool";
  let another = [1, 2, 3];

  function doSomething(){
    console.log(something)
  };

  function doAnother(){
    console.log(another.join('!'));
  }

  return {
    doSomething,
    doAnother,
  }
})();

foo.doSomething() // coll
foo.doAnother() // 1!2!3!
  1. 模块莫斯另一个简单但强大的用法是命名将要作为公共API返回的对象