javascript之闭包六(闭包的作用与注意事项)
六、闭包的作用
闭包:内部函数保存到外部
当内部函数被保存到外部时,将会生成闭包。 闭包会导致原有作用域链不释放,造成内存泄漏(内存占用)
一)闭包的作用
- 实现公有变量: eg:函数累加器
- 可以做缓存(存储结构):eg:eater
- 可以实现封装,属性私有化:eg:new Person();
- 模块化开发,防止污染全局变量
二)闭包作用举例
1、累加器:
题目:定义一个定时器,计算点击网页的次数 这个题目非常简单,想必大家都能写出来。
var count = 0;
function addCount() {
count++;
}
document.body.addEventListener("click", addCount);
count作为一个全局变量,其他地方都可以对它进行操作,如果其他地方对count重新赋值或者重新定义count,那么这个计时器就被破坏了。这时候,闭包就起作用了。
function addCount() {
var count = 0;
var addCount = function() {
count++;
}
return addCount;
}
document.body.addEventListener("click", addCount);
点击一次->输出1
点击两次->输出2
2、缓存:
function eater(){
var food="apple";
var obj={
eat:function (){
if(food!=""){
console.log("i am eating "+ food);
food="";
}else{
console.log("eat emtpy ");
}
}
push:function(myFood){
food = myFood;
}
}
return obj;
}
var eat1= eater();
eat1.eat();->输出apple
eat1.eat();->输出empty
eat1.push('banana');
eat1.eat();->输出banana
看另外一个缓存的例子:
function isFirstLoad(){
var list=[];
return function(option){
if(list.indexOf(option)>=0){
//检测是否存在于现有数组中,有则说明已存在
console.log('已存在')
}else{
list.push(option);
console.log('首次传入');
//没有则返回true,并把这次的数据录入进去
}
}
}
var ifl=isFirstLoad();
ifl("zhangsan");
ifl("lisi");
ifl("zhangsan");
在浏览器控制台打印如下:

