您的位置:首页 > Web前端 > JQuery

jQuery闭包之浅见,从面向对象角度来理解

2014-06-23 21:22 429 查看
本篇的标题虽然是"jQuery闭包之浅见...",但实际上本篇涉及的多半是javascript"闭包"相关内容,之所以写这个标题,完全是因为自己平常用jQuery写前端习惯了。还有一个原因是:javascript"闭包"很容易造成"内存泄漏",而jQuery已经自动为我们规避、处理了由"闭包"引发的"内存泄漏"。

javascript的"闭包"是这样定义的:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

定义高度提炼,还是从代码实例来体验"闭包"的来龙去脉吧。本篇主要包括:

从面向对象的角度来理解"闭包"

调用内部函数的几种方式

变量的作用域

jQuery中的"闭包"

内存泄漏

从面向对象的角度来理解"闭包"

在面向对象中,我们是这样定义类:

publicclassSomeClass

[code]{
privatestring_someVariable;


publicvoidSomeMethod()

{

//TODO:

}

}

[/code]

在javascript中,有一种情况是,一个函数中包含了另外一个内部函数:


functionOutFunction()

[code]{
vartemp=0;

functioninnerFunction(){

}

}

[/code]

我们把OutFunction称为外部函数,把innerFunction称为内部函数。

这里的外部函数OutFunction相当于一个类。
外部函数变量temp相当于类的私有字段。
内部函数innerFunction相当于类的方法。

就像我们需要实例化类来调用类的实例方法一样,在javasript中,当我们在外部函数之外调用内部函数的时候,我们把此时的内部函数叫做"闭包",相当于面向对象的实例方法。

接下来,从"如何调用内部函数"这个角度,来循序渐进地体会何谓"闭包"。

调用内部函数的几种方式

□在外部函数之外直接调用内部函数


<scripttype="text/javascript">

[code]functionouterFunction(){
console.log('外部函数执行了');

functioninnerFunction(){

console.log('内部函数执行了');

}

}


$(function(){

innerFunction();

});

</script>

[/code]

结果报错:UncaughtReferenceError:innerFunctionisnotdefined

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之外调用内部函数的时候,自然就报错。

□在外部函数之内调用内部函数:


<scripttype="text/javascript">

[code]functionouterFunction(){
console.log('外部函数执行了');

functioninnerFunction(){

console.log('内部函数执行了');

}


innerFunction();

}


$(function(){

outerFunction();

});

</script>

[/code]

结果:
外部函数执行了
内部函数执行了

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之内调用内部函数,自然不会报错。

□在外部函数之外调用内部函数,把内部函数赋值给一个全局变量


<scripttype="text/javascript">

[code]
//全局变量

varglobalInner;


functionouterFunction(){

console.log('外部函数执行了');

functioninnerFunction(){

console.log('内部函数执行了');

}


globalInner=innerFunction;

}


$(function(){

outerFunction();

console.log('以下是全局变量调用内部方法:');

globalInner();

});

</script>

[/code]

结果:
外部函数执行了
以下是全局变量调用内部方法:
内部函数执行了

以上情况,全局变量globalInner相当于面向对象的委托,当把内部函数赋值给全局变量,调用委托方法就会调用内部函数。

□在外部函数之外调用内部函数,把内部函数赋值给一个变量:


<scripttype="text/javascript">

[code]
functionouterFunction(){

console.log('外部函数执行了');

functioninnerFunction(){

console.log('内部函数执行了');

}


returninnerFunction;

}


$(function(){

console.log('先把外部函数赋值给变量');

vartemp=outerFunction();

console.log('再执行外部函数变量');

temp();

});

</script>

[/code]

结果:
先把外部函数赋值给变量
外部函数执行了
再执行外部函数变量
内部函数执行了

以上情况,我们可以看到,内部函数不仅可以赋值给全局变量,还可以赋值给局部变量。

就像面向对象的方法会用到类的字段,内部函数也会用到变量,接下来体验变量的作用域。

变量的作用域

□内部函数变量


<scripttype="text/javascript">

[code]
functionouterFunction(){

functioninnerFunction(){

vartemp=0;

temp++;

console.log('内部函数的变量temp的值为:'+temp);

}

returninnerFunction;

}


