2019独角兽企业重金招聘Python工程师标准>>>

闭包是什么?

闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(Function Closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是函数和声明该函数的词法环境的组合。你也可以理解为闭包就是一个带状态(状态即引用的自由变量)的函数。

1、函数作为返回值

在高阶函数中函数不仅可以作为参数进行传递,还可以作为返回值返回。

?我们先来实现一个两个数字相加的例子

function add(x, y) {return x + y;
}
add(1, 2) // 3

但是如果我们不想立马计算,在后面根据需求在进行计算应该怎么办?答案是返回求和的方法,而不是返回求和的结果。

function lazy_add(x, y) {var add = function(x, y) {return x + y;}return add;
}

当我们调用lazy_add() 时返回的并不是求和的结果,而是返回的求和函数

var fun = lazy_add(1, 2)  // function add(){}

当真正需要进行计算的时候我们可以调用fun()

var sum = fun();    //3 

那么我们调用的是fun方法,也并没有将他怎么就知道是1和2相加了呢?原因就是fun方法是通过lazy_add方法传递两个参数1和2生成的,lazy_add方法在返回add方法的同时将变量x和y也一并保存返回了。这就形成了自由变量x,y和函数add组成的闭包。

需要注意的是,当我们调用lazy_add()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_add(1, 2);
var f2 = lazy_sum(1, 2);
f1 === f2; // false

2、循环中的闭包

?我们再看一个例子

function count() {var arr = [];for (var i=1; i<=3; i++) {arr.push(function () {return i * i;});}return arr;
}var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的代码中在每次循环中生成了一个函数放到了数组中。你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:三个都是 16

为什么会这样呢?原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16

所以返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果非要引用循环变量应该怎么办?那就是在循环变量的外层包一层立即执行函数,将循环变量绑定到立即执行函数的入参,如下:

function count() {var arr = [];for (var i=1; i<=3; i++) {arr.push((function (n) {return function () {return n * n;}})(i));}return arr;
}var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];f1(); // 1
f2(); // 4
f3(); // 9

另外可以通过ES6中的语法更轻松地解决这个问题,那就是通过let声明循环变量,该方法可以避免使用过多的闭包(过多的闭包影响性能)代码如下:

function count() {var arr = [];for (let i=1; i<=3; i++) {arr.push(function () {return i * i;});}return arr;
}var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。

说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?当然不是!让我们接着往下看。

3、函数柯里化(curry)

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。读起来可能优点绕,看了下面的例子你就会明白。

想必大家应该都用过 Math.pow(x, y)方法,是的这个就是计算x的y次方的方法,但是由于我们通常都是计算2次方和3次方,所以我们要通过闭包基数对该方法进行柯里化。

function make_pow(n) {return function (x) {return Math.pow(x, n);}
}//创建两个新的方法
var pow2 = make_pow(2);
var pow3 = make_pow(3);pow2(5); // 25
pow3(5); // 125

4、对象封装

在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。

在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。下面我们就用javascript创建一个包含私有变量的计数器。

function create_counter(initial) {var x = initial || 0;return {inc: function () {x += 1;return x;}}
}var c1 = create_counter(1);
c1.inc(); // 2
c1.inc(); // 3
c1.inc(); // 4

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量

5、闭包性能考量

如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是,每个对象的创建)。

function MyObject(name, message) {this.name = name.toString();this.message = message.toString();
}
MyObject.prototype.getName = function() {return this.name;
};
MyObject.prototype.getMessage = function() {return this.message;
};var obj1 = new MyObject('name1', 'message1');
var obj2 = new MyObject('name2', 'message2');
obj1.getName == obj2.getName; // true

上面的代码中构造的每个实例中的方法都指向同一个

但是如果是用闭包封装的对象会怎么样,接着用之前的计数器例子做一下测试

var count1 = create_counter(1);
var count2 = create_counter(2);count1.inc == count2.int; //false

从测试结果可以看出每个对象中的方法都是独立的方法,这样违背了对象方法公用原则

