JavaScript闭包详解

JavaScript闭包详解_第1张图片

1. 啥是闭包

如果一个函数内部包含嵌套函数并且嵌套函数被返回出来,那么内部函数被保存到了外部,就会生成闭包,此时边有一个外部的引用指向这个嵌套函数。

【例】如下,b函数被保存到了外部,输出结果为101、102

      function a() {
         var num = 100;

         function b() {
            num++;
            document.write(num);
         }
         return b;
      }
      var glob = a();
      glob();
      glob();

2. 闭包缺点

闭包会导致原有作用域链不能释放,造成内存泄漏。

3. 闭包作用

3.1. 实现公有变量

【例】函数累加器

      function add() {
         var count = 0;
         function sum() {
            count++;
            console.log(count + "\n");
         }
         return sum;
      }
      var counter = add();
      counter();
      counter();

3.2. 可以做缓存

【例1】简易存储结构,此时的 obj 中的方法被返回出来,可以临时存储变量 food

      function eater() {
         var food = "";
         var obj = {
            eat: function () {
               console.log("I am eating " + food);
               food = "";
            },
            push: function (myfood) {
               food = myfood;
            }
         }
         return obj;
      }
      var eater1 = eater();
      eater1.push("honey");
      eater1.eat();

【例2】题外话,代码注释中提了个问题,为什么那里的this指向window?

      function memory(f) {
         // 缓存处理结果
         var cache = {};
         return function () {
            // 将传入的参数作为键,传入类数组: 3,4,5
            var key = arguments.length + ":" + Array.prototype.join.call(arguments, ',');
            console.log(typeof (key) + ":" + key);
            if (key in cache) { // 如果值在缓存,直接读取返回
               console.log("值在缓存,直接读取返回:" + cache[key]);
               return cache[key];
            } else { // 否则执行计算,并把结果放到缓存,此处的 f 指阶乘运算
               // 此处的this指向window,这是为什么?
               console.log(this);
               cache[key] = f.apply(this, arguments);
               // 此处的arguments是一个类数组,但是阶乘运算只会取第一位进行阶乘运算
               console.log(cache[key]);
               return cache[key];
            }
         }
      }
      var factorial = function (n) { // 阶乘
         return (n <= 1) ? 1 : n * factorial(n - 1);
      }
      var factorialWithMemory = memory(factorial);
      factorialWithMemory(3, 4, 5); //3:3,4,5

【例3】闭包指向附加示例

      var num = 1,
         obj = {
            num: 2,
            getNum: function () {
               return (function () {
                  return this.num;
               })();
            }
         }
      console.log(obj.getNum()); // 1
      // 里面的自执行匿名函数不属于任何对象,他不是一个对象的方法(你如何使用点运算符调用?),也就是说他不属于任何一个对象,在非严格模式中,无指向的 函数内部的this,指向window

      // this的值取决于调用上下文,如果一个函数不是作为某个对象的方法被调用,那么this就是global object.否则就是该对象。

      // 对象实例化之后的this指的是本身,未实例化则this指的是调用者的对象

      // 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
      
      // new Function中的this指全局对象

      // eval中的this指调用上下文中的this

      // 要是想要改变这情况,可以这么写
      var num = 1,
         obj = {
            num: 2,
            getNum: function () {
               return (function (self) {
                  return self.num;
               })(this);
            }
         }
      console.log(obj.getNum()); // 2

参考

  1. 知乎 为什么在闭包中this会指向window?

之所以指向Window和《ECMAScript® 2015 Language Specification》有关,它指出了,如果thisArgumentsundefined,则thisValue就是 [[globalThis]]

引入this的初衷就是想在原型继承的情况下,拿到函数的调用者,如果函数没有指明调用者呢,那就让this指向全局对象。更多的情况在注视中写了。

锚点

3.3. 可以实现封装,属性私有化

【例】此时的 mathical 就是一个私有的方法,可以通过函数接口调用,但是无法直接访问

      var counter = (function () { // 立即执行函数,返回一个对象
         var value = 0 // 私有属性,无法直接访问
         var mathical = function (val) {
            value += val
         } // 私有方法,无法直接访问
         // 以下多个嵌套函数共享一个作用域链
         return {
            retValue: function () {
               return value
            },
            add: function () {
               mathical(+1)
            },
            dec: function () {
               mathical(-1)
            }
         }
      })()
      console.log(counter.retValue()) //0
      counter.add()
      console.log(counter.retValue()) //1
      counter.dec()
      console.log(counter.retValue()) //0

3.4. 模块化开发,防止污染全局变量

闭包既能重复使用局部变量,又不污染全局!

4. 使用闭包注意点

  1. this指向问题
  2. 内存消耗问题(建议在退出函数前将不适用的局部变量删除)
  3. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
  4. 外部变量的值是否改变
      function test() {
         var array = []
         for (var count = 0; count < 5; count++) {
            array[count] = function (value) { // 立即执行函数
               return function () {
                  console.log(value)
               }
            }(count)//此处若不是用立即执行函数则输出的结果都是5
         }
         return array
      }

      var result = test()
      result[0]()
      result[1]()
      result[2]()
      result[3]()
      result[4]()

参考:

  1. ruanyf 学习Javascript闭包(Closure)
  2. JavaScript学习笔记(十一) 闭包

5. 立即执行函数

针对初始化功能的函数(只执行一次,执行后再也不需要,不希望它继续占内存)

两种形式

  • (function () {} () ); W3C建议
  • (function () {} ) ();
为什么这样做就相当于立即执行函数?

答: 因为练习正常执行函数都是函数的引用或者说名称加上执行符号(),例如test();这里所说的函数的引用或者说名称实际上代表了函数的表达式,在函数表达式后面加上执行符号就代表执行函数,此处

      (function () {}());// W3C建议
      (function () {})();

【例】1

      //可以执行,因为var 变量名 = function(){}相当于函数表达式
      //执行之后再访问test1为undefined,
      //因为function作为立即执行函数执行后放弃了表达式的名称test1
      var test1 = function () {
         document.write("doing");
      }();

【例】2

      //会报错,不能执行,因为此处 function 是函数声明,而不是表达式
      function test2() {
         document.write("doing");
      }();
      // 这么写本身就存在语法错误了!

【例】3

      //不能执行,但是不会报错,对比test2由于立即执行符号()传了参数
      //所以系统会把函数后面的立即执行符号看作单独的一行表达式
      // function test3 (a, b, c, d) {
      //     document.write(a + b + c +d);}
      //(1,2,3,4);此处与直接写1,2,3,4其实一样
      function test3(a, b, c, d) {
         document.write(a + b + c + d + "doing");
      }(1, 2, 3, 4);

【例】4

      //能执行,因为+把函数声明转换为表达式,同时! - || &&都可以
      + function test4() {
         document.write("doing");
      }();

你可能感兴趣的:(javascript,闭包)