JavaScript闭包浅谈
-------------------
作者:willingtolove;
本文链接:http://www.cnblogs.com/willingtolove/p/4745889.html
1. 变量的作用域:
在javascript中,局部变量的作用域是由它定义的函数决定的,嵌套函数可以访问它的外部作用域的变量。
EX1:
function hello() {
var name = "world";
function hi() {
alert(name);
}
hi();
}
hello();
上述代码的运行结果:弹出框 “world”;
因为name这个变量的作用域是1-7行(hello这个函数内);
要想详细理解请查阅:作用域与作用域链;这个对理解闭包很有用!
2. js的垃圾回收机制
js具有自动垃圾收集机制,开发人员不用再关心内存使用问题;这种垃圾收集机制原理很简单:找出不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按固定的时间间隔
(或按代码设定的)周期性的执行这一操作。
函数中局部变量的生命周期:局部变量只在函数执行的过程中存在。这个过程中,会为变量在栈或堆上分配相应的空间,以便存储它们的值。当函数结束后,该变量就没用了,就会被垃圾
收集器所标记,等待回收。下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是有时候,这个函数内部又嵌套了另一个函数,而这个内部函数使用了外部函数的变量,并且
内部函数在外面被调用.这时垃圾回收机制就会有问题.如果在外部函数结束后,又直接调用了其内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定
义的时候,会自动把函数和它可能使用的变量(包括本函数内变量和父级和祖先级函数的变量)一起存储起来,相当于存储了一个环境,也就是构建一个闭包,这些变量(或者说是环境)将不会被内
存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了...),才会销毁这个闭包,而没有任何一个闭包引用的变量会被下一次内存回收启动时所回收.
3. 闭包的定义:
闭包是涉及独立变量的的函数;换句话说,在闭包中定义的函数会记住它创建的环境;
也有这样理解的:闭包就是能够读取其他函数内部变量的函数;由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。
创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量;
EX2:
function hello() {
var name = "world";
function hi() {
alert(name);
}
return hi;
}
var myHello = hello();
myHello();
上面代码的运行结果与EX1中的效果一样;
为什么呢?
这段代码有两个特点:
1、函数hi嵌套在函数hello内部;
2、函数hello返回函数hi。
这样在执行完var myHello=hello()后,变量myHello实际上是指向了函数hi,再执行myHello()后就会弹出一个窗口显示变量name的值。这段代码其实就创建了一个闭包,为什么?
因为函数hello外的变量myHello引用了函数hello内的函数hi,就是说:当函数hello的内部函数hi被函数hello外的一个变量引用的时候,就创建了一个闭包。
代码执行第8行时,name变量生命周期开始,但当代码执行到第9行时,name变量的生命周期并没有结束;
通过上面的例子,可以这样理解闭包:当一个函数在它所创建的环境之外执行时,就是闭包。(函数myhello就是一个闭包)
闭包是一个特殊的对象,它包括两部分:① 函数;②创建函数的环境;
EX3: 闭包例子
function play() {
var num = 1;
return function () {
alert(num++)
};
}
var myPlay = play();
myPlay();// 结果:1
myPlay();// 结果:2
myPlay = null;//num被回收!
myPlay();//错误: 缺少对象
分析:内部函数在返回前,会将所有已访问过的外部函数中的变量(num)在内存中锁定,也就是说,这些变量将常驻 myPlay的内存中,不会被垃圾回收器回收;
num被锁定在myPlay的闭包里,那么每次执行 myPlay的时候,num都会自增一次;
EX4: 闭包例子
function Add(x) {
return function (y) {
return x + y;
};
} var Add5 = Add(5);
var Add10 = Add(10); console.log(Add5(2));
console.log(Add10(2));
运行结果:7,12
分析:
//Add5 就相当于:
function Add5(y) {
return 5 + y;
};
//Add10 就相当于:
function Add10(y) {
return 10 + y;
};
Add5和Add10都是闭包,但是它们存储了不同的环境; Add5的环境中x是5,在Add10的环境中x是10;
4. 闭包的使用场景
1) 匿名自执行函数:(html中元素事件初始化)
EX5:
<head>
<title></title>
<script type="text/javascript">
window.onload = function() {
for (var i = 1; i < 4; i++) {
var li = document.getElementById("a" + i);
li.onclick = (function(i) {//匿名自执行函数
return function() {
alert(i);
}
})(i);
}
} </script>
</head>
<body>
<ul>
<li id="a1">a1</li>
<li id="a2">a2</li>
<li id="a3">a3</li>
</ul>
</body>
效果:点击a1,弹出a1;点击a2,弹出a2;点击a3,弹出a3;
2) 实现封装,从而实现了从函数外部读取函数内部的局部变量;
EX6:
var person = function () {
var name = "world";//变量作用域为函数内部,外部无法访问
return {
getName: function () {
return name;
},
setName: function (newName) {
name = newName;
}
}
}();
alert(person.name);//直接访问,结果为undefined
alert(person.getName());//结果:world
person.setName("hello");
alert(person.getName());//结果:hello
3) 模拟面向对象中的对象;
不同的对象拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。
EX7:
function Person(){
var name = "default"; return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}; var hello = Person();
print(hello .getName()); //结果: default
hello .setName("hello");
print(hello .getName()); //结果: hello var world = Person();
print(world .getName()); //结果: default
world .setName("world");
print(world .getName()); //结果: world
5. 常见错误
EX8-1:
var result = [];
function play() {
var i = 0;
for (; i < 3; i = i + 1) {
result[i] = function () {
alert(i)
}
}
};
play();
result[0](); //结果: 3
result[1](); //结果: 3
result[2](); //结果: 3
EX6中,本希望play函数中的变量i被内部循环的函数使用,而实际上,只能获得该变量最后保留的值,也就是说.闭包中所记录的变量,只是对这个变量的一个引用,而非变量的值,当这个变量被改变了,
闭包里获取到的变量值,也会被改变.
解决的方法之一,是让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里.然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:
EX8-2:
var result = [];
function play() {
var i = 0;
for (; i < 3; i = i + 1) {
result[i] = (function (j) {
return function () {
alert(j);
};
})(i);
}
};
play();
result[0](); //结果: 0
result[1](); //结果: 1
result[2](); //结果: 2
性能考虑:
EX9-1:
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一个MyObject,getName和getMessage就会copy一次,影响性能;
利用闭包对EX9-1进行改进:
EX9-2:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
(function() {
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}).call(MyObject.prototype);
分析:无论new多少个MyObject,getName和getMessage只copy一次;
总结:
在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据
时,我们可以通过外面再包裹一层函数形成闭包来解决。
当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露。
------
此片随笔也是参考了各位前辈们的讲义,还有很多知识点等待学习研究,欢迎指正!
JavaScript闭包浅谈的更多相关文章
- javascript数组浅谈2
上次说了数组元素的增删,的这次说说数组的一些操作方法 join()方法: ,,] arr.join("_") //1_2_3 join方法会返回一个由数组中每个值的字符串形式拼接而 ...
- javascript数组浅谈1
最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用 ...
- JavaScript之浅谈内存空间
JavaScript之浅谈内存空间 JavaScipt 内存自动回收机制 在JavaScript中,最独特的一个特点就是拥有自动的垃圾回收机制(周期性执行),这也就意味者,前端开发人员能够专注于业余, ...
- 【javascript】浅谈javaScript的深拷贝
前言: 最开始意识到深拷贝的重要性是在我使用redux的时候(react + redux), redux的机制要求在reducer中必须返回一个新的对象,而不能对原来的对象做改动,事实上,当时 ...
- javascript数组浅谈3
前两节说了数组最基本的创建,队列方法,排序和一些操作方法,这节说说迭代和归并方法. every()方法 & some()方法 这两个方法会对数组中的每一项运行给定函数,然后返回一个布尔值,理解 ...
- Js之浅谈dom操作
JavaScript之浅谈dom操作 1.理解dom: DOM(Document Object Model ,文档对象模型)一种独立于语言,用于操作xml,html文档的应用编程接口. 怎么说,我从两 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈javascript函数节流
浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...
- 【Unity游戏开发】浅谈Lua和C#中的闭包
一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...
随机推荐
- hadoop-2.7.1伪分布环境搭建
1.准备Linux环境 1.0 点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改subnet i ...
- Arduino 1602液晶屏实验和程序
在Arduino IDE中, 项目->加载库->管理库中搜索LiquidCrystal,然后安装即可 1.接线图 2.引脚图 3.最简单程序 #include <LiquidCrys ...
- C# 闭包问题-你被”坑“过吗?
引言 闭包是什么?以前看面试题的时候才发现这个名词. 闭包在实际项目中会有什么问题?现在就让我们一起来看下这个不太熟悉的名词. 如果在实际工作中用到了匿名函数和lamada表达式,那你就应该高度注意啦 ...
- 记一次与a标签相遇的小事
最近做的一个项目,按钮使用的是a标签做的,样子还不错.不过正是这个a标签把我坑死了,有一个场景是点击a标签去调后台服务,为了防止用户频繁点击按钮提交,在去请求后台服务的时候肯定要先把按钮的事件给禁止掉 ...
- php调接口
浏览器直接访问接口时会弹出账号密码框 当用程序调用时需要加入 curl_setopt($ch, CURLOPT_USERPWD, "$username:$password") ...
- ActiveMQ笔记(5):JMX监控
系统上线运行后,及时监控报警是很必要的手段,对于ActiveMQ而言,主要监控的指标有:MQ本身的健康状况.每个队列的生产者数量.消费者数量.队列的当前消息数等. ActiveMQ支持JMX监控,使用 ...
- [原]CentOS7 部署GeoServer2.92
转载请注明作者think8848和出处(http://think8848.cnblogs.com) 1. 安装Jre 1. 安装ftp客户端 sudo yum install ftp -y 2. 登录 ...
- [LeetCode] Russian Doll Envelopes 俄罗斯娃娃信封
You have a number of envelopes with widths and heights given as a pair of integers (w, h). One envel ...
- [LeetCode] Data Stream as Disjoint Intervals 分离区间的数据流
Given a data stream input of non-negative integers a1, a2, ..., an, ..., summarize the numbers seen ...
- [LeetCode] Find Peak Element 求数组的局部峰值
A peak element is an element that is greater than its neighbors. Given an input array where num[i] ≠ ...