《JavaScript高级程序设计 第三版》学习笔记 (八)块作用域及私有成员


一、模仿块级作用域

1.js不像C和java一样有块级作用域。

  在C和Java中,一对大括号{}决定一个作用域,比如for循环。在js中,变量可以在函数任何一处定义,并且忽略重复定义。变量初始化之前使用,值永远是undefined。这些内容在以前已经提到了。

[javascript] view plain copy

  1. //小实验  
  2. function count(){  
  3.     b=1;  
  4.     a=1;  
  5.     alert(a);//1  
  6.     for(var a=0;a<7;a++){  
  7.         //todo    
  8.     }  
  9.     var a;  
  10.     alert(a);  
  11. }  
  12. count();  
  13. alert(b);//1  
  14. alert(a);//a is not defined  

  这个例子中,count函数中没有定义b,所以在执行b=1时,在全局中添加了变量b;count种定义了a,注意是在for中定义的,a的作用域是count,在全局alert会报错;在for循环后面的var a是被解释器忽略的语句,因为它属于重复定义;重复定义并没有把a清空,后面的alert还是有效的。

2.块级作用域的模仿

(1)块级作用域的特点:一是内部定义的变量在外部不可用;二是可使用外部变量;三是立即执行不属于调用;四是如果没定义内部变量,去掉块和加上块完全没区别。
(2)根据以上特点,我们可以用《<JavaScript高级程序设计 第三版>学习笔记 (六) 函数与闭包详解》中介绍的“函数强制执行”,来模拟块级作用域。根据块级作用域的特点,不需要使用声明函数,也不需要块执行后返回一个特定结果,因此使用“正常一”的语法就能办到。详见:http://blog.csdn.net/lbxx1984/article/details/38944861
(3)匿名函数提供了一个子作用域,满足了块级作用域特点一;匿名函数作为子函数,利用作用域链满足了特点二;强制执行满足了特点三;前三点联合起来满足了特点四。
(4)使用强制执行的匿名函数模仿块级作用域,还有一个好处是在生成闭包时,能保护父级临时变量,因为强制执行是可以传递形参的。这在上一篇中已经讲过了。
(5)匿名函数毕竟也是函数,因此必要是可以保存它的执行返回值。所以说,这种方式其实比块级作用域强大得多,使用起来也灵活得多。

[javascript] view plain copy

  1. //小实验  
  2. var a=1;  
  3.   
  4. (function(){  
  5.     var i=1;  
  6.     for(var n=0;n<10;n++) i++;  
  7.     alert(i);//11  
  8.     alert(a);//1  
  9. })();  
  10.   
  11. (function(p){  
  12.     alert(p+2);//3  
  13. })(a);  
  14.   
  15. var d=(function(p){  
  16.     return p+1;  
  17. })(a);  
  18.   
  19. alert(d)//2  
  20. alert(i);//i is not defined  

二、模拟私有成员

  之前讨论的对象、继承,涉及到的方法都是公有成员。而一般的面向对象语言C++或JAVA,对象都是有私有成员的。js中没有类的改变,同样也没有对象的私有成员这个概念。但是可以通过某些特殊写法,模拟出私有成员。

1.特权模式

(1)在构造函数内部声明的变量、子函数以及参数,全部都是函数私有的,可以看作私有成员。给this指针添加的闭包,全部都是公有成员。

[javascript] view plain copy

  1. //小实验  
  2. function MyClass(a,b){  
  3.       
  4.     //private  
  5.     var _value=0;  
  6.     function add(){  
  7.         _value=a+b;  
  8.     }  
  9.       
  10.     //process  
  11.     add();  
  12.       
  13.     //public  
  14.     this.getValue=function(){  
  15.         return _value;    
  16.     }  
  17.     this.setValue=function(a1,b1){  
  18.         a=a1;  
  19.         b=b1;  
  20.         add();  
  21.     }  
  22. }  
  23.   
  24. var c1=new MyClass(1,2);  
  25. var c2=new MyClass(1,2);  
  26. c1.setValue(3,4);  
  27. alert(c1.getValue());//7  
  28. alert(c2.getValue());//3  
  29. alert(c1.add());//Object #<MyClass> has no method 'add'   

