1.什么是闭包???

"官方"的解释是指一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分;

红皮书是这样说的,闭包是指有权访问另一个函数作用域中变量的函数;常见的创建闭包的方式就是在一个函数中再创建一个函数;

闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成;

光看定义是云里雾里,但是到了真正的代码了又是什么样的形式呢?经典的闭包例子:

function fn() {
var name = 4;
return function () {
var n = 0;
alert(++n);
alert(++name);
}
}
var fun = fn();
fun();//n =>1,name=>5;
fun();// n =>1,name=>6;

这里就有闭包产生了,fun就是闭包;这个闭包由fn中的匿名函数和name变量构成。,但是呢,它又是一种特殊的函数,它是一种能能够读取其他函数内部变量的特殊函数;fn中的匿名函数的父函数在执行完了之后,按正常来说,它应该被销毁,里面的变量也被销毁,,但是里面的变量现在没有被销毁,而是被这个匿名函数引用着;(说实在的它不应该被销毁,因为这个匿名函数还没有执行,还要用上一级的函数的中的变量,你给我销毁了,我可怎么办,那不是让我报错呀,但是呢,我给你用,不销毁,这又不符合规矩,按规矩是这样的:当一个函数执行完之后,是要被立即销毁的,执行环境销毁,里面的变量销毁,你现在不让我销毁,那不乱套了,那怎么办呢,于是乎,一群砖家,就说赶这种方式叫闭包吧)意思是说我跟你们不一样;因为是特殊函数,代码的最后一句fun()执行完之后,name变量还是没有释放,但是每次执行fun,里面的n变量是都是新创建的,执行完之后又释放掉;要是你明白了就不需要看括号里的内容了(这里我就形象的说为什么说name变量会一直在内存中?在刚开始的时候,父函数fn在刚要执行完了,开始销毁时,匿名子函数就说了,我要用你的name变量,你先别销毁了,父函数说好吧,于是乎,父函数执行完之后(归天了),就没有销毁name,在当你调用fun,执行匿名子函数,fun()调用完了,你把自己家的n变量销毁了,fun就说了name又不是我的东西,我就是用了一下,凭什么我给销毁,我不给销毁,但是这时父函数已经去世了(执行完了),于是就产生了内存消耗,除非你手动销毁,垃圾收回机制不会自动收回;这又牵扯到内存泄漏,性能问题了,后面说。)

作用域链:

讲到这里,如果要想整整的明白还有知道作用域链,和垃圾收回机制;

我就说说上面代码执行时的作用域链:

我就说说这张图是什么意思,这种图是执行var fun = fu();fun();这两句代码时所发生的情况;

其实匿名函数在fu()被返回时,它的作用域链就被初始化为包含全局变量对象和fu函数的活动对象;也就是说当fu函数执行完返回后,它的执行环境会被销毁,但是其活动对象不会被销毁,仍然在内存中,因为匿名函数的作用域链中引用了这个活动对象。只有到匿名函数被手动销毁时才销毁;其实在fu执行完后,红字显示的部分就消失了,就活动变量没有消失;

再说一点关于作用域链的问题:

1。作用域链中的变量对象(函数中叫活动对象)保存的是变量和函数;

2.作用域链的作用就是为了保证对执行环境有权访问的所有变量和函数的有序访问。

3.查找一个变量是从作用域链的最前端,逐渐向上找,只要找到就不再向上找了,不论上面是否还有这个值;

4.就以上面的fu函数为例吧,在声明fu函数的时候就开始预先创建一个包含全局变量对象的作用域链了(如果嵌套多了,其实就是在函数声明的地方创建父函数及其之上的作用域链),这个作用域链将被保存在刚创建函数的内部[[Scope]]的属性中;当调用fu函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]中的作用域链构建起执行环境的作用域链;之后还要创建一个本函数的活动对象,并把这个活动对象推入执行环境作用域链的前端。

5.作用域链本质上就是一个指向变量对象的指针列表。

