闭包closure是javascript中一个很是强大的功能。所谓闭包就是函数中的函数,内部函数能够访问外部函数的做用域范围,从而可使用闭包来作一些比较强大的工做。javascript
今天将会给你们详细介绍一下闭包。java
咱们提到了函数中的函数能够访问父函数做用域范围的变量,咱们看一个例子:闭包
function parentFunction() { var address = 'flydean.com'; function alertAddress() { alert(address); } alertAddress(); } parentFunction();
上面的例子中,咱们在parentFunction中定义了一个变量address,在parentFunction内部定义了一个alertAddress方法,在该方法内部访问外部函数中定义的address变量。函数
上面代码运行是没问题的,能够正确的访问到数据。性能
函数中的函数有了,那么什么是闭包呢?字体
咱们看下面的例子:this
function parentFunction() { var address = 'flydean.com'; function alertAddress() { alert(address); } return alertAddress; } var myFunc = parentFunction(); myFunc();
这个例子和第一个例子很相似,不一样之处就是咱们将内部函数返回了,而且赋值给了myFunc。prototype
接下来咱们直接调用了myFunc。code
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;
对比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。
看下面的一个例子:
// global scope var e = 10; function sum(a){ return function(b){ return function(c){ // outer functions scope return function(d){ // local scope return 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的博客
欢迎关注个人公众号:「程序那些事」最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!