浅谈js之闭包
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之闭包的更多相关文章
- 浅谈JS中的闭包
浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...
- 浅谈JS中 var let const 变量声明
浅谈JS中 var let const 变量声明 用var来声明变量会出现的问题: 1. 允许重复的变量声明:导致数据被覆盖 2. 变量提升:怪异的数据访问.闭包问题 3. 全局变量挂载到全局对象:全 ...
- 浅谈JS之AJAX
0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HT ...
- 浅谈 js 正则字面量 与 new RegExp 执行效率
原文:浅谈 js 正则字面量 与 new RegExp 执行效率 前几天谈了正则匹配 js 字符串的问题:<js 正则学习小记之匹配字符串> 和 <js 正则学习小记之匹配字符串优化 ...
- 浅谈 js 字符串之神奇的转义
原文:浅谈 js 字符串之神奇的转义 字符串在js里是非常常用的,但是你真的了解它么?翻阅<MDN String>就可以了解它的常见用法了,开门见山的就让你了解了字符串是怎么回事. 'st ...
- 浅谈 js 正则之 test 方法
原文:浅谈 js 正则之 test 方法 其实我很少用这个,所以之前一直没注意这个问题,自从落叶那厮写了个变态的测试我才去看了下这东西.先来看个东西吧. var re = /\d/; console. ...
- 浅谈 js 数字格式类型
原文:浅谈 js 数字格式类型 很多人也许只知道 ,123.456,0xff 之类的数字格式.其实 js 格式还有很多数字格式类型,比如 1., .1 这样的,也有 .1e2 这样的. 可能有人说这是 ...
- 浅谈 js 语句块与标签
原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...
- 浅谈 js 字符串 trim 方法之正则篇
原文:浅谈 js 字符串 trim 方法之正则篇 关于 trim 其实没啥好说的,无非就是去除首位空格,对于现代浏览器来说只是简单的正则 /^\s+|\s+$/ 就可以搞定了.而且支持中文空格 等 ...
随机推荐
- [JZOJ5987] 仙人掌毒题
Description Solution 套路题... 全他娘的是套路... 首先如何处理仙人掌,可以在线拿 \(lct\) 维护,或者离线之后树剖.(\(lct\) 维护太毒了写不来,就离线树剖了又 ...
- [EOJ439] 强制在线
Description 见EOJ439 Solution 先考虑不强制在线怎么做. 按询问区间右端点排序,从左往右扫,维护所有后缀的答案. 如果扫到 \(a[i]\),那么让统计个数的 \(cnt[a ...
- java中Map集合的理解
Map |--Hashtable:底层是哈希表数据结构,不可以存入null键null值.该集合是线程同步的.jdk1.0.效率低. |--HashMap:底层是哈希表数据结构,允许使用 null 值和 ...
- LeetCode两数之和-Python<一>
下一篇:LeetCode链表相加-Python<二> 题目:https://leetcode-cn.com/problems/two-sum/description/ 给定一个整数数组和一 ...
- lnmp首次安装重置mysql密码
第一种方法:一键修改LNMP环境下MYSQL数据库密码脚本 一键脚本肯定是非常方便.具体执行以下命令: wget http://soft.vpser.net/lnmp/ext/reset_mysql_ ...
- vue路由传参的三种基本方式
现有如下场景,点击父组件的li元素跳转到子组件中,并携带参数,便于子组件获取数据. 父组件中: <li v-for="article in articles" @click= ...
- 洛谷P4438 [HNOI/AHOI2018]道路(dp)
题意 题目链接 Sol 每当出题人想起他出的HNOI 2018 Day2T3,他都会激动的拍打着轮椅 读题比做题用时长系列... \(f[i][a][b]\)表示从根到\(i\)的路径上,有\(a\) ...
- JS中的三种循环
三种循环1.while 2.do while 3.for 1.while: 语法结构:while(条件){代码块:改变条件} 步骤:1.初始化变量 2.判断条件 3.执行代码块 4.改变初始条 ...
- OkHttp3源码详解(一) Request类
每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装 public final class R ...
- 使用VSTS的Git进行版本控制(六)——拉取请求
使用VSTS的Git进行版本控制(六)--拉取请求 在将代码合并到主干之前,拉取请求让团队对特性分支的更改提供反馈.审阅人可以通过建议修改留下评论,并投票批准或拒绝代码. 任务1:在Visual St ...