javascript 之function的(closure)闭包特性

[size=large][color=green][b]javascript之 function的闭包(closure)特性[/b][/color][/size]

javascript 的 function 具有闭包的特性,是 javascript 语言的一个特色。

[size=large][b]一、基础知识:变量的作用域 [/b][/size]

要理解闭包,首先必须理解javascript变量的作用域。
变量的作用域有两种:全局变量和局部变量。

1. 在javascript中,函数内部可以直接读取全局变量。
var n = 9999;
function f1(){
alert(n);
}
f1();//9999


2. 另外一方面,在函数外部无法读取函数内部的局部变量。
function f1(){
var n = 99;
}
alert(n); //[Error] Uncaught ReferenceError: n is not defined


3. 这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。
如果不用的话,则向上查找引用,直至全局。 没有找到时,则定义一个全局变量。

即:如果没有使用 var 关键字创建的变量都是全局变量,即使是在函数内部。


function f1(){
n =99;
}
f1();
console.log(n); //99


[size=large][b]二、基础知识:如何从外部读取局部变量[/b] [/size]

出于种种原因,我们有时候需要得到函数内的局部变量。
但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数内部再定义一个函数。
fucntion f1(){  
var n =99;
function f2(){
alert(n); //99
}
}

父对象的所有变量,对子对象都是可见的,反之不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗?
function f1(){
var n=99;
function f2(){
alert(n);
}
return f2;
}
var result = f1();

result(); //99


[size=large][b]三、闭包的概念及原理 [/b][/size]

[b]概念:[/b]
在 javascript 中,function 的闭包是指:
在 function 运行时产生的局部的上下文(闭包)环境,以 return 的形式被外部引用,而在内存中不被释放。

可以把具有闭包功能的 function 看成是一个生产闭包的工厂,每执行一次该函数,就会产生一个闭包。
这个闭包中的变量和函数被外部引用,而被保存在内存中。


[b]运行原理:[/b]
根据垃圾回收机制,局部变量在函数运行完毕后就被从内存中移除。
但是,如果局部变量被外部引用(或外部存在对局部变量的引用),
则局部变量 及 在其上下文环境内被引用到的变量不会被释放。
因此,由于局部变量的值还在内存中存在着,当引用闭包时,可以引用到其上下文环境的变量的值。

[b]构成闭包的两个要素:[/b]
1. 局部变量被外部引用。
在函数中以 return 形式返回给外部。
2. 被引用的局部变量类型是函数。
javascript是解释执行的,无需编译。只有被外部引用的对象是函数时,其上下文才会被保留,而形成闭包。因为此时函数还未执行,只是返回了一个函数的引用。所以需要保留其上下文,以备函数执行时使用。


[size=large][b]四、闭包的用途[/b] [/size]

闭包的用途有两个,
一个是可以在函数内部任意定义变量而不必担心命名冲突。
另一个就是让这些变量存储一些我们需要的值在内存中,不被释放。

怎么理解呢,看看如下:

function f1(){
var n =99;

nAdd = function(){
n+=1;
};

function f2(){
console.log(n);
}

return f2;
}

//test
var result = f1();

result(); // 99
nAdd();
result(); // 100

这段代码证明了,函数f1中的局部变量一直保存在内存中,并没有在f1调用后被自动清除。

另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量,所以nAdd可以作为一个setter,在函数外部对函数内部的局部变量进行操作。

再看下面的例子:

function f2(){
var n =99;

// 定义一个全局变量
nAdd = function(){
n += 1;
console.log(n);
};
}

// 执行函数,生成局部变量上下文
f2();

nAdd(); // 100


//



[size=large][b]五、使用闭包的注意点 [/b][/size]

1.由于闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决方法是:在推出函数之前,将不使用的局部变量删除。

2.闭包会在父函数外部,改变父函数内部变量的值。
所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。


[size=large][b]六、对比 function 的构造函数 [/b][/size]

前面已经说过,既然 javascript 的 function 的闭包特性,其本质就是生产一个包含有局部变量的闭包。