垃圾回收机制:

再说说垃圾回收机制:

1.如果一个对象不再有引用了,这个对象就会被GC收回;

2,如果两个对象互相引用,但不被第三个引用,这两个互相引用的对象也会收回的。

而闭包不再这个范畴之内。

闭包的特性:

1.引用的变量不被垃圾回收机制收回

2.函数内部可以引用外部的变量;

3.函数里面嵌套函数

闭包的用处(好处):

1.私有变量和方法

var a=(function() {
var privateNum = 1;
function privateFun(val) {
alert(val);
}
return {
publicFun: function() {
privateFun(2);
},
publicNum:function() {
return privateNum;
}
} })();
a.publicNum();//1
a.publicFun();//2

如果你用a.privateNum,a.privateFun();这是会报错的。

2.实现一些变量的累加

function a() {
var n=0;
return function () {
n++;
alert(n);
}
}
var b = a();
b();//1
b();//2
b();//3

这里只是要使用累加,就这样干,具体还要具体分析,原理是这样了

因闭包产生的问题

初学者常见的,循环闭包

大部分我们所写的 Web JavaScript 代码都是事件驱动的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常添加为回调:响应事件而执行的函数。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>闭包循环问题</title>
<style type="text/css">
p {background:red;}
</style>
</head>
<body>
<p class="p">我是1号</p>
<p class="p">我是2号</p>
<p class="p">我是3号</p>
<p class="p">我是4号</p>
<p class="p">我是五号</p>
<script type="text/javascript">
var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
page[i].onclick = function () {
alert("我是"+i+"号");
}
}
</script>
</body>
</html>

你不管点击哪一个,都alert”我是5号“;
原因就是你循环了五次产生了五个闭包,而这5个闭包共享一个变量i,说的明白一点就是,在for循环结束时,只是把这五个匿名函数注册给click事件,当时在循环的时候并没有执行,当循环结束了,此时i的值是5;之后你去点击p标签,你点击哪一个就执行哪一个对应的匿名函数(这个时候才执行),这时候匿名中发现一个i,匿名中没有定义i,于是沿着作用域链找,找到了,但是这时候循环早就结束了,i等于5,于是弹出”我是5号“来;点击其他的同理;

怎么解决呢:

一种方法是再创建一个闭包,把js代码改为这样就行了

var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
!function(num) {
page[i].onclick = function () {
alert("我是"+num+"号");
}
}(i)
}

我只说一点,这次五个闭包不共享num,而是创建五个num变量

还有一种解决方式:

var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
page[i].num = i//先把每个变量值存起来
page[i].onclick = function () {
alert("我是"+this.num+"号");
}
}

闭包中的this对象

var num = 1;
var obj = {
num:2,
getNum:function() {
return function () {
return this.num;
}
}
}
alert(obj.getNum()());//num -> 1

为什么不弹出2呢,这里是说明闭包中你需要注意现在的this的指向那一个对象,其实记住一句话就永远不会用错this的指向问题,this永远指向调用它的作用域;

如果这样写你就可能理解了

var num = 1;
var obj = {
num:2,
getNum:function() {
return function () {
return this.num;
}
}
}
var a = obj.getNum();
alert(window.a());//1

其实是window对象调用的,这就是说闭包中的this让你看不清this的指向;

要是让它alert 2你要这样:

var num = 1;
var obj = {
num:2,
getNum:function() {
var _this = this;//在这里保存this
return function () {
return _this.num;
}
}
}
var a = obj.getNum();
alert(window.a());

性能考量

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

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

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

应该是这样

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

示例中,继承的原型可以为所有对象共享,且不必在每一次创建对象时定义方法

欢迎评论指正;

参考:

红皮书

Mozilla:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Closures

阮一峰:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