可以看到,如果外界想访问list变量,只能通过我定义的函数isFirstLoad来进行访问,我对想访问list的外界只提供了isFirstLoad这一个接口。至于怎么操作_list,我已经定义好了,外界能做的就只是使用我的函数,然后传几个不同的参数罢了。
最后顺便说一下,作用域链是在定义的时候就已经确定了,和谁来执行,什么时候执行均没有一毛钱关系。
3. 私有化变量:下面例子输入deng.prepareWife是undefind 形成了私有化变量
function Deng(name,wife){
//正常情况 函数里的var对象 函数执行完了就会被销毁,
但是在这里因为被this.divorce的函数使用被返回了形成了闭包,所以无法销毁。
var prepareWife="xiaozhang";
this.name=name;
this.wife=wife;
this.divorce=function(){
this.wife=prepareWife;
}
this.changePrepareWife=function(target){
prepareWife=target;
}
this.sayPraprewife=function(){
console.log(prepareWife);
}
}
var deng=new Deng('deng','xiaoliu');
模拟实现类的私有属性的例子:
function Boy(name){
this.name = name;
var sex = 'boy';
this.saySex = function(){
console.log("my sex is "+sex)
};
}
var xiaoming = new Boy('xiaoming');
console.log(xiaoming.name);
console.log(xiaoming.sex);
xiaoming.saySex();
VM344:16 xiaoming
VM344:18 undefined
VM344:9 my sex is boy
4. 立即执行函数解决闭包作用域问题:
立即执行函数(执行完立即销毁) 针对初始化功能的函数 定义:此类函数没有生命,在一起执行过后释放。适合做初始化工作。
function test(){
var arr=[];
for(var i=0;i<10;i++){
(function (j){
arr[j]=function(){
document.write(j+" ");//输为0,1,2,3,4,5,6,7,8,9
}
}(i));
//用立即执行函数(立即执行函数也可以生成自己的作用域)
i作为参数传给j j不会随着i改变而改变
}
return arr;
}
var myArr=test();
for(var j=0;j<10;j++){
myArr[j]();
}
<ul>
<li id="myli">a</li>
<li id="myli">a</li>
<li id="myli">a</li>
</ul>
<script type="text/javascript">
function test(){
var liList=document.getElementsByTagName("li");
for (i = 0; i < liList.length; i++) {
(function(j){
liList[j].onclick=function(){
console.log(j); //输出1 2 3 如果不用立即执行函数会报错
}
}(i))
}
}
test();
解决for循环中点击事件的问题举例 我们先来看一段代码
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
console.log(i);
}
}
运行这段代码后,大家或许会有疑问,为什么点击任一个li标签,console台打印的都是3。 代码运行结束后,给每个li标签定义了click函数,但这个函数没有立即执行,只有当点击li时,才会执行该click函数;当点击li执行函数时,函数中的变量i没有在函数中定义,根据js的作用域链原则,会继续向上级作用域查询,因此找到了全局作用域中的i,这时for循环已经执行结束,此时全局作用域中的i已经变为了3,故打印出来的当然是3了。 要想实现,打印出来的值为点击li的顺序值,这时,闭包又起到了作用
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = (function(i) {
var clickLi = function() {
console.log(i);
}
return clickLi;
})(i)
}
在for循环执行时,立即将当前的i值作为形参传入clickLi中,而形参默认为函数内的局部变量,函数外部是不能对i进行操作的。所以,当点击li时,执行clickLi函数时,打印出来的则是li的顺序值。
三)闭包的缺点及解决
缺点:函数执行完后,函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。
解决:能不用闭包就不用,及时释放。比如:
f = null; // 让内部函数成为垃圾对象 -->回收闭包
总而言之,你需要它,就是优点;你不需要它,就成了缺点。
七、闭包的注意事项
1. 内存泄漏内存溢出
内存泄漏:占用的内存没有及时释放。内存泄露积累多了就容易导致内存溢出。
常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
情况1举例:意外的全局变量
function fn() {
a = new Array(10000000);
console.log(a);
}
fn();
情况2举例:没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId); //清理定时器
情况3举例:闭包占用内存
<script type="text/javascript">
function fn1() {
var arr = new Array[100000]; //这个数组占用了很大的内存空间
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包
</script>
2. 关于闭包当中的this对象
this对象指的是什么,这个要看函数所运行的环境。如果函数在全局范围内调用 ,函数内的this指向的是window对象。对象中的方法,通过闭包如果运行的环境为window时,则this为window。因为闭包并不是该对象的方法。
var color="red";
function fn(){
return this.color;
}
var obj={
color:"yellow",
fn:function(){
return function(){//返回匿名函数
return this.color;
}
}
}
console.log(fn());//red 在外部直接调用this为window
var b=obj.fn();//b为window下的变量,获得的值为obj对象下的fn方法返回的匿名函数
console.log(b());//red 因为是在window环境下运行,所以this指缶的是window
//可以通过call或apply改变函数内的this指向
console.log(b.call(obj));//yellow
console.log(b.apply(obj));//yellow
console.log(fn.call(obj));//yellow
通过变量可以获得上一个作用域中的this指向
var color="red";
function fn(){
return this.color;
}
var obj={
color:"yellow",
fn:function(){
var _this=this;//将this赋值给变量_this
return function(){
return _this.color;//通过_this获得上一个作用域中的this指向
}
}
}
console.log(fn());//red
var b=obj.fn();
console.log(b());//yellow
可以通过构造方法传参来访问私有变量
function Desk(){
var str="";//局部变量str,默认值为""
this.getStr=function(){
return str;
}
this.setStr=function(value){
str=value;
};
}
var desk=new Desk();
//为构造函数的局部变量写入值。
desk.setStr("zhangPeiYue");
//获取构造函数的局部变量
console.log(desk.getStr());//zhangPeiYue
转载自:https://juejin.cn/post/6844903910944014350