问题: 使用 function 的闭包来产生一个包含局部变量的闭包
与使用 function 的构造函数来new 一个函数的实例有什么不同?
回答:
1. 变量的访问权限不同。
2. 在只生产一个闭包的情况下,前者似乎更简洁。


请看下面的例子:

[b]使用闭包:[/b]


<!DOCTYPE html>
<html>
<body>

<p>Counting with a local variable.</p>
<button type="button" onclick="myFunction()">Count!</button>
<p id="demo">0</p>

<script>

// 定义一个匿名函数,并让它只执行一次。
// 变量 add 是这个匿名函数产生的闭包。
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

// add 函数可以访问到 counter 变量,但:
// counter 变量并不是 add 函数的一个属性。
// counter 变量只能通过 add 函数访问到。
function myFunction(){
document.getElementById("demo").innerHTML = add();
}

</script>

</body>
</html>




[b]使用构造函数同样也可以实现:[/b]



<!DOCTYPE html>
<html>
<body>

<p>Counting with a local variable.</p>
<button type="button" onclick="myFunction()">Count!</button>
<p id="demo">0</p>

<script>

//定义一个构造函数,可以多次调用。
function Factory(){
this.counter = 0;
this.increase = function(){
return ++this.counter;
}
}
// 变量 add 是这个构造函数产生的(instance)实例。
var add = new Factory();


// add 对象可以访问到 counter 变量
// counter 变量是 add 对象的一个属性。这个属性的访问权限是对外公开的。
// 可以使用 ++add.counter
function myFunction(){
document.getElementById("demo").innerHTML = add.increase();
// document.getElementById("demo").innerHTML = ++add.counter;
}


</script>

</body>
</html>




[size=large][b]七、闭包的应用 [/b][/size]

[b]1、使用闭包:创建 POJO (Plain Old Javascript Object)[/b]

有时需要创建一些简单的 Javascript 对象,用来存储一些属性。例如:

var dog = function (name, age) {
return {
name: name,
age: age
};
}


你可以像上面那样,每需要创建一类对象,就手动写一段代码。但是,完全没必要这么做。使用 Javascript 的闭包特性,可以为你动态的存储和构建:

var POJO = function () {
// 取得对 function 的 arguments 对象的引用
// arguments 是一个数组,包括所有调用 function 时输入的参数
var members = arguments;
return function () {
var obj = {}, i = 0, j = members.length;
for (; i < j; ++i) {
obj[members[i]] = arguments[i];
}

return obj;
};
};


使用我们创建的 POJO 函数:

//================================
// 创建一个 Dog 构造函数
var Dog = POJO('name', 'age');

// 创建一个 POJO 对象
var dog1 = Dog('Fido', 2);

// 这就是我们创建的 POJO 对象
// dog1 = {
// "name" : "Fido",
// "age" : 2
// }

//=================================
// 创建一个 Cat 构造函数
var Cat = POJO('name', 'color', 'age');

// 创建二个 POJO 对象
var cat1 = Cat('Areo', 'black', 2);
var cat2 = Cat('Hellen', 'yellow', 1);





[b]2、使用闭包:任意使用【增加】变量【属性】。并将其私有化,不必担心变量名冲突。[/b]

下面的代码实现了一个动态加载的进度条功能。
连续点击加载按钮时,进度条总是从头开始。

先看一下未使用的情况: id 是全局变量

<!DOCTYPE html>q<html>
<head>
<!-- LINK:
http://www.w3schools.com/w3css/default.asp
-->
<title>W3.CSS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">

<style>
.w3-progress-container-s{
height:0.15em;
}
</style>

</head>
<body class="w3-container">

<h2>Dynamic Progress Bar</h2>

<div class="w3-progress-container w3-progress-container-s">
<div id="myBar" class="w3-progressbar w3-cyan" style="width:1%"></div>
</div>

<br>
<button class="w3-btn w3-dark-grey" onclick="move()">Click Me</button>

