文章目录

  • 简介
  • 函数中的函数
  • Closure闭包
  • 使用闭包实现private方法
  • 闭包的Scope Chain
  • 闭包常见的问题
  • 闭包性能的问题
  • 总结

简介

闭包closure是javascript中一个非常强大的功能。所谓闭包就是函数中的函数,内部函数可以访问外部函数的作用域范围,从而可以使用闭包来做一些比较强大的工作。

今天将会给大家详细介绍一下闭包。

函数中的函数

我们提到了函数中的函数可以访问父函数作用域范围的变量,我们看一个例子:

function parentFunction() {var address = 'flydean.com'; function alertAddress() { alert(address); }alertAddress();
}
parentFunction();

上面的例子中,我们在parentFunction中定义了一个变量address,在parentFunction内部定义了一个alertAddress方法,在该方法内部访问外部函数中定义的address变量。

上面代码运行是没问题的,可以正确的访问到数据。

Closure闭包

函数中的函数有了,那么什么是闭包呢?

我们看下面的例子:

function parentFunction() {var address = 'flydean.com'; function alertAddress() { alert(address); }return alertAddress;
}
var myFunc = parentFunction();
myFunc();

这个例子和第一个例子很类似,不同之处就是我们将内部函数返回了,并且赋值给了myFunc。

接下来我们直接调用了myFunc。

myFunc中访问了parentFunction中的address变量,虽然parentFunction已经执行完毕返回。

但是我们在调用myFunc的时候,任然可以访问到address变量。这就是闭包。

闭包的这个特性非常拥有,我们可以使用闭包来生成function factory,如下所示:

function makeAdder(x) {return function(y) {return x + y;};
}var add5 = makeAdder(5);
var add10 = makeAdder(10);console.log(add5(2));  // 7
console.log(add10(2)); // 12

其中add5和add10都是闭包,他们是由makeAdder这个function factory创建出来的。通过传递不同的x参数,我们得到了不同的基数的add方法。

最终生成了两个不同的add方法。

使用function factory的概念,我们可以考虑一个闭包的实际应用,比如我们在页面上有三个button,通过点击这些button可实现修改字体的功能。

我们可以先通过function factory来生成三个方法:

function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};
}var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

有了这三个方法,我们把DOM元素和callback方法绑定起来:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

使用闭包实现private方法

对比java来说,java中有private访问描述符,通过private,我们可以指定方法只在class内部访问。

当然,在JS中并没有这个东西,但是我们可以使用闭包来达到同样的效果。

var counter = (function() {var privateCounter = 0;function changeBy(val) {privateCounter += val;}return {increment: function() {changeBy(1);},decrement: function() {changeBy(-1);},value: function() {return privateCounter;}};
})();console.log(counter.value());  // 0.counter.increment();
counter.increment();
console.log(counter.value());  // 2.counter.decrement();
console.log(counter.value());  // 1.

我们在父function中定义了privateCounter属性和changeBy方法,但是这些方法只能够在内部function中访问。

我们通过闭包的概念,将这些属性和方法封装起来,暴露给外部使用,最终达到了私有变量和方法封装的效果。

闭包的Scope Chain

对于每个闭包来说,都有一个作用域范围,包括函数本身的作用域,父函数的作用域和全局的作用域。

如果我们在函数内部嵌入了新的函数,那么就会形成一个作用域链,我们叫做scope chain。

看下面的一个例子:

// global scope
var e = 10;
function sum(a){return function(b){return function(c){// outer functions scopereturn function(d){// local scopereturn a + b + c + d + e;}}}
}console.log(sum(1)(2)(3)(4)); // log 20

闭包常见的问题

第一个常见的问题就是在循环遍历中使用闭包,我们看一个例子:

function showHelp(help) {document.getElementById('help').innerHTML = help;
}function setupHelp() {var helpText = [{'id': 'email', 'help': 'Your e-mail address'},{'id': 'name', 'help': 'Your full name'},{'id': 'age', 'help': 'Your age (you must be over 16)'}];for (var i = 0; i < helpText.length; i++) {var item = helpText[i];document.getElementById(item.id).onfocus = function() {showHelp(item.help);}}
}setupHelp();

上面的例子中,我们创建了一个setupHelp函数,setupHelp中,onfocus方法被赋予了一个闭包,所以闭包中的item可以访问到外部function中定义的item变量。

因为在循环里面赋值,所以我们实际上创建了3个闭包,但是这3个闭包共享的是同一个外部函数的作用域范围。

我们的本意是,不同的id触发不同的help消息。但是如果我们真正执行就会发现,不管是哪一个id,最终的消息都是最后一个。

因为onfocus是在闭包创建完毕之后才会触发,这个时候item的值实际上是变化的,在循环结束之后,item的值已经指向了最后一个元素,所以全部显示的是最后一条数据的help消息。

怎么解决这个问题呢?

最简单的办法使用ES6中引入的let描述符,从而将item定义为block的作用域范围,每次循环都会创建一个新的item,从而保持闭包中的item的值不变。

  for (let i = 0; i < helpText.length; i++) {let item = helpText[i];document.getElementById(item.id).onfocus = function() {showHelp(item.help);}}

还有一种方法,就是再创建一个闭包:


function makeHelpCallback(help) {return function() {showHelp(help);};
}for (var i = 0; i < helpText.length; i++) {var item = helpText[i];document.getElementById(item.id).onfocus = makeHelpCallback(item.help);}

这里用到了之前我们提到的function factory的概念,我们为不同的闭包创建了不同的作用域环境。

还有一种方法就是将item包含在一个新的function作用域范围之内,从而每次创建都是新的item,这个和let的原理是相似的:

  for (var i = 0; i < helpText.length; i++) {(function() {var item = helpText[i];document.getElementById(item.id).onfocus = function() {showHelp(item.help);}})(); }

第二个常见的问题就是内存泄露。

 function parentFunction(paramA){var a = paramA;function childFunction(){return a + 2;}return childFunction();}

上面的例子中,childFunction引用了parentFunction的变量a。只要childFunction还在被使用,a就无法被释放,从而导致parentFunction无法被垃圾回收。

闭包性能的问题

我们定义了一个对象,并且通过闭包来访问其私有属性:

function MyObject(name, message) {this.name = name.toString();this.message = message.toString();this.getName = function() {return this.name;};this.getMessage = function() {return this.message;};
}

上面的对象会有什么问题呢?

上面对象的问题就在于,对于每一个new出来的对象,getName和getMessage方法都会被复制一份,一方面是内容的冗余,另一方面是性能的影响。

通常来说,我们将对象的方法定义在prototype上面:

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;
};

注意,我们不要直接重写整个prototype,这样会导致未知的错误,我们只需要根据需要重写特定的方法即可。

总结

闭包是JS中非常强大和有用的概念,希望大家能够喜欢。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/js-closure/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

javascript中的闭包closure详解相关推荐

  1. JavaScript中DOM对象的详解

    *** JavaScript中DOM对象的详解*** DOM对象:Document Object Model,文档对象模型.也称为document(文档对象),是HTML页面当前窗体的内容,是连接JS ...

  2. php 实现setinterval,JavaScript中setInterval的使用详解

    相信很多大伙都知道JavaScript中setInterval的作用是在播放动画的时,每隔一定时间就调用函数,方法或对象,也有很多小伙伴对此也只是知道setInterval的定义.也是一知半解,今天我 ...

  3. Javascript中的Document对象详解

    Document对象详解 document 文挡对象 - JavaScript脚本语言描述           -------------------------------------------- ...

  4. Python闭包(closure)详解

    阿巴阿巴阿巴,老Amy 来啦~由于闭包这家伙是块难啃得的骨头,所以 老Amy 就想着用一篇逐字稿帮助大家来搞定它! 首先,在 get 闭包这个知识点之前,我们已经知道了函数的作用域.那现在考考大家,思 ...

  5. JavaScript 中常见排序算法详解

    十大经典算法 一张图概括: 名词解释: n:数据规模 k:"桶"的个数 In-place:占用常数内存,不占用额外内存 Out-place:占用额外内存 稳定性:排序后2个相等键值 ...

  6. javascript 中match函数使用详解

    javascript中的match函数是使用正则表达式对字符串进行查找,并将查找的结果作为数组返回,在实际开发中非常的有用,使用方法如下: stringObj.match(rgExp) 其中strin ...

  7. JavaScript中的valueOf方法详解

    Object.prototype.valueOf() valueOf() 方法返回指定对象的原始值. 语法 object.valueOf() 返回值 返回值为该对象的原始值. 描述 JavaScrip ...

  8. JavaScript中的window对象详解

    1.window对象简介 (1)在JavaScript中,一个浏览器窗口就是一个window对象. (2)一个窗口就是一个window对象,这个窗口里面的HTML文档就是一个document对象,do ...

  9. php 中的闭包函数详解

    面向对象变成语言代码的复用主要采用继承来实现,而函数的复用,就是通过闭包来实现.这就是闭包的设计初衷. 注:PHP里面闭包函数是为了复用函数而设计的语言特性,如果在闭包函数里面访问指定域的变量,使用u ...

最新文章

  1. Android 监听APP进入前台、后台
  2. c++编码风格指南_100%正确编码样式指南
  3. scala helloworld
  4. 阻塞与非阻塞都是什么呢?
  5. 【模拟电子技术基础】学习笔记 第一章 半导体二极管
  6. 64位计算机安装xp,xp系统一键安装64位win7
  7. 【matlab】iir滤波器
  8. GAGE EON采集卡
  9. Nginx专题:nginx+tomcat实现动静分离
  10. “2021流行趋势发布·新品试骑活动”引爆羊城
  11. ATM和电路交换和分组交换区别
  12. 保护环境从我做起(10件力所能及之事)
  13. Elasticsearch 数据迁移方案
  14. [LeetCode]91.Decode Ways
  15. H264/H265码流类型
  16. Javascript读书笔记(六)函数作用域,声明提前,作用域链,闭包
  17. 修改Linux的swap空间实现进程注入
  18. 2021最新个人主页源码-靳吉朕的个人主页
  19. Res2Net: 一种新的多尺度主干体系结构(Res2Net: A New Multi-scale Backbone Architecture )
  20. Non-function value encountered for slot “role“. Prefer function slots for better performance.

热门文章

  1. 工资管理信息系统java_java+sql server,工资管理信息系统,源程序+数据库+报告
  2. socket穿透代理代码(C++版)
  3. Win32多线程编程(6) — 多线程协作及线程的池化管理
  4. StackOverflow 上面最流行的 7 个 Java 问题!| 值得一看
  5. 多线程小抄集(新编四)
  6. 太卷了~ 八股文,算法张口就来?2022版Java面试手册帮你全拿捏
  7. 搞定系统设计 01:从 0 到百万用户的系统
  8. OS- -进程详详解
  9. 音视频技术开发周刊 | 183
  10. 音视频技术开发周刊 | 172