javascript中闭包closure的深入讲解


Posted in Javascript onMarch 03, 2021

简介

闭包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 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中非常强大和有用的概念,希望大家能够喜欢。

到此这篇关于javascript中闭包closure的文章就介绍到这了,更多相关javascript闭包closure内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
javascript获取当前鼠标坐标的方法
Jan 10 Javascript
jquery实现简易的移动端验证表单
Nov 08 Javascript
简单理解Vue条件渲染
Dec 03 Javascript
JavaScript队列的应用实例详解【经典数据结构】
Apr 12 Javascript
详解Angular2中Input和Output用法及示例
May 21 Javascript
详谈commonjs模块与es6模块的区别
Oct 18 Javascript
微信小程序录音与播放录音功能
Dec 25 Javascript
函数式编程入门实践(一)
Apr 20 Javascript
vue实现手机端省市区区域选择
Sep 27 Javascript
如何在wxml中直接写js代码(wxs)
Nov 14 Javascript
ElementUI Tree 树形控件的使用并给节点添加图标
Feb 27 Javascript
基于JavaScript实现控制下拉列表
May 08 Javascript
详解Vue.js 可拖放文本框组件的使用
Mar 03 #Vue.js
详解vue3中组件的非兼容变更
Mar 03 #Vue.js
three.js如何实现3D动态文字效果
Mar 03 #Javascript
Webpack3+React16代码分割的实现
Mar 03 #Javascript
微信小程序input抖动问题的修复方法
Mar 03 #Javascript
微信小程序组件生命周期的踩坑记录
Mar 03 #Javascript
vite2.0+vue3移动端项目实战详解
Mar 03 #Vue.js
You might like
ThinkPHP3.1新特性之动态设置自动完成及自动验证示例代码
2014/06/23 PHP
laravel-admin 在列表页添加自定义按钮的例子
2019/09/30 PHP
FCK调用方法..
2006/12/21 Javascript
namespace.js Javascript的命名空间库
2011/10/11 Javascript
js setTimeout 参数传递使用介绍
2013/08/13 Javascript
使用jquery组件qrcode生成二维码及应用指南
2015/02/22 Javascript
jquery插件NProgress.js制作网页加载进度条
2015/06/05 Javascript
js实现仿阿里巴巴城市选择框效果实例
2015/06/24 Javascript
JS实现的表格操作类详解(添加,删除,排序,上移,下移)
2015/12/22 Javascript
基于nodejs 的多页面爬虫实例代码
2017/05/31 NodeJs
学习React中ref的两个demo示例
2018/08/14 Javascript
JS中FileReader类实现文件上传及时预览功能
2020/03/27 Javascript
解决vuex刷新数据消失问题
2020/11/12 Javascript
[05:15]2018年度CS GO社区贡献奖-完美盛典
2018/12/16 DOTA
Python实现的生成自我描述脚本分享(很有意思的程序)
2014/07/18 Python
利用Python脚本在Nginx和uwsgi上部署MoinMoin的教程
2015/05/05 Python
Python的条件语句与运算符优先级详解
2015/10/13 Python
Python实现的爬取网易动态评论操作示例
2018/06/06 Python
Python标准库shutil用法实例详解
2018/08/13 Python
Pyqt5 基本界面组件之inputDialog的使用
2019/06/25 Python
对Python获取屏幕截图的4种方法详解
2019/08/27 Python
Python多线程模块Threading用法示例小结
2019/11/09 Python
使用pyshp包进行shapefile文件修改的例子
2019/12/06 Python
AmazeUI 评论列表的实现示例
2020/08/13 HTML / CSS
联想英国官网:Lenovo英国
2019/07/17 全球购物
印尼在线旅游门户网站:NusaTrip
2019/11/01 全球购物
CHRONEXT英国:您的首选奢华腕表目的地
2020/03/30 全球购物
网络教育毕业生自我鉴定
2013/10/10 职场文书
欢度春节标语
2014/07/01 职场文书
2014年作风建设工作总结
2014/10/29 职场文书
2015年学校心理健康教育工作总结
2015/05/11 职场文书
2015年宣传部个人工作总结
2015/05/14 职场文书
2015年小学教师培训工作总结
2015/07/21 职场文书
办公室规章制度范本
2015/08/04 职场文书
公务员爱岗敬业心得体会
2016/01/25 职场文书
Python 高级库15 个让新手爱不释手(推荐)
2021/05/15 Python