<script>
var id; // id 是全局变量
function move() {
/*
if move() method is called repeatedly, then remove the previus interval function.
*/
if(typeof id !== "undefined"){
clearInterval(id);
}

var elem = document.getElementById("myBar");
var width = 1;
id = setInterval(frame, 10);
function frame() {
if (width >= 75) {
if( width < 98){
width = width + (100 - width) * 0.005;
elem.style.width = width + '%';
}else{
clearInterval(id);
}
}else{
width = width + 2;
elem.style.width = width + '%';
}
}
}//end of "move()"
</script>

</body>
</html>



只使用函数时所面对的问题:
每次函数执行时,所有函数内部的变量都会被重置。无法记忆上一次函数执行的结果。
如果把这些结果放在全局变量中,有二个问题:(1)可以被任意访问和修改。(2)可能会引起变量名的冲突,覆盖已经存在的变量。

闭包为函数提供了变量存放的地方。这些变量是函数私有的,只能被函数自己引用到。

使用闭包:
id 被私有化,成为局部变量。
私有化的局部变量不会引起和其它变量名冲突,可以任意起名,任意数量(只要内存够用)。


<!DOCTYPE html>
<html>
<head>
<!-- LINK:
http://www.w3schools.com/w3css/default.asp
__________________________________________________________________________________________
-->
<title>W3.CSS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">

<style>
.w3-progress-container-s{
height:0.15em;
}
</style>

</head>
<body class="w3-container">

<h2>Dynamic Progress Bar</h2>

<div class="w3-progress-container w3-progress-container-s">
<div id="myBar" class="w3-progressbar w3-cyan" style="width:0"></div>
</div>
<br>

<button class="w3-btn w3-dark-grey" onclick="startLoading()">Click Me</button>

<script>
var startLoading = function(){
var id; // id 被私有化,成为局部变量。且不会引起和其它变量名冲突。
return function move(){
/*
if move() method is called repeatedly, then remove the previus interval function.
*/
if(typeof id !== "undefined"){
clearInterval(id);
}

var elem = document.getElementById("myBar");
var width = 1;
id = setInterval(frame, 10);
function frame() {
if (width >= 75) {
if( width < 98){
width = width + (100 - width) * 0.005;
elem.style.width = width + '%';
}else{
clearInterval(id);
}
}else{
width = width + 2;
elem.style.width = width + '%';
}
}
};//end of "move()"
}();
</script>

</body>
</html>




[b]3、一个典型的 counter 对象[/b]


//
// 变量 i 只能通过 counter 对象提供的方法进行操作。
//
var counter = (function(){
var i = 0;
return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
}());


//
//改进为:可设置初始值
var counter = (function(i){
return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
}(0));




注意:除了函数内部的变量也以被 Closure,函数的参数也可以被 Closure。

例子:

function makeAdder(x) {
return function(y) {
return x + y;
};
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12



例子二:写一个 add 函数,可以这样调用:add(3,5) 或 add(3)(5)

function add(x, y){
if(y)
return x + y;
else
return function(y){
return x + y;
}
}



—————————————

javascript 函数基础系列文章

[url=http://lixh1986.iteye.com/blog/1955314]1、JavaScript之变量的作用域[/url]
[url=http://lixh1986.iteye.com/blog/2028899]2、javascript之变量类型与变量声明及函数变量的运行机制[/url]
[url=http://lixh1986.iteye.com/blog/1947017]3、javaScript之function定义[/url]
[url=http://lixh1986.iteye.com/blog/1896682]4、javascript之function的prototype对象[/url]
[url=http://lixh1986.iteye.com/blog/1891833]5、javascript之function的(closure)闭包特性[/url]
[url=http://lixh1986.iteye.com/blog/1960343]6、javascript之function的this[/url]
[url=http://lixh1986.iteye.com/blog/1943409]7、javascript之function的apply(), call()[/url]


___________


javascript 面向对象编程系列文章:

[url=http://lixh1986.iteye.com/blog/1958956]1、javaScript之面向对象编程[/url]
[url=http://lixh1986.iteye.com/blog/2332467]2、javascript之面向对象编程之属性继承[/url]
[url=http://lixh1986.iteye.com/blog/2348442]3、javascript之面向对象编程之原型继承[/url]


-


引用请注明
原文出处: http://lixh1986.iteye.com/blog/1891833

-


-
2016-07-12
-


-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值