###1、与闭包有关的两个概念:

(1)变量的作用域 不带有关键字var的变量会成为全局变量; 在函数中使用关键字var声明的变量是局部变量。 局部变量只有在函数内部才能访问到,在函数外面是访问不到的。但在函数内部可以通过作用域链一直向上搜索直到全局对象,也就是说,函数内部可以访问函数外部的变量。 (2)变量的生存周期 对于全局变量,其生存周期是永久的,除非主动销毁这个全局变量; 而对于在函数内用关键字var声明的局部变量,当退出函数时,这些局部变量会随着函数调用结束而被销毁。

var func = function() {var i = 1;alert(i); // 输出:1
};
alert(i); // 报错:i is not defind.
复制代码

例外情况:闭包

var func = function() {var i = 1;return function() {alert(i);i++;}
};
var f1 = func();
f1(); // 输出:1
f1(); // 输出:2
var f2 = func();
f2(); // 输出:1
f2(); // 输出:2
复制代码

###2、从闭包的一个经典应用谈起

<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>var divs = document.getElementsByTagName("div");for (var i = 0; i < divs.length; i++) {divs[i].onclick = function() {alert(i);};}//欢迎加入全栈开发交流圈一起学习交流:864305860
</script>
复制代码

问题:无论单击哪个div,都会弹出5。 原因:onclick事件是异步触发的,当事件被触发时,for循环早已结束,此时变量i的值早已经是5。 解决:在闭包的帮助下,把每次循环的i值都封闭起来。当事件函数顺着作用域链从内到外查找变量i时,会先找到被封闭在闭包环境的i,单击div时,会分别输出0,1,2,3,4。

<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {divs[i].onclick = (function(num) {return function() {alert(num);};})(i);
}//欢迎加入全栈开发交流圈一起学习交流:864305860
</script>
复制代码

类似实例:闭包直接赋给数组

function createFunctions() {var result = new Array();for (var i = 0; i < 10; i++){result[i] = function(){return i;};}return result;
}
for (var i = 0; i < 10; i++)alert(createFunctions()[i]());
复制代码

结果:result的每个元素都返回10。 说明:闭包的作用域链有明显的副作用——闭包总是获得外部函数变量的最终值。上面代码中,外部函数产生一个函数数组result并返回。函数数组中的每个元素都是一个函数,每个函数都返回 i变量。似乎每个函数应该返回每次循环的i值,即依次返回0到9,但事实是,每个函数的返回结果都是10。这是因为每个内部函数返回的是变量i,而不是i在某个时刻的特定值,而i的作用域是整个外部函数,当外部函数执行完成后,i的值是10。 解决:在每个内部函数的内部,再产生一个匿名函数并返回。

function createFunctions() {var result = new Array();for (var i = 0; i < 10; i++) {result[i] = (function(num) {return function() {return num;};})(i);}return result;
}//欢迎加入全栈开发交流圈一起学习交流:864305860
for (var i = 0; i < 10; i++)alert(createFunctions()[i]());
复制代码

结果:result依次返回0到9。 说明:(i)使得该层匿名函数立即执行。 ###3、闭包

有时候需要得到函数内的局部变量。如何从外部读取局部变量?那就是在函数的内部,再定义一个函数。 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。 ① 闭包的原理

  • 后台执行环境中,闭包的作用域链包含着自己的作用域、函数的作用域和全局作用域。
  • 通常,函数的作用域和变量会在函数执行结束后销毁。
  • 但是,当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。 ② 闭包的特性
  • 函数内再嵌套函数。
  • 内部函数可以引用外层的参数和变量。
  • 参数和变量不会被垃圾回收机制回收。 ③ 闭包的用途
  • 读取函数内部的变量。
