JavaScript 闭包
1 闭包是什么?
什么是JavaScript 闭包,当一个内部函数在其外部函数之外被调用,就形成了一个闭包。
function init() { var name = "Mozilla"; // name 是一个被 init 创建的局部变量 function displayName() { // displayName() 是内部函数,一个闭包 alert(name); // 使用了父函数中声明的变量 } displayName(); } init();
init() 内部有一个局部变量name 和一个函数displayName()。displayName() 没有自己的局部变量,它可以访问外部函数的变量name 。要注意的是,如果displayName() 有同名局部变量name ,将使用自己的同名局部变量。
2 闭包的写法
闭包的5种写法如下:
//第1种写法 function Circle(r) { this.r = r; } Circle.PI = 3.14159; //使用prototype 属性定义的对象方法是实例方法(非静态),只有在实例化后才能使用,在方法内部可以通过this.xxx 来引用对象自身中的其他属性。 Circle.prototype.area = function() { return Circle.PI * this.r * this.r; } var c = new Circle(1.0); alert(c.area());
在JS 中一切都是对象,函数也是对象。此种写法只是给函数添加了一些属性。
//第2种写法 var Circle = function() { var obj = new Object(); obj.PI = 3.14159; obj.area = function( r ) { return this.PI * r * r; } return obj; } var c = new Circle(); alert( c.area( 1.0 ) );
声明一个变量,将函数作为属性赋值给变量。
//第3种写法 var Circle = new Object(); Circle.PI = 3.14159; Circle.Area = function( r ) { return this.PI * r * r; } alert( Circle.Area( 1.0 ) );
新建一个对象,然后给它添加属性和方法。
//第4种写法 var Circle={ "PI":3.14159, "area":function(r){ return this.PI * r * r; } }; alert( Circle.area(1.0) );
声明对象的同时把变量和方法也声明了。
//第5种写法 var Circle = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}"); alert( (new Circle()).area(1.0) );
呵呵,此种方式看着奇怪,亲测能用。
写法多种多样,看自己喜好。下面是一个有意思的demo,。
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
本质上讲,makeAdder 是一个函数工厂,创建了指定值和它的参数相加的函数。通过这个函数工厂创建了两个新的函数:一个将参数和5 求和,另一个将参数和10 求和。两个新函数都是闭包,共享相同的函数定义,但有各自不同的上下文环境。
3 闭包的用处
使用闭包,可以做很多事情,比如:实现面向对象,代码更优雅简洁,某些方面提高代码执行效率。
3.1 匿名自执行函数
JS 中所有的变量,如果不加上var 关键字,默认会添加到全局对象的属性。如果随意地加入很多全局变量会污染全局对象,造成开发维护上的困难。实际情况中有些函数只需执行一次,其内部变量无需维护,比如页面初始化,这里可用到闭包。
var data= { table : [], tree : {} }; (function(dm){ for(var i = 0; i < dm.table.rows; i++){ var row = dm.table.rows[i]; for(var j = 0; j < row.cells; i++){ drawCell(i, j); } } })(data);
创建一个匿名函数,并立即执行,由于函数外部不能引用它的内部变量,执行完后会立即释放资源,不会污染全局对象。
3.2 缓存结果
有些耗费大量时间计算出来的数据可能会重复使用,为避免重复计算,可将其缓存起来。调用的时候如果还没计算则计算一次,并存储在内部变量,以后再调用则直接使用,无需再次计算。
var CachedSearchBox = (function(){ var cache = {}, count = []; return { attachSearchBox : function(dsid){ if(dsid in cache){//如果结果在缓存中 return cache[dsid];//直接返回缓存中的对象 } var fsb = new uikit.webctrl.SearchBox(dsid);//新建 cache[dsid] = fsb;//更新缓存 if(count.length > 100){//保正缓存的大小<=100 delete cache[count.shift()]; } return fsb; }, clearSearchBox : function(dsid){ if(dsid in cache){ cache[dsid].clearSelection(); } } }; })(); CachedSearchBox.attachSearchBox("input");
3.3 封装
var person = function(){ //变量作用域为函数内部,外部无法访问 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }(); print(person.name);//直接访问,结果为undefined print(person.getName()); person.setName("abruzzi"); print(person.getName()); 得到结果如下: undefined default abruzzi
函数内部变量不能在其外部被访问,通过return 方法的形式将内部变量暴露出来,实现面向对象的封装特性。
3.4 实现类和继承
function Person(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var p = new Person(); p.setName("Tom"); alert(p.getName()); var Jack = function(){}; //继承自Person Jack.prototype = new Person(); //添加私有方法 Jack.prototype.Say = function(){ alert("Hello,my name is Jack"); }; var j = new Jack(); j.setName("Jack"); j.Say(); alert(j.getName());
声明的Person 就像一个类,new 一个这样的对象,并调用其方法。Jack 继承Person,并添加了自己的方法。这样就实现了面向对象的特性。
浙公网安备 33010602011771号