万物皆有利弊,所以具体要怎么使用还是要仔细考量。

转载于:https://my.oschina.net/kimyeongnam/blog/733809

JavaScript 闭包详解相关推荐

  1. JavaScript闭包详解及案例

    JavaScript闭包详解及案例 一. 变量作用域 函数内部可以使用全局变量 函数外部不可以使用局部变量 当函数执行完毕时,本作用域内的局部变量会被销毁 二. 闭包 闭包:有权访问另一个函数作用域中 ...

  2. JavaScript闭包详解

    文章出自个人博客https://knightyun.github.io/2018/05/31/js-closure,转载请申明 变量作用域 首先来了解一下Javascript中变量的作用域,除了常见的 ...

  3. JavaScript重难点解析4(作用域与作用域链、闭包详解)

    JavaScript重难点解析4(作用域与作用域链.闭包详解) 作用域与作用域链 作用域 作用域与执行上下文 作用域链 闭包 闭包理解 将函数作为另一个函数的返回值 将函数作为实参传递给另一个函数调用 ...

  4. Javascript 函数详解

    Javascript 函数详解 1)函数声明: 通过关键字function定义,把函数作为变量来声明 函数声明后不会立即执行,会在我们需要的时候调用到. <script>function ...

  5. JavaScript正则表达式详解(一)正则表达式入门

    JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...

  6. JavaScript事件详解-jQuery的事件实现(三)

    正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...

  7. Swift 中的Closures(闭包)详解

    Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...

  8. JavaScript原型详解(通俗易懂)

    JavaScript原型详解 1,前言 下面是2008年Github创建以来,各种编程语言的排名情况 其中JavaScript自2013年之后就盘踞第一名,成为github上被使用最多的语言,早期,J ...

  9. JavaScript对象详解

    转载请注明预见才能遇见的博客:https://my.csdn.net/ 原文地址:https://blog.csdn.net/weixin_42787326/article/details/81297 ...

最新文章

  1. 修改Activity响应音量控制键修改的音频流
  2. python下载opencv库_Window系统下Python如何安装OpenCV库
  3. 路由器01---k2刷Pandora
  4. 接口方法上的注解无法被@Aspect声明的切面拦截的原因分析
  5. openstack部署中的错误总结
  6. spring @component的作用详细介绍
  7. 易语言 取自定义数据类型的大小
  8. 如何获得CSDN下载积分
  9. Date 日期时间工具类,针对日期的一些常用的处理方法
  10. 使用Neo4j分析《权力的游戏》
  11. 如何进行数据安全管理体系建设?
  12. 高通烧录报ufs需要重新provision
  13. 【RDMA】intel 因特尔RDMA 驱动和ibverslib 库安装笔记
  14. PTB中的Screen函数
  15. 助力无人船舶,开拓水上智能
  16. 3dmax入门 | 学3d建模必备软件技能基础教学
  17. FFmpeg源码分析:视频滤镜介绍(上)
  18. 【javaScript获取时间,计算任意两个日期之间相隔的天数】计算任意两个日期之间相隔的天数
  19. 对象映射框架MapStruct与orika的简单使用
  20. 西安电子科技大学-信号与线性系统大作业-歌曲人声消除

热门文章

  1. 实现redis 手动_Redis精华所在,一口气说完Redis的主从复制和哨兵模式
  2. 根据字符串选择类并完成类的初始化--方法一
  3. 共享x轴,使用双y轴
  4. html一级二级菜单,纯JS添加一级二级菜单的代码
  5. QT键盘响应卡顿的解决方法
  6. 信息系统项目管理师:第4章:项目整体管理与变更管理-章节真题
  7. Leaflet中使用leaflet.easyPrint插件实现打印效果
  8. MobileIMSDK怎样修改Server端和安卓端TCP连接方式时报文的的限制大小
  9. Winform中实现文件批量更名器(附代码下载)
  10. Winfrom中设置ZedGraph显示多个标题(一个标题换行显示)效果