(2)这种模拟方法的优点是代码清晰明了,缺点是每个对象的成员函数,不论是私有还是公有,都是自己独有的。即便成员函数的功能相同,但他们却是存放在不同位置的不同函数。
(3)这种模拟方法只模仿了一个外形。OO语言中,每个对象的成员函数,在内存中只体现为一个单元,我们违背了这一原则。

2.私有作用域模式

(1)既然模拟出了块级作用域,可以考虑把块级作用域拿来,模拟一下私有成员。

[javascript] view plain copy

  1. //小实验  
  2. (function(){  
  3.     //private  
  4.     var _value=0;  
  5.     var _a=0;  
  6.     var _b=0;  
  7.     function add(){  
  8.         _value=_a+_b;  
  9.     }  
  10.       
  11.     //construct  
  12.     MyClass=function(a,b){  
  13.         _a=a;  
  14.         _b=b;  
  15.         add();    
  16.     }  
  17.   
  18.     //public  
  19.     MyClass.prototype.getValue=function(){  
  20.         return _value;    
  21.     }  
  22.     MyClass.prototype.setValue=function(a1,b1){  
  23.         _a=a1;  
  24.         _b=b1;  
  25.         add();  
  26.     }  
  27. })();  
  28.   
  29. var c1=new MyClass(1,2);  
  30. var c2=new MyClass(1,2);  
  31. c1.setValue(3,4);  
  32. alert(c1.getValue());//7  
  33. alert(c2.getValue());//7  
  34. alert(c1.add());//Object #<MyClass> has no method 'add'   

(2)上面例子我们使用了块级作用域,_value、_a、_b、add都是匿名函数的私有变量;MyClass前面没有var,所有MyClass是全局变量,可以在块级作用域外部使用;getValue和setValue是通过原型模式为MyClass添加的公共方法。
(3)这样写,MyClass所有实例的公共方法不再单独创建,而是共享使用;而私有方法,则是共享作用域链中的add,也不是独立创建的。针对成员方法而言,已经与C++和java非常接近了。
(4)但这样写的缺点也是显而易见的,就是私有数据成员_value、_a、_b在各实例之间也是共享的。这些成员,相当于static成员。

3.综合模式

(1)下面是我自己尝试写的模式。
(2)为了解决私有作用域模式中私有数据成员共享的问题,必须将私语数据成员写在MyClass内。但这样,公有成员函数就不能使用他们了。所以还是要使用一个特权函数,提供私有数据成员的接口,这个是无法避免的,因此只能让代价尽量的小,比如对私有数据成员做一个打包。
(3)私有方法是内存共享的,比如add函数。然而实际上,私有方法只要共享内存,就是private static状态,而private static状态方法,如果想使用非形参变量时,也只能使用private static的变量,比如_count。想使用实例的私有数据成员,只能通过形参传入。
(4)公有方法是内存共享的,是通过原型链实现的。公有方法使用私有数据成员,就必须得到私有数据成员的接口。我们做的this.data就是接口。
(5)整个模式中,闭包个数只有一个,就是this.data,这个闭包是无法避免的。

[javascript] view plain copy

  1. //小实验  
  2. (function(){  
  3.       
  4.     //private static  
  5.     var _count=0;  
  6.     function add(a,b){  
  7.         return a+b;  
  8.     }  
  9.       
  10.     //construct  
  11.     MyClass=function(a,b){  
  12.         //private  
  13.         var _data={  
  14.             value:0   
  15.         }  
  16.         //process  
  17.         _data.value=add(a,b);  
  18.         _count++;  
  19.         //public  
  20.         this.data=function(){return _data;}  
  21.     }  
  22.   
  23.     //public  
  24.     MyClass.prototype.getValue=function(){  
  25.         return this.data().value;     
  26.     }  
  27.     MyClass.prototype.setValue=function(a,b){  
  28.         this.data().value=add(a,b);  
  29.     }  
  30.     MyClass.prototype.getCount=function(){  
  31.         return _count;    
  32.     }  
  33. })();  
  34.   
  35. var c1=new MyClass(1,2);  
  36. var c2=new MyClass(1,2);  
  37. c1.setValue(3,4);  
  38. alert(c1.getValue());//7  
  39. alert(c2.getValue());//3  
  40. alert(c1.getCount());//2  
  41. alert(c2.getCount());//2 
分享