理解闭包Closure

熟悉OO语言的程序员,对JavaScript中的闭包Closure可能一时难以理解。本篇介绍下究竟什么是闭包,及一些典型的闭包的应用。

要理解闭包需要先学会3个基本的事实:

1.JavaScript允许你引用在当前函数外定义的变量

看个例子:
function sayHello(){
	var s = "Hello world";
	function say(words){
		return s + words;	//say函数允许使用定义在外的变量s
	}
	return say("Jack");
}
sayHello();	//Hello world Jack
你可能会感到惊讶,say函数虽然定义在sayHello函数内部,但在say函数作用域内,函数外部的变量s应该不可见才对,换句话说应该报错才对。但请结合这个代码例子再多念几遍事实一的标题 ^_^

2.即使外部函数已经返回,当前函数仍然可以引用在外部函数中定义的变量

简单地说就是闭包比创建它们的函数有更长的生命周期

function sayHello(){
	var s = "Hello world";
	function say(words){
		return s + words;
	}
	return say;	//返回函数对象的引用
}
var f = sayHello();
f("Jack");		//Hello world Jack
f("Zhang");		//Hello world Zhang
你可能会感到惊讶,变量f得到say函数的引用这没问题,毕竟函数指针大家已经见怪不怪了。但sayHello函数已经返回,sayHello函数内部的变量s应该被自动销毁,不可见才对。因此通过变量f调用say函数时,say函数内部会认为s是undefined才对。

实际结果已经表明即使外部函数已经返回,内部say函数仍旧能记住变量s的值。

因为JavaScript的函数值包含了比调用它们时执行所需要的代码还要多的信息。JavaScript函数值在内部存储它们可能会引用的定义在其封闭作用域的变量。那些在其所涵盖的作用域内跟踪变量的函数被称为闭包。

say函数就是个闭包,能访问并存储了外部变量s,每次调用say函数都能获得这个变量s。

函数可以引用在其作用域内的任何变量,包括参数和外部变量,利用这点改进函数:

function sayHello(s){
	function say(words){
		return s+ words;
	}
	return say;
}
var f = sayHello("Hello world");
f("Jack");		//Hello world Jack
f("Zhang");		//Hello world Zhang

var g = sayHello("Hi");
g("Jack");		//Hi Jack
g("Zhang");		//Hi Zhang
都是自解释代码,一看就能明白。闭包是如此优雅,因此JavaScript还提供了一种更方便的构建闭包的字面量语法,即函数表达式。继续改进:
function sayHello(s){
	return function(words){  //返回匿名函数,因为根本不想在局部调用该函数,因此不需要函数名
		return s + words;
	};
}
感受一下代码之美吧!

3.闭包可以更新外部变量的值

实际上,闭包存储的是外部变量的引用,而不是它们的副本。因此对于任何可以访问这些外部变量的闭包都可以进行更新

function box(){
	var val = undefined;
	return {
		set: function(v) { val = v; },
		get: function() { return val; },
		type: function() { return typeof val; }
	};
}

var b = box();
b.type();		//undefined
b.set(100);
b.get();		//100
b.type();		//number
该例子产生了个包含3个闭包(set,get,type)。它们共享val变量。

你已经理解了闭包,那闭包可以用在何处呢?

闭包的应用非常广泛,只举两个例子作抛砖引玉之用:

例子一:用闭包存储private数据

前一篇文章【JavaScript对象创建模式】http://blog.csdn.net/hongse_zxl/article/details/44595809  里已经介绍了JavaScript中并没有类,只能通过原型对象模拟实现类。普通OO语言的类中有个很重要的概念就是访问权限,即public,protected,private。

那JavaScript中如何实现私有变量呢?有人习惯在属性前加上_下划线,但这只是在命名方法给别人一些建议,希望别人不要修改这些属性,已达到私有变量的目的。当然每个人都应该遵守这些潜规则,但毕竟不是在技术上彻底实现私有,如果有人违反这个约定,开发者也只能两手一摊,无能为力。

如何在技术上实现私有呢?用闭包。

因为对象和闭包有相反的策略:对象的属性会自动暴露,闭包中的变量会自动隐藏。闭包是一种简朴的数据结构,将数据存储到封闭的变量中而不提供对这些变量的直接访问,获取闭包内部结构的唯一方式是该函数显式地提供获取它的途径。

我们利用闭包特性在对象中存储真正的私有数据。不是将数据作为对象的属性来存储,而是在构造函数中以变量的方式来存储它,并将对象的方法转变为引用这些变量的闭包:

function Person(name, age) { 
	this.sayName = function(){
		alert(name);
	};
        this.sayAge = function(){
		alert(age);
	};
}
 
  

这里sayName和sayAge方法是以变量的方法来引用name和age的,而非以this.name属性和this.age属性方式引用的。现在Person实例根本不包含任何实例属性,因此外部代码无法直接访问Person实例对象的name和age变量。

(当然该方法会导致方法副本的扩散,详见前一篇文章【JavaScript对象创建模式】http://blog.csdn.net/hongse_zxl/article/details/44595809  但为了安全,这代价是值得考虑的)

例子二:用闭包为方法包装参数

比如setTimeout(func, time),该方法大家都很熟悉,第一个参数为一个函数对象的引用,第二个参数为时间。作用是在指定时间后执行某方法。但其无法给方法传递参数:

function sayName(n1, n2) { 
    alert(n1 + n2);
}
sayName("Jack", "Zhang");    //JackZhang

setTimeout(sayName, 1000);    //NaN
上面这样用setTimeout通知浏览器1秒之后执行sayName方法,结果显示NaN,这并不意外,因为你并没有为sayName方法提供参数。

可以用闭包来为sayName方法提供参数:

function sayName(n1, n2) { 
    alert(n1 + n2);
}

function callLater(func, paramA, paramB){
   return function(){
        func.call(this, paramA, paramB);
    };
}
var sayNameLater = callLater(sayName, "Jack", "Zhang");

setTimeout(sayNameLater, 1000);  //1秒之后,显示JackZhang

各种注册事件如onClikc,onMouseMove同样可以

你可能感兴趣的:(JavaScript)