JavaScript闭包详解

image

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");
      }();

学技术、骑摩托

19 声望
0 粉丝
0 条评论
推荐阅读
JavaScript数据类型及变量
JavaScript的变量是松散型变量,和之前在python中的 动态机 很类似,这让我想起了之前的一篇讲python变量的文章,松散型指的是变量可以存任何类型数据,所谓变量只是仅仅是存值的占位符而已。如下操作完全没问题...

HuiDT阅读 1.3k

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.2k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2.1k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.7k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.9k评论 9

学技术、骑摩托

19 声望
0 粉丝
宣传栏