- 模拟块级作用域
大家都知道在JavaScript中没有块级作用域的概念,我们可以通过使用闭包来模拟实现块级作用域,看下面的示例:
1 (function () { 2 for (var i = 0; i < 10; i++) { 3 //Do Nothing 4 } 5 6 alert(i); //输出10 7 })();
第6行可以访问到for循环块中的变量i,如果我们稍微修改以上代码,把for循环块放置在闭包中,情况就不一样了:
1 (function () { 2 (function () { 3 for (var i = 0; i < 10; i++) { 4 //Do Nothing 5 } 6 })(); 7 8 alert(i); //Error: 'i' is undefined 9 })();
在第8行访问变了i时,出现错误,实现了我们想要的块级作用域。
- 私有属性
在JavaScript中没有块级作用域的概念,同样也没有私有属性的概念,但是存在私有变量。如果我们想把一些数据封装隐藏起来要怎么做呢?想必大家可能已经想到了,可以通过使用闭包+私有变量的方式来实现对象的私有属性。
<1>.实例私有属性
实例私有属性的特点就是每个对象都会包含独立的属性,对象和对象之间没有共享。为了实现这个目标,可以在构造函数中增加一个私有变量,然后定义公共方法来访问这个私有变量,就如同其他OO语言的setter和getter一样,下列示例就实现了实例的私有属性:
1 //实例私有变量 2 function MyObject(name) { 3 //定义私有变量 4 //注意:此处没有用this.name,如果使用this.name变成公共属性了 5 var privateName = name; 6 //定义私有熟悉 7 var privateFunction = function () { 8 return "Private Function"; 9 } 10 11 //公共方法访问私有熟悉 12 MyObject.prototype.getName = function () { 13 return privateName; 14 } 15 MyObject.prototype.getFunction = function () { 16 return privateFunction(); 17 } 18 } 19 20 var moGyy = new MyObject("gyy"); 21 alert(moGyy.getName()); //输出gyy 22 alert(moGyy.getFunction()); //输出Private Function 23 24 var moCyy = new MyObject("cyy"); 25 alert(moCyy.getName()); //输出cyy 26 alert(moCyy.getFunction()); //输出Private Function
在上面的示例中创建的两个对象moGyy和moCyy的getName返回不同的值,同时如果想调用私有方法同样也需要公共接口。上面的示例中两个公共函数之所以能访问私有变量,是因为两个公共函数都是闭包,而闭包的作用域链中包含了包含函数的变量对象,因此在进行变量查找时,顺着作用域链可以访问包含函数中的私有变量。在上面的示例中把公共方法添加到MyObject的原型中,目的是防止每次创建对象都创建功能一样的两个函数实例。
<2>.静态私有属性
在有些情况下我们可能希望数据全局共享,那么可能就会用到静态属性,我们还是希望这个属性为私有的,那么怎样实现静态私有属性呢?首先这个私有应该在构造函数的外部,为了把构造函数外部的变量和构造函数结合为一体,可以使用闭包把私有变量和构造函数都包含在其作用域中,为了在闭包外面访问内部的构造函数,可以使用一个全局的变量来引用构造函数,如下代码示例:
1 //静态私有变量和实例私有变量 2 (function () { 3 //定义私有变量 4 var staticPrivateValue = ""; 5 6 //构造函数,把构织函数赋值给一个全局的变量 7 MyObject = function (name) { 8 //定义实例变量 9 this.name = name; 10 }; 11 12 //定义两个公共方法用于访问私有变量,再一次把公共方法添加到原型中 13 MyObject.prototype.getPrivateValue = function () { 14 return staticPrivateValue; 15 } 16 MyObject.prototype.setPrivateValue = function (value) { 17 staticPrivateValue = value; 18 } 19 })(); 20 21 var mo = new MyObject("jeff-gyy"); 22 mo.setPrivateValue("gyycyy"); //设置私有属性的值 23 alert(mo.getPrivateValue()); //输出gyycyy 24 alert(mo.name); //输出jeff-gyy 25 26 var mo1 = new MyObject("jeff-cyy"); 27 alert(mo1.getPrivateValue()); //输出gyycyy 28 alert(mo1.name); //输出jeff-cyy
从上面的代码来看mo1调用getPrivateValue函数返回的值就是mo设置的值"gyycyy",为什么会这样呢?首先我们定义了一个匿名函数并立即调用函数,函数包含了私有变量staticPrivateValue,那么为MyObject定义的两个原型方法其实通过闭包的作用域链可以访问在包含函数的私有变量,也就是getPrivateValue和setPrivateValue两个函数的作用域链中都包含了匿名函数的变量对象,我们知道作用域链中包含的变量对象其实就是一个指针,所以创建的两个对象通过公共方法房屋私有变量时,其实访问的都是匿名函数的变量对象中的staticPrivateValue,因此实现变量实例间共享的目的。从传统OO语言的角度来看我们实现的静态属性其实并不是真正意义上的静态,只是实现了静态属性实例共享的特点。
<3>.模块模式和增强模块模式
还有一种全局共享数据的方式就是singleton, 可以使用模块模式来实现Object类型的单例模式,也可以使用增强模块模式实现自定义类型的单例模式,如下示例:
1 //自定义构造函数 2 var mo = new function () { 3 //私有变量 4 var privateValue = ""; 5 6 //普通模块模式 7 return { 8 publicValue: "public", 9 //访问私有变量 10 getPrivateValue: function () { 11 return privateValue; 12 }, 13 setPrivateValue: function (value) { 14 privateValue = value; 15 } 16 } 17 }(); 18 19 mo.setPrivateValue("private value"); 20 alert(mo.getPrivateValue()); 21 alert(mo.publicFunction());
模块模式使用匿名函数来封装内部实现,就上面的示例匿名函数中包含了私有变量privateValue,返回的对象中的公共函数通过闭包的作用域链访问包含函数中的私有变量,由于定义的匿名函数被立即调用,因此变量mo引用的是返回的对象。上面的单例模式返回的是一个Object对象,可以使用增强模块模式实现自定义类型的单例模式:
1 //增强模块模式 2 //自定义构造函数 3 function MyObject(name) { 4 this.name = name; 5 }; 6 7 //自定义构造函数 8 var mo = new function () { 9 //私有变量 10 var privateValue = ""; 11 12 //增强模块模式 13 var o = new MyObject("gyycyy"); 14 o.publicValue = "public"; 15 //访问私有变量 16 o.getPrivateValue = function () { 17 return privateValue; 18 } 19 o.setPrivateValue = function (value) { 20 privateValue = value; 21 } 22 return o; 23 }(); 24 25 mo.setPrivateValue("private value"); 26 alert(mo.getPrivateValue()); 27 alert(mo.publicFunction());
以上代码示例实现了MyObject的单例模式。
最后需要提一点的是使用闭包有利也有弊,由于闭包作用域链引用包含函数的变量对象,因此会占用额外的内存,而且进行变量查找是也需要通过作用域链,因此会消耗查找时间,闭包越深情况更严重。另外在IE(早些版本)中由于垃圾回收机制使用引用计数,因此可能会出现循环引用的情况,导致内存泄露,如下示例:
1 function assignHandler(){ 2 var element = document.getElementById("someElement"); 3 element.onclick = function(){ 4 alert(element.id); 5 }; 6 }
在上面的代码中创建了一个闭包作为element的事件,该闭包引用了包含函数assingHandler的变量对象,而恰恰是对变量对象的引用使得element引用计数至少为1,因此element不会被回收,导致内存泄露。修改的方法大家可以想想。