function f1(){var n = 999;function f2(){alert(n);//999}
}//欢迎加入全栈开发交流圈一起学习交流:864305860
复制代码

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取它的内部变量了。

function f1(){var n = 999;function f2(){alert(n);}return f2;
}
var result=f1();
result();//999
复制代码

闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

function f1(){var n = 999;nAdd = function(){n += 1}function f2(){alert(n);}//欢迎加入全栈开发交流圈一起学习交流:864305860return f2;//面向1-3年前端人员
}//帮助突破技术瓶颈,提升思维能力
var result=f1();
result();//999
nAdd();
result();//1000
复制代码

result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。 (2)让函数内部的变量的值始终保持在内存中(延长局部变量的寿命)。

var print, add, set;
function closure() {var number = 8;print = function() {alert(number);}add = function() {number++;}set = function(x) {number = x;}
}
closure();//创建一个闭包
add();
print();//9
set(0);
print();//0
var oldClosure = print;
closure();//创建一个新的闭包
print();//8
oldClosure();//0
复制代码

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。也就是说,闭包会引用外部函数作用域,会占用更多的内存,过度使用闭包,会导致性能问题。所以,仅当必要时才使用闭包。对产生闭包的函数,使用后应该解除引用。 (3)自执行函数+闭包减少全局变量污染(封装私有变量)

var person = (function() {var_name = "Alice";var _id = 16;return {getUserInfo: function() {return _name + ": " + _id;}//欢迎加入全栈开发交流圈一起学习交流:864305860}//面向1-3年前端人员
})();//帮助突破技术瓶颈,提升思维能力
复制代码

使用下划线来约定私有变量_name和_age,它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命令污染。 ④ 闭包的缺点: (1) 需要维护额外的作用域。 (2) 过渡使用闭包会占用大量内存。 4、this对象 在闭包内使用this对象将产生一些复杂的行为。this对象的值基于函数所在的执行环境在运行时决定:在全局函数中使用时,this等于window(非严格模式)或undefined(严格模式);而当作为对象的方法调用时,this等于这个对象。

var name = "The window";
var object = {name: "My object",getName: function() {return function() {return this.name;};}
};
alert(object.getName()());//输出:"The window"
复制代码

每个函数一旦被调用,它将自动获得this和arguments两个变量。一个内部函数是不能直接从外部函数访问到这两个变量的。可以通过将this对象存储在另一个变量中来解决这个问题。把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name ="The window";
var object = {name: "My object",getName: function() {var that = this;return function() {return that.name;};//欢迎加入全栈开发交流圈一起学习交流:864305860}//面向1-3年前端人员
};//帮助突破技术瓶颈,提升思维能力
alert(object.getName()());//输出:"My object"
复制代码

要让闭包访问外部函数的this和arguments对象,可以通过将它们的引用存储在另一个变量中来完成。 5、内存泄漏 使用闭包的时候很容易造成循环引用,若闭包的作用域包含着一些DOM节点,这时候就有可能造成内存泄漏,但其实,这本身不是闭包的问题,而是由于:BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略,在基于引用计数策略的垃圾回收机制中,若两个对象之间形成了循环引用,则这两个对象都无法被回收。

function assignHandler() {var element = document.getElementById("id");element.onclick = function() {alert(element.id);}
}
复制代码

匿名函数保存了一个对element的引用,只要匿名函数存在,element的引用数至少为1,它所占用的内存就永远不会被回收。

function assignHandler() {var element = document.getElementById("id");var id = element.id;element.onclick = function() {alert(id);}element = null;
}
复制代码

通过把element.id的一个副本保存在变量中,并且在闭包中引用该变量消除了循环引用,但是仅仅做到这一步还不能解决内存泄漏的问题,闭包会引用包含函数的所有活动对象,包含element,即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用,因此有必要把element元素设置为null,这样就能解除对DOM对象的引用,确保正常回收其占用的内存。 6、模仿块级作用域 JavaScript中没有直接的块级作用域。

function output(count) {for (var i = 0; i < count; i++) {alert(i);}alert(i);//输出:10
}
复制代码

使用闭包可以模仿块级作用域——创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。结果是内部函数的所有变量都会立即被销毁,除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。 用作块级作用域的匿名函数:将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,紧随其后的另一对圆括号会立即调用这个函数。

(function() {//这里是块级作用域;
}) ();
复制代码

可以使用匿名函数表达式来模拟块级作用域,任何定义在匿名函数中的变量在匿名函数执行完之后都将被销毁,在匿名函数外访问这些变量将会产生错误。

function output(count) {(function() {for (var i = 0; i < count; i++) {alert(i);
//欢迎加入全栈开发交流圈一起学习交流:864305860}}) ();alert(i);//出错
}
复制代码

结语

感谢您的观看,如有不足之处,欢迎批评指正。

本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。 对web开发技术感兴趣的同学,欢迎加入Q群:864305860,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。 最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。

JavaScript闭包原理与用法实例相关推荐

  1. 简述游标原理 mysql_mysql游标的原理与用法实例分析

    本文实例讲述了mysql游标的原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是游标 创建游标 使用游标 首发日期:2018-04-18 什么是游标: 如果你前面看过mysql函数,会发 ...

  2. mysql 实时聚合分析,mysql累积聚合原理与用法实例分析

    本文实例讲述了mysql累积聚合原理与用法.分享给大家供大家参考,具体如下: 累积聚合为聚合从序列内第一个元素到当前元素的数据,如为每个员工返回每月开始到现在累积的订单数量和平均订单数量 行号问题有两 ...

  3. mysql 实时聚合分析_mysql滑动聚合/年初至今聚合原理与用法实例分析

    本文实例讲述了mysql滑动聚合/年初至今聚合原理与用法.分享给大家供大家参考,具体如下: 滑动聚合是按顺序对滑动窗口范围内的数据进行聚合的操作.下累积聚合不同,滑动聚合并不是统计开始计算的位置到当前 ...

  4. php namespace原理作用,php 命名空间(namespace)原理与用法实例小结

    本文实例讲述了php 命名空间(namespace)原理与用法.分享给大家供大家参考,具体如下: 命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致 ...

  5. python设计模式案例分析_Python设计模式之职责链模式原理与用法实例分析

    本文实例讲述了Python设计模式之职责链模式原理与用法.分享给大家供大家参考,具体如下: 职责链模式(Chain Of Responsibility):使多个对象都有机会处理请求,从而避免发送者和接 ...

  6. 多字段mysql触发器实例_mysql触发器原理与用法实例分析

    本文实例讲述了mysql触发器原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是触发器 创建触发器 单条触发器语句 多条触发器语句 查看触发器 删除触发器 触发器的新旧记录引用 首发日期 ...

  7. mysql 自定义函数实例_mysql自定义函数原理与用法实例分析

    本文实例讲述了mysql自定义函数原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是函数 函数的创建 函数的调用 函数的查看 函数的修改 函数的删除 首发日期:2018-04-18 什么 ...

  8. JavaScript中window.open用法实例详解

    本文较为详细的分析了JavaScript中window.open用法.分享给大家供大家参考.具体如下: 复制代码 代码如下: <script LANGUAGE="javascript& ...

  9. java中的asList_Java Arrays.AsList原理及用法实例

    java.util.Arrays的asList方法可以方便的将数组转化为集合,我们平时开发在初始化ArrayList时使用的比较多,可以简化代码,但这个静态方法asList()有几个坑需要注意: 一. ...

最新文章

  1. HDU1212(大数取模-秦九昭算法)
  2. libxml解析的attributes参数理解
  3. Linux C Socket编程原理及简单实例
  4. MySQL where后面的列子查询使用
  5. 单片机小白学步系列(二十三) IO口原理知识补充:双向IO口、互补推挽、高阻态
  6. 大数据新手之路二:安装Flume
  7. 计算机科学与技术 天涯,计算机科学与技术专业
  8. 部署Java web项目到阿里云服务器
  9. 用css和js写表单验证,使用javascript及正则表达式实现表单验证(CSS,js练习)
  10. Encoder与Decoder
  11. 【DFS笔记】对dfs(index,状态)一类问题的思考
  12. 用rollup打包vue组件库
  13. 动态规划 之 完全背包
  14. w ndows10启动黑屏,win10开机一直黑屏的解决方法
  15. Carsim 学习心得-粗略翻译1
  16. 虾皮shopee蓝海词怎么找?挖掘shopee蓝海词有哪些技巧?
  17. windows远程桌面的管理
  18. 共建网络安全 共享网络文明 2015福建网络安全宣传日成功举办
  19. Google Maps JavaScript API V3的使用(四)
  20. python中的星号和乘号_乘号在python中的用法,用乘号将元素重复在列表中

热门文章

  1. 3星|《财经》年刊:各行业专家学者的现状分析与短期预测
  2. 风口上的政府大数据 需要消除“大误区”
  3. C# 7.2和8.0路线图
  4. SAS vs SSD各种模式下MySQL TPCC OLTP对比测试结果
  5. 关于Office 365 域名绑定问题
  6. tar 解压缩命令详解
  7. 【SICP练习】9 练习1.15
  8. JSPServlet(1)
  9. 关于DELLR710/R910(rhel5.3-5.5) Broadcom netxtreme 5709网卡间歇性的停止接收数据包的问题解决方案...
  10. Tech.Ed 2006 博客园兄弟聚会