$(function(){

varout1=outerFunction();

out1();

out1();


varout2=outerFunction();

out2();

out2();

});

</script>

[/code]

结果:
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1

从中我们可以看出内部函数变量temp的生命周期:
→当第一次执行内部函数,JavaScript运行时创建temp变量
→当第二次执行内部函数,JavaScript垃圾回收机制把先前的temp回收,并释放与该temp对应的内存,再创建一个新的内部函数变量temp

.....
所以,每次调用内部函数,内部函数的变量是全新的。也就是说,内部函数的变量与内部函数同生共灭。

□全局变量


<scripttype="text/javascript">

[code]//全局变量
vartemp=0;

functionouterFunction(){

functioninnerFunction(){

temp++;

console.log('全局变量temp的值为:'+temp);

}

returninnerFunction;

}


$(function(){

varout1=outerFunction();

out1();

out1();


varout2=outerFunction();

out2();

out2();

});

</script>

[/code]

结果:
全局变量temp的值为:1
全局变量temp的值为:2
全局变量temp的值为:3
全局变量temp的值为:4

可见,全局变量供外部函数和内部函数共享。

□外部函数变量


<scripttype="text/javascript">

[code]functionouterFunction(){
vartemp=0;

functioninnerFunction(){

temp++;

console.log('外部函数变量temp的值为:'+temp);

}

returninnerFunction;

}


$(function(){

varout1=outerFunction();

out1();

out1();


varout2=outerFunction();

out2();

out2();

});

</script>

[/code]

结果:
外部函数变量temp的值为:1
外部函数变量temp的值为:2
外部函数变量temp的值为:1
外部函数变量temp的值为:2

可见,外部函数的变量与外部函数同生共灭。

以上情况,更接近与"闭包"的原型。有如下几个要素:
1、外部函数
2、外部函数变量
3、内部函数

当我们在外部函数之外调用内部函数的时候,这时的内部函数就叫做"闭包",可以理解为面向对象的实例方法。"闭包"与外部函数变量的"外部环境"是外部函数,他俩与外部函数同生共灭。

一个外部函数中可以有多个内部函数,当调用"闭包"的时候,多个"闭包"共享外部函数变量:


<scripttype="text/javascript">

[code]functionouterFunction(){
vartemp=0;

functioninnerFunction1(){

temp++;

console.log('经innerFunction1,外部函数变量temp的值为:'+temp);

}


functioninnerFunction2(){

temp+=2;

console.log('经innerFunction2,外部函数变量temp的值为:'+temp);

}


return{'fn1':innerFunction1,'fn2':innerFunction2};

}


$(function(){

varout1=outerFunction();

out1.fn1();

out1.fn2();

out1.fn1();


varout2=outerFunction();

out2.fn1();

out2.fn2();

out2.fn1();

});

</script>


[/code]

结果:
经innerFunction1,外部函数变量temp的值为:1
经innerFunction2,外部函数变量temp的值为:3
经innerFunction1,外部函数变量temp的值为:4
经innerFunction1,外部函数变量temp的值为:1
经innerFunction2,外部函数变量temp的值为:3
经innerFunction1,外部函数变量temp的值为:4

jQuery中的"闭包"

"闭包"在jQuery中最常见的应用是,当Document加载完毕再执行jQuery部分:


<scripttype="text/javascript">

[code]$(document).ready(function(){
vartemp=0;


functioninnerFunction(){

temp++;

console.log(temp);

}


innerFunction();

innerFunction();

});

</script>

[/code]

结果:
1
2

可见,$(document).ready()的参数就是一个匿名外部函数,匿名函数内的函数是内部函数。

把元素的事件也可看作是内部函数:

<head>

[code]<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>
<title></title>

<scriptsrc="../Scripts/jquery-2.1.1.min.js"></script>

<scripttype="text/javascript">
$(document).ready(function(){

varcounter=0;


$('#btn1').click(function(event){

counter++;

console.log(counter);

});


$('#btn2').click(function(event){

counter--;

console.log(counter);

});

});

</script>

</head>

<body>

<inputid="btn1"type="button"value="递增"/>

<inputid="btn2"type="button"value="递减"/>