浅谈js之闭包的更多相关文章

  1. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  2. 浅谈JS中 var let const 变量声明

    浅谈JS中 var let const 变量声明 用var来声明变量会出现的问题: 1. 允许重复的变量声明:导致数据被覆盖 2. 变量提升:怪异的数据访问.闭包问题 3. 全局变量挂载到全局对象:全 ...

  3. 浅谈JS之AJAX

    0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HT ...

  4. 浅谈 js 正则字面量 与 new RegExp 执行效率

    原文:浅谈 js 正则字面量 与 new RegExp 执行效率 前几天谈了正则匹配 js 字符串的问题:<js 正则学习小记之匹配字符串> 和 <js 正则学习小记之匹配字符串优化 ...

  5. 浅谈 js 字符串之神奇的转义

    原文:浅谈 js 字符串之神奇的转义 字符串在js里是非常常用的,但是你真的了解它么?翻阅<MDN String>就可以了解它的常见用法了,开门见山的就让你了解了字符串是怎么回事. 'st ...

  6. 浅谈 js 正则之 test 方法

    原文:浅谈 js 正则之 test 方法 其实我很少用这个,所以之前一直没注意这个问题,自从落叶那厮写了个变态的测试我才去看了下这东西.先来看个东西吧. var re = /\d/; console. ...

  7. 浅谈 js 数字格式类型

    原文:浅谈 js 数字格式类型 很多人也许只知道 ,123.456,0xff 之类的数字格式.其实 js 格式还有很多数字格式类型,比如 1., .1 这样的,也有 .1e2 这样的. 可能有人说这是 ...

  8. 浅谈 js 语句块与标签

    原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...

  9. 浅谈 js 字符串 trim 方法之正则篇

    原文:浅谈 js 字符串 trim 方法之正则篇 关于 trim 其实没啥好说的,无非就是去除首位空格,对于现代浏览器来说只是简单的正则 /^\s+|\s+$/ 就可以搞定了.而且支持中文空格   等 ...

随机推荐

  1. JS之BOM、DOM

    一.BOM对象 1,window对象 所有浏览器都支持window对象,从概念上讲:一个HTML文档对应一个window对象,从功能上讲:控制浏览器窗口的,从使用上讲:window对象不需要创建对象, ...

  2. 【Tomcat】Tomcat日志切割

    下载并解压缩 cronolog # tar zxvf cronolog-1.6.2.tar.gz 2.进入cronolog安装文件所在目录 # cd cronolog-1.6.2 3.运行安装  # ...

  3. Matlab 如何输入矩阵

    A=[1 2 3;4 5 6;7 8 9],每行之间用分号隔开 也可以用循环控制输入 n=input('请输入矩阵阶数:') for i=1:n     for j=1:n         a(i,j ...

  4. 支持开源,推动Orchard

    希望正在研究果园,和对果园感兴趣的,加入Orchard高级开发群,进行交流和讨论及深入研究Orchard开发,我们致力寻求志同道合推动Orchard发展的屌丝!!! 干净.专注.社区力量的圈子

  5. instanceof和typeof的细节

    我骑着小毛驴,喝着大红牛哇,哩个啷格里格朗,别问我为什么这木开心,如果活着不是为了浪荡那将毫无意义 今天来捋一捋我们平日经常用的instanceof和typeof的一些小问题 typeof: type ...

  6. 【代码笔记】Web-HTML-脚本

    一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  7. 长文本溢出显示省略号(…) text-overflow: ellipsis

    text-overflow 属性规定当文本溢出包含元素时发生的事情. 默认值: clip 继承性: no 版本: CSS3 JavaScript 语法: object .style.textOverf ...

  8. 自定义控件详解(五):onMeasure()、onLayout()

    前言: 自定义控件的三大方法: 测量: onMeasure(): 测量自己的大小,为正式布局提供建议 布局: onLayout(): 使用layout()函数对所有子控件布局 绘制: onDraw() ...

  9. js判断当前浏览器语言类型

    console.log(window.navigator.language.slice(0, 2)); 得到的是zh

  10. MQTT详解以及在IoT中的应用

    MQTT定义: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分.该协议支持所有平台, ...