</body>


[/code]

可见,2个input元素的click事件看作是内部函数,共享同一个外部函数变量counter。

在循环体遍历中,把每次遍历的元素事件也可看作是内部函数:

<head>

[code]<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>
<title></title>

<scriptsrc="../Scripts/jquery-2.1.1.min.js"></script>

<scripttype="text/javascript">
$(document).ready(function(){

for(vari=0;i<3;i++){

$('<div>Print'+i+'</div>').click(function(){

console.log(i);

}).insertBefore('#results');

}

});

</script>

</head>

<body>

<divid="results"></div>

</body>

[/code]

页面呈现的结果如预期:
Print0
Print1
Print2

可当点击每个div的时候,原本以为控制器台应该显示:0,1,2,但实际显示的始终是3,为什么?
--i看作是匿名外部函数的"自由变量",当页面加载完毕后,i就变成了3。div的每次点击看作是内部函数的闭环,而所有的闭环都共享了值为3的这个变量。

我们可以使用jQuery的each()方法来解决以上问题,遍历一个数组,每一次遍历元素值都不同:


<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>

[code]<title></title>
<scriptsrc="../Scripts/jquery-2.1.1.min.js"></script>

<scripttype="text/javascript">
$(document).ready(function(){

$.each([0,1,2],function(index,value){

$('<div>Print'+value+'</div>').click(function(){

console.log(value);

}).insertBefore('#results');

});

});

</script>

</head>

<body>

<divid="results"></div>

</body>

[/code]

内存泄漏

内存泄漏可以狭隘地理解为:应用程序中变量对应一块内存,由于某些原因(比如代码上的漏洞),始终没有对变量及时实施手动或自动垃圾回收,内存没有得到及时释放,造成内存泄漏。

在JavaScript中,如果对象间的关系比较简单:





以上,A有一个属性指向B,B有一个属性指向C,当A被回收的时候,没有依赖B的对象,B随之被自动回收,C也被最终回收。

当对象间的关系比较复杂,比如存在循环引用的时候,如下:





以上,A有一个属性指向B,B有一个属性指向C,而C又有一个属性指向B,B和C之间存在循环引用。当A被回收之后,B和C是无法自动被回收的,在JavaScript中应该手动处理回收。

JavaScript闭包有可能会造成内存泄漏:

→内部函数闭包引发的内存泄漏


$(function(){

[code]varouterVal={};

functioninnerFunction(){

console.log(outerVal);

}


outerVal.fn=innerFunction;

returninnerFunction;

});

[/code]

以上,outVal是在内存中的一个对象,而内部函数innerFunction同样是内存中的一个对象。对象outVal的属性fn指向内部函数,而内部函数通过console.log(outerVal)引用outVal对象,这样outVal和内部函数存在循环引用的情况,如果不手动处理,就会发生"内存泄漏"。

如果,我们在内部函数中不显式引用outerVal对象变量,会造成"内存泄漏"吗?


$(function(){

[code]varouterVal={};

functioninnerFunction(){

console.log('hello');

}


outerVal.fn=innerFunction;

returninnerFunction;

});

[/code]

答案是:会的。因为,当内部函数被引用、调用的时候,即使内部函数没有显式引用外部函数的变量,也会隐式引用外部函数变量。

→元素事件引发的内存泄漏

在IE中,如下写法会造成内存泄漏:

$(document).ready(function(){

[code]varbutton=document.getElementById("btn");
button.onclick=function(){

console.log('hello');

returnfalse;

}

});

[/code]

而如下JavaScript写法不会造成内存泄漏:


functionhello(){

[code]console.log('hello');
returnfalse;

}

$(document).ready(function(){

varbutton=docuemtn.getElementById('btn');

button.onclick=hello;

});

[/code]

而在jQuery中,类似的写法就不用担心内存泄漏了,因为jQuery为我们做了自动处理来规避内存泄漏。

$(document).ready(function(){

[code]var$button=$('#btn');
$button.click(function(event){

event.preventDefault();

console.log('hello');

});

});

[/code]

总结

与"闭包"相关的包括:变量的作用域、javascript垃圾回收、内存泄漏,需在实践多体会。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: