2009年8月2日星期日
[转]Apache commons 简介
BeanUtils
Commons-BeanUtils 提供对 Java 反射和自省API的包装
Betwixt
Betwixt提供将 JavaBean 映射至 XML 文档,以及相反映射的服务.
Chain
Chain 提供实现组织复杂的处理流程的"责任链模式".
CLI
CLI 提供针对命令行参数,选项,选项组,强制选项等的简单API.
Codec
Codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder.
Collections
Commons-Collections 提供一个类包来扩展和增加标准的 Java Collection框架
Configuration
Commons-Configuration 工具对各种各式的配置和参考文件提供读取帮助.
Daemon
一种 unix-daemon-like java 代码的替代机制
DBCP
Commons-DBCP 提供数据库连接池服务
DbUtils
DbUtils 是一个 JDBC helper 类库,完成数据库任务的简单的资源清除代码.
Digester
Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件.
Discovery
Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。.
EL
Commons-EL 提供在JSP2.0规范中定义的EL表达式的解释器.
FileUpload
FileUpload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力
HttpClient
Commons-HttpClient 提供了可以工作于HTTP协议客户端的一个框架.
IO
IO 是一个 I/O 工具集
Jelly
Jelly是一个基于 XML 的脚本和处理引擎。 Jelly 借鉴了 JSP 定指标签,Velocity, Cocoon和Xdoclet中的脚本引擎的许多优点。Jelly 可以用在命令行, Ant 或者 Servlet之中。
Jexl
Jexl是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言。.
JXPath
Commons-JXPath 提供了使用Xpath语法操纵符合Java类命名规范的 JavaBeans的工具。也支持 maps, DOM 和其他对象模型。.
Lang
Commons-Lang 提供了许多许多通用的工具类集,提供了一些java.lang中类的扩展功能
Latka
Commons-Latka 是一个HTTP 功能测试包,用于自动化的QA,验收和衰减测试.
Launcher
Launcher 组件是一个交叉平台的Java 应用载入器。 Commons-launcher 消除了需要批处理或者Shell脚本来载入Java 类。.原始的 Java 类来自于Jakarta Tomcat 4.0 项目
Logging
Commons-Logging 是一个各种 logging API实现的包裹类.
Math
Math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题.
Modeler
Commons-Modeler 提供了建模兼容JMX规范的 Mbean的机制.
Net
Net 是一个网络工具集,基于 NetComponents 代码,包括 FTP 客户端等等。
Pool
Commons-Pool 提供了通用对象池接口,一个用于创建模块化对象池的工具包,以及通常的对象池实现.
Primitives
Commons-Primitives提供了一个更小,更快和更易使用的对Java基本类型的支持。当前主要是针对基本类型的 collection。.
Validator
The commons-validator提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则。支持校验规则的和错误消息的国际化。
2009年7月29日星期三
[转]对控制反转(IOC)和依赖注入(DI)的理解
我想对这一概念进行一个个人的阐述,以方便我的理解。控制反转,从字面意思来看,就是控制权由被动变主动又变为被动,或被动变主动又变为被动。从这个角度来说,IOC就变得非常容易理解了。
举个例子:你的主管要求你做一件事情,这个时候就存在这么几个过程,
主管命令你做事情(这个时候主动权在主管,你是被动的)
你接到命令做事情(这个时候主题是你,你是主动的,控制权在你手里)
你完成事情(这个时候主题依然是你,控制权在你手里)
报告主管做完事情(主动权又叫交到主管手里了)
上面的整个过程就完成了一次IOC,从上面可以看出,IOC的基本思想是控制权的转换过程。
举个代码的例子:
假如有Class A,Class B,在A内部会初始化一个B,调用B的一个方法DoMethod
public Class B
{
public void DoMethod()
{
/// do somthing;
}
}
public Class A
{
public void Excute()
{
B b = new B();
b.DoMethod();
}
}
假如在Main函数中如下执行:
A a = new A();
a.Excute();
从这两行代码来看,事实上也存在一个IOC的过程,a——>b——>a,理解的关键点就在在A的内部调用Excute的时候,方法b.DoMethod的执行。
理解了IOC,我们再看一下DI,从上面A调用B我们可以看出,在初始化一个A的实例时,也必须实例化一个B,也就是说如果没有B或者B出了问题,A就无法实例化,这就产生了一种依赖,就是A依赖B,这种依赖从设计的角度来说就是耦合,显然它是无法高内聚低耦合的要求的。这个时候就需要解耦,当然解耦有很多种方法,而DI就是其中一种。不管任何一种解耦方法,都不是说使A和B完全没有关系,而是把这种 关系的实现变得隐晦,不那么直接,但是又很容易实现,而且易于扩展,不像上面的代码那样,直接new一个B出来。那为什么我们总是把IOC和DI联系到一 起呢?是因为DI的基本思想就是IOC,而体现IOC 思想的方法还有另外一个,那就是Service Locator,这个方法好像涉及到的很少。
××××××××××××××××××××IoC的几种实现类型
(1)Type1接口注入
通常做法是利用接口将调用者与实现者分离。
public class Sport {
private InterfaceBall ball; //InterfaceBall是定义的接口
public void init() {
//Basketball实现了InterfaceBall接口
ball = (InterfaceBall) Class.forName("Basketball").newInstance();
}
}
Sport类在编译期依赖于InterfaceBall的实现,为了将调用者与实现者分离,我们动态生成Basketball类并通了强制类型转换为InterfaceBall。Apache Avalon是一个典型的Type1型IoC容器。
(2)setter方法注入
在类中暴露setter方法来实现依赖关系。
public class Sport {
private InterfaceBall ball;
public void setBall(InterfaceBall arg) {
ball = arg;
}
}
这种方式对已经习惯了JavaBean的程序员而言,更显直观。Spring就是实现了该类型的轻量级容器。
(3)Type3构造子注入
即通过构造方法完成依赖关系。
public class Sport {
private InterfaceBall ball;
public Sport(InterfaceBall arg) {
ball = arg;
}
}
可以看到,通过类的构造方法建立依赖关系。由于Type3在构造期就形成了对象的依赖关系,即存对象的重用变的困难。有些框架需要组件提供一个默认的构造 方法,此时就体现了Type3的局限性。通常所有的参数都是通过构造方法注入的,当对象间的依赖关系较多时,构造方法就显的比较复杂,不利于单元测试。 PicoContainer就是实现了Type3依赖注入模式的轻量级容器。
DI,依赖注入,从字面意思就可以看出,依赖是通过外接注入的方式来实现的。这就实现了解耦,而DI的方式通常有三种,
构造器注入
属性设置器注入
接口注入(我感觉接口注入是同时存在于上两种注入方式的,而不应该独立出来)
以 上的阐述只是为了先让我们能对IOC和DI有一个感性的理解,那么IOC他真正解决的问题是什么呢?我们讲了那么多主动被动的问题,那我们是从什么视角来 看待这个问题的呢?所谓为什么你是主动,而我不是主动呢?这就需要一个参照物,那这个参照物是什么呢?就是容器,在容器中来体现主动和被动。“用白话来 讲,就是由容器控制程序之 间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓”控制反转“的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转", 这是通常对IOC的一个解释。从容器的角度来看主动和被动,和由容器来控制程序之间的关系,应该是相通的,是一个意思。到这里我们就应该基本明白 了,IOC要解决的就是程序之间调用的一个问题,它应该是一个思想层面的东西,是一个中心,就像一支乐队的指挥,而程序就是乐器,通过指挥来协调各种乐 器,来演奏出美好的音乐来。
2009年7月28日星期二
[转]PO/VO/DAO/BO/POJO是什么(JAVA几种对象的解释)
VO:value object值对象。通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.个人觉得同DTO(数据传输对象),在web上传递.
DAO:data access object数据访问对象,此对象用于访问数据库。通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作.
BO:business object业务对象,封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作;
POJO:plain ordinary java object 简单无规则java对象,我个人觉得它和其他不是一个层面上的东西,VO和PO应该都属于它.
PO:
persistant object持久对象
最形象的理解就是一个PO就是数据库中的一条记录。
好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
BO:
business object业务对象
主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。
比如一个简历,有教育经历、工作经历、社会 关系等等。
我们可以把教育经历对应一个PO,工作经历对应一个PO,社会 关系对应一个PO。
建立一个对应简历的BO对象处理简历,每个BO包含这些PO。
这样处理业务逻辑时,我们就可以针对BO去处理。
VO :
value object值对象
ViewObject表现层对象
主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
DTO :
Data Transfer Object数据传输对象
主要用于远程调用等需要大量传输对象的地方。
比如我们一张表有100个字段,那么对应的PO就有100个属性。
但是我们界面上只要显示10个字段,
客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,
这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO
POJO :
plain ordinary java object 简单java对象
个人感觉POJO是最常见最多变的对象,是一个中间对象,也是我们最常打交道的对象。
一个POJO持久化以后就是PO
直接用它传递、传递过程中就是DTO
直接用来对应表示层就是VO
DAO:
data access object数据访问对象
这个大家最熟悉,和上面几个O区别最大,基本没有互相转化的可能性和必要.
主要用来封装对数据库的访问。通过它可以把POJO持久化为PO,用PO组装出来VO、DTO
2009年7月14日星期二
我看到中华民族的希望了
表弟今年才刚初三,准备考中考。成绩不错能进个上海的不错的重点高中,比我强。以前从没和他谈论过政治的问题。今日能的聊了一下,让我惊叹不已。他对中国政治体制缺点的认识和我当年相比真是天差地别。他谈到的中国政府中存在的既得利益集团,以及政府将新一代的70后、80后"团结"到既得利益团体来维护统治地位等等问题。他还提到了网络将来在中国民主化进程中会起到较大的作用。并谈到谷歌被焦点访谈归类为传播淫秽思想网站的事。他认为这是政府为了控制网络这一新的新闻传播媒体的决心的显示。
虽然说的不仅全对,我也和他交流了一下我的想法。谈及了绿坝、GFW等等一些实事,以及让他看了下汶川地震危房小学的一些视频。
从他身上看到了中国民主发展的可能性。深感欣慰!
2009年7月10日星期五
[转]JavaScript运行机制浅探
<script type="text/javascript">
alert(i); // ?
var i = 1;
</script>
输出结果是undefined,
这种现象被称成"预解析":JavaScript引擎会优先解析var变量和function定义。在预解析完成后,才会执行代码。如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),运行顺序是:
step1. 读入第一个代码段
step2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到step5
step3. 对var变量和function定义做"预解析"(永远不会报错的,因为只解析正确的声明)
step4. 执行代码段,有错则报错(比如变量未定义)
step5. 如果还有下一个代码段,则读入下一个代码段,重复step2
step6. 结束
上面的分析,已经能解释很多问题了,但老觉得欠缺点什么。比如step3里,"预解析"究竟是怎么回事?还有step4里,看下面的例子:
<script type="text/javascript">
alert(i); // error: i is not defined.
i = 1;
</script>
为什么第一句会导致错误?JavaScript中,变量不是可以不定义吗?
编译过程
时间如白马过隙,书柜旁翻开恍如隔世般的《编译原理》,熟悉而又陌生的空白处有着这样的笔记:
对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成。
但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。
简单地说,词法分析是将字符流(char stream)转换为记号流(token stream), 比如将c = a - b;转换为:
NAME "c"
EQUALS
NAME "a"
MINUS
NAME "b"
SEMICOLON
上面只是示例,更进一步的了解请查看 Lexical Analysis.
《JavaScript权威指南》的第2章,讲的就是词法结构(Lexical Structure),ECMA-262
中也有描述。词法结构是一门语言的基础,很容易掌握。至于词法分析的实现那是另一个研究领域,在此不探究。
可以拿自然语言来类比,词法分析是一对一的硬性翻译,比如一段英文,逐词翻译成中文,得到的是一堆记号流,还很难理解。进一步的翻译,就需要语法分析了,下图是一个条件语句的语法树:
synttree
构造语法树的时候,如果发现无法构造,比如if(a { i = 2; }, 就会报语法错误,并结束整个代码块的解析,这就是本文开头部分的step2.
通过语法分析,构造出语法树后,翻译出来的句子可能还会有模糊不清的地方,接下来还需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配。对于弱类型语言来说,这一步可能没有(精力有限,没时间去看JS的引擎实现,不敢确定JS引擎中是否有语义检查这一步)。
通过上面的分析可以看出,对于JavaScript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。
上面的编译过程,还是无法更深入的解释文章开头部分的"预解析",我们还得仔细探究下JavaScript代码的执行过程。
执行过程
周爱民在《JavaScript语言精髓与编程实践》的第二部分,对此有非常仔细的分析。下面是我的一些领悟:
通过编译,JavaScript代码已经翻译成了语法树,然后会立刻按照语法树执行。
进一步的执行过程,需要理解JavaScript的作用域机制,JavaScript采用的是词法作用域(lexcical
scope)。通俗地讲,就是JavaScript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static
scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,实际上,只能说JS的作用域机制非常接近lexical scope.
JS引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。execution
context中包含一个调用对象(call object),
调用对象是一个scriptObject结构,用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意:varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到
scriptObject上)。scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致。
lexical scope是JS的作用域机制,还需要理解它的实现方法,这就是作用域链(scope chain)。scope
chain是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父级scriptObject中寻找,一直
lookup到全局调用对象(global object)。
当一个函数实例执行时,会创建或关联到一个闭包(closure)。
scriptObject用来静态保存与函数相关的变量表,closure则在执行期动态保存这些变量表及其运行值。closure的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁,closure则要等要数据引用为空后,由JS引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。
别被上面的一堆名词吓住,一旦理解了执行环境、调用对象、闭包、词法作用域、作用域链这些概念,JS语言的很多现象都能迎刃而解。
小结
至此,对于文章开头部分的疑问,可以解释得很清楚了:
step3中所谓的"预解析",其实是在step2的语法分析阶段完成,并存储在语法树中。当执行到函数实例时,会将varDelcs和funcDecls从语法树中复制到执行环境的scriptObject上。
step4中,未定义变量意味着在scriptObject的变量表中找不到,JS引擎会沿着scriptObject的upvalue往上寻找,如果都没找到,对于写操作i
= 1; 最后就会等价为 window.i = 1;
给window对象新增了一个属性。对于读操作,如果一直追溯到全局执行环境的scriptObject上都找不到,就会产生运行期错误。
理解后,雾散花开,天空一片晴朗。
[转]javascript---类的构建
1.构造方式定义类
2.原型方式定义类
3.构造和原型结合方式创建类
4.动态的原型方式
各有优缺点,具体如下
1.构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say
Java代码
1. //构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say
2. function User(){
3. this.username = "zhangsan";//this.不能丢
4.
5. this.say = function(){//this.不能丢
6. alert("username:" + this.username );//this.不能丢
7. }
8. //下面注释的这种写法不对
9. // function say(){
10. // alert("username:" + this.username );
11. // }
12. }
13.
14. var user = new User();
15. user.username = "lisi";
16. user.say();//username:lisi
17.
18. var user1 = new User();
19. user1.say();//username:zhangsan,不受user对象的影响
//构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say
function User(){
this.username = "zhangsan";//this.不能丢
this.say = function(){//this.不能丢
alert("username:" + this.username );//this.不能丢
}
//下面注释的这种写法不对
// function say(){
// alert("username:" + this.username );
// }
}
var user = new User();
user.username = "lisi";
user.say();//username:lisi
var user1 = new User();
user1.say();//username:zhangsan,不受user对象的影响
//多个实例对象不共享类的属性值:
Java代码
1. //多个实例对象不共享类的属性值,如下:
2. function User(){
3. this.username = new Array();//this.不能丢
4.
5. this.say = function(){//this.不能丢
6. alert("username:" + this.username );//this.不能丢
7. }
8. }
9.
10. var user = new User();
11. user.username.push("zhangsan");
12. user.say();//username:zhangsan
13.
14. var user1 = new User();
15. user1.say();//user1的username为空,不为zhangsan,因为user1的属性值不受user影响
//多个实例对象不共享类的属性值,如下:
function User(){
this.username = new Array();//this.不能丢
this.say = function(){//this.不能丢
alert("username:" + this.username );//this.不能丢
}
}
var user = new User();
user.username.push("zhangsan");
user.say();//username:zhangsan
var user1 = new User();
user1.say();//user1的username为空,不为zhangsan,因为user1的属性值不受user影响
2.原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
Java代码
1. //原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
2. function User(){
3. }
4. User.prototype.username = "zhangsan";
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8.
9. var user = new User();
10. user.username = "lisi";
11. user.say();//username:lisi
12.
13. var user1 = new User();
14. user1.say();//username:zhangsan
//原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
function User(){
}
User.prototype.username = "zhangsan";
User.prototype.say = function(){
alert("username: " + this.username );
}
var user = new User();
user.username = "lisi";
user.say();//username:lisi
var user1 = new User();
user1.say();//username:zhangsan
类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享:
Java代码
1. //类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享,如下
2. function User(){
3. }
4. User.prototype.username = new Array();
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8.
9. var user = new User();
10. user.username.push("zhangsan") ;
11. user.say();//username:zhangsan
12.
13. var user1 = new User();
14. user1.say();//username:zhangsan,因为user1属性也会受到user的影响,user1和user指向同一引用,即共享同一属性
//类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享,如下
function User(){
}
User.prototype.username = new Array();
User.prototype.say = function(){
alert("username: " + this.username );
}
var user = new User();
user.username.push("zhangsan") ;
user.say();//username:zhangsan
var user1 = new User();
user1.say();//username:zhangsan,因为user1属性也会受到user的影响,user1和user指向同一引用,即共享同一属性
3.构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
Java代码
1. //构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
2. function User(){
3. this.username = "zhangsan";
4. }
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8. var user = new User();
9. alert(user.username);
//构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
function User(){
this.username = "zhangsan";
}
User.prototype.say = function(){
alert("username: " + this.username );
}
var user = new User();
alert(user.username);
4.动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的
Java代码
1. ////动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的
2.
3. function User(){
4. this.username = "zhangsan";
5.
6. if(typeof User.flag == "undefined"){
7. alert("execute...");
8. User.prototype.say = function(){
9. alert("username: " + this.username );
10. }
11.
12. User.flag = true;
13. }
14. }
15.
16. var user1 = new User();//execute...
17. var user2 = new User();//不会打印出execute...,则说明方法只创建了一次,即方法只会产生一个
18. user1.say();//username
////动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的
function User(){
this.username = "zhangsan";
if(typeof User.flag == "undefined"){
alert("execute...");
User.prototype.say = function(){
alert("username: " + this.username );
}
User.flag = true;
}
}
var user1 = new User();//execute...
var user2 = new User();//不会打印出execute...,则说明方法只创建了一次,即方法只会产生一个
user1.say();//username
总结:
构造方式定义类:缺点:类里的方法,每个实例对象都会产生一个,导致产生大量方法;优点:所有实例对象都单独拥有一份类里的属性,即属性不共享
原型方法定义类:缺点:所有实例对象都共同拥有一份类里的属性,即属性共享。优点:类的方法只会产生一个,不会产生大量方法
构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的
[转]了解JavaScript类
许多刚接触编程的朋友都可能理解不了类,其实类是对我们这个现实世界的模拟,把它说成"类别"或者"类型"可能会更容易理解一些。比如"人"这种动物就是一个类,而具体某一个人就是"人"这个类的一个实例,"人"可以有许多实例(地球人超过六十亿了),但"人"这个类只有一个。你或许会说那男人和女人不也是人么?怎么只能有一个?其实这里要谈到一个继承的东西,后边才讲,请继续看下去。
*********如何建立一个类?
在C++中是以class来声明一个类的,JavaScript与
C++不同,它使用了与函数一样的function来声明,这就让许多学Jscript的朋友把类与函数混在一起了,在Jscript中函数与类确实有些混,但使用久了自然而然会理解,这篇文章是针对想进攻面向对象编程的朋友而写,就不打算一下子讨论得太深了。
请看下边这个类的定义:
function WuYouUser()
{
this.Name; //名字
}
上边的代码定义了一个WuYouUser(无忧用户)类,它有个属性:Name(名字)。Name就是WuYouUser类的一个属性。
一个类有固定的属性,但类的实例却有不同的属性值,就像我是属于"人"这个类的,性别是男,而我有一个女同学,她也属于"人"类,但她的性别属性值却为女。
那么如何声明某个类的一个实例呢?非常简单:
var Wo = new WuYouUser(); //实例一:"我"
var Biyuan = new WuYouUser(); //实例二:"碧原"(Biyuan哥,不好意思。。。嘿嘿)
*********类的属性
这个Wo(我)就是WuYouUser类的一个实例,它拥有WuYouUser给它的一切:Name属性、Sex属性以及Age属性,我们可以这样子来设置它的属性:
Wo.Name = "泣红亭";
很简单是不是?试着运行
window.document.write(Wo.Name);
看看,是不是输出了我的名字:泣红亭?
同样设置一下碧原兄的属性
Biyuan.Name = "碧原";
运行
window.document.write(Biyuan.Name);
可以看到输出了"碧原",也就说明了Biyuan与Wo同样是WuYouUser类的实例,但却是不同的实体,具有不同的属性值。
属性是可以设置默认值的,无忧里都有记录大家各自发了多少贴子,我们也同样给WuYouUser类添加一个发贴数量的属性ArticleCount
function WuYouUser()
{
this.Name;
this.ArticleCount = 0;
}
一个无忧新用户刚注册完之后他的发贴数量为0,在上边的代码中可以看到直接给属性ArticleCount设置值为0。
可以运行一下这样的代码:
var Wo = new WuYouUser();
window.document.write(Wo.ArticleCount);
可以看到输出了0,说明ArticleCount属性被我们成功设置默认值为0
*********类的方法
方法这个词不大好理解,我觉得说成行为会更容易理解。一个人具有许多共同的行为,比如睡觉、吃饭、走路等等,现在我们给WuYouUser类添加一个发贴的方法。
function WuYouUser()
{
this.Name;
this.ArticleCount = 0;
this.NewArticle = function()
{
/*
*
* 具体如何发贴我们大家都知道,不就是打打字,加加图片再按一下保存之类的按钮么?
* 关于具体如何发贴的代码没有必要在这里写出来,我们要了解的仅仅是方法的定义与使用
* 我们在这里实现一个最简单的功能,也是很重要的功能:给我们的发贴数量加上1!
* 注意:恐龙等级就是这样加出来的,因此呀……大家狂发贴吧。。。
*/
this.ArticleCount++;
}
}
既然定义好了这个方法,我们来试试效果如何:
var Wo = new WuYouUser();
Wo.NewArticle();
document.write(Wo.ArticleCount);
可以看到输出了1,说明我们发贴成功了!真是有历史纪念意义的一刻,离恐龙等级又近一步了。
*********静态属性
静态属性又称公共属性,它不属于某个类的实例,而是直接属于某个类。
比如说无忧用户有一个属性:注册用户的数量,它是属于整个无忧用户的,而不是属于泣红亭或者谁的
静态属性的声明方法是:
类名.prototype.属性名 = 属性值;
比如给WuYouUser类定义一个注册用户的数量Count:
WuYouUser.prototype.Count = 0;
那么如何读取它呢?有两种方法:
1. 直接用 WuYouUser.prototype.Count
2. 使用Wo.Count
这两者没有区别,都是得到0
虽然读取方法可以有两种,但在改变它的时候却得特别小心了,请看下边代码
var Biyuan = new WuYouUser();
WuYouUser.prototype.Count++;
document.write(Wo.Count);
document.write(Biyuan.Count);
你会发现两者的Count属性都是1,也就是说WuYouUser.prototype.Count改变了会影响到各个实例的相应属性,其实原理就是Wo、Biyuan的Count属性与WuYouUser.prototype.Count根本就是同一个!
现在来看另外一段代码:
var Biyuan = new WuYouUser();
Biyuan.Count++; //特别注意一下这里,这是直接改变Biyuan的Count属性
document.write(Biyuan.Count); // 输出 1
document.write(WuYouUser.prototype.Count); //输出 0
document.write(Wo.Count); //同样输出0,为什么?
可以看到如果直接修改实例的静态属性值,那么会出现其它实例甚至类的静态属性与它不同步了?这是因为直接修改的时候,该实例会生成一个属于该实例的属性
Count,这个时候Biyuan.Count不再与WuYouUser.prototype.Count是同一个了,也不与Wo.Count是同一个,这个Count属性是属于Biyuan自己所有的,以后改变了它也只是影响它自己而已。
因此如果不是特别的需要,建议不管在读取还是赋值的时候,都统一使用WuYouUser.prototype.Count这样的方式,以做到万无一失!
*********静态方法
与静态属性相似,它也有个另称:公共方法,同样属于类本身的。
静态方法的定义方式是:
类名.方法名 = function(参数1,参数2...参数n)
{
//方法代码
}
我们现在就来定义一个无忧用户类的注册新用户静态方法:
WuYouUser.prototype.AddOne = function()
{
//*** 同样具体代码不写出来,给静态属性Count增加1,表示注册用户数量又多一个
WuYouUser.prototype.Count++;
}
现在我们来看看如何用它,同样有两种方法:
1.直接使用WuYouUser.prototype.AddOne()
2.使用某实例的AddOne()
这两种方法没有什么不同:
var Wo = new WuYouUser();
var Biyuan = new WuYouUser();
document.write(WuYouUser.prototype.Count); // 0
Wo.AddOne();
document.write(WuYouUser.prototype.Count); // 1
document.write(Wo.Count); // 1
document.write(Biyuan.Count); // 1
WuYouUser.prototype.AddOne();
document.write(WuYouUser.prototype.Count); // 2
document.write(Wo.Count); // 2
document.write(Biyuan.Count); // 2
可以看出不管是使用Wo.AddOne()还是WuYouUser.prototype.AddOne()效果都是一样的,都是给WuYouUser.prototype.Count加上1
现在再看一段代码:
function NewClass() //由于上边的WuYouUser类不合适当这个例子的代码,我声明了一个新类NewClass
{
this.Name = "泣红亭"; //这里默认值为我的名字
}
NewClass.prototype.ChangeName = function(NewName)
{
this.Name = NewName;
}
var Wo = new NewClass();
Wo.ChangeName("郑运涛"); //我的真名
可以看到Wo.Name确实已经变成了"郑运涛",这个方法似乎是可以用的,但里边是不是内有天机呢?
再看下边的代码,类的定义以及ChangeName的定义我们照样,但改变一下下边的代码:
NewClass.prototype.ChangeName("郑运涛");
document.write(NewClass.Name); //undefined,即未定义
document.write(NewClass.prototype.Name); //郑运涛
var Wo = new NewClass();
document.write(Wo.Name); //泣红亭
可以看到我们并没有定义NewClass.prototype.Name这个静态属性,但编译器给我们自己加了一个。
可是再看下边输出Wo.Name,它并不是为"郑运涛",而是原来的默认值"泣红亭",说明了什么?
其实很简单,看一下NewClass的定义里已经有Name这个属性,因此Wo也有自己的Name属性,它跟NewClass.prototype.Name并不是同一个的,因此就还是那样子。
那为什么前一个例子运行了Wo.ChangeName("郑运涛")却能够实现改变Wo.Name属性呢?其实在这里跟改变Wo.Count的值是同一个道理,编译器自动给Wo增加了一个方法ChangeName,这个方法代码与NewClass.prototype.ChangeName一样,但
Wo.ChangeName是Wo这个实例所特有的,而非NewClass.prototype.ChangeName!
分析可知道在静态方法里尽量不要使用this这样的关键字来引用实例本身的属性,除非你有特别的目的,而且能够清楚地明白这里边的运行机制!
如果真的需要在静态方法里使用this,可以直接把this当作参数传进去:
NewClass.ChangeName = function(This,NewName) //注意这里是This,不是this
{
This.Name = NewName;
}
*********构造函数
一个类在初始化的时候其实也是一个函数的执行过程,这个函数就是构造函数,我们看一下下边的代码:
function WuYouUser()
{
this.Name = "泣红亭"; //默认定义为泣红亭
alert(this.Name);
}
var Wo = new WuYouUser();//可以看到出现一个窗口显示泣红亭三个字
可以看出类的定义不仅仅是定义了它的属性与方法,还同时可以加入一些代码,而这些代码就是该类的构造函数的代码,在实例声明过程中被执行!
其实说起来,类的属性与类的方法都是在构造函数里执行定义的,看下边的代码:
function WuYouUser()
{
this.Name = "泣红亭";
return;
this.Sex = "男";
}
var Wo = new WuYouUser();
document.write(Wo.Name); //泣红亭
document.write(Wo.Sex); //undefined,即未定义
看得出什么?Sex属性是在return;之后的,而WuYouUser类的构造函数遇到return即停止运行,换句话说this.Sex =
"男";这一行是没有被执行,即Sex属性根本没有被定义!
构造函数可以有参数,参数值在声明实例的时候被传入:
function WuYouUser(Name)
{
this.Name = Name;
}
var Wo = new WuYouUser("泣红亭");
document.write(Wo.Name); //泣红亭
构造函数不需要返回值,但如果你设置了返回值,可以把它当成一个函数来使用。
function Sum(a, b)
{
this.a = a;
this.b = b;
return this.a + this.b;
}
document.write(Sum(12, 23)); //输出的是12与23的和35
var Obj = new Sum(12,23);
document.write(Obj.a) // 12
document.write(Obj.b) // 23
感觉挺奇妙,对吧?我写这文章写着写着也觉得挺奇妙的,呵呵!
但强烈建议不要把一个类当成一个函数来使用!如果你需要的是一个函数,请直接写成函数而不要写成类,以免搞混了。
*********继承
继承这个词在面向对象的编程里是非常重要的,虽然JavaScript并不是真正面向对象的语言,而是跟VB一样是基于对象的语言,它同样提供了继承机制。
文章开头时谈到了男人与女人,这也同样是两个不同的类,但却具有相同的一些属性以及方法,而这些相同的特性是来自"人"这个类的,换句话说男人与女人继承了"人"的所有特性!但是男人与女人却有其不同的地方,编程语言里的继承也一样,一个类A继承了另一个类B,那么类B就是类A的父类,类A就是类B的派生类,也称为子类。比如男人就是人的派生类,而人就是男人的父类。最高一级的类称为基类,想象一下就可以明白,男人继承自人,男孩继承自男人,人就是男孩的基类,男人就是男孩的父类。
*********题外:多重继承
这里再涉及一个多重继承的话题,但如果你仅仅是学JavaScript的话就没有必要看下去,因为JavaScript不提供多重继承,准确一点说没有一种简单而标准的方法来实现多重继承(其实是有办法实现的,只不过麻烦了一点,而且确实没有必要)。
在C++中是有多重继承的概念的,这里是讨论JavaScript,因此不打算讲,只是说说它的一点点思想以供参考。
在上边男孩的继承问题中,男孩其实不仅仅是继承自男人,还继承自孩子(有男孩子,也有女孩子)这个类,因此,它同时继承了两个类:男人与男孩,这就是所谓的多重继承。
好,这个问题打住,我们还是回归主题。
先看第一个类的定义:
function A()
{
this.Name = "泣红亭";
alert(this.Name);
}
这个类定义了一个属性Name,默认值为"泣红亭"
现在看第二个类的定义:
function B()
{
this.Sex = "男";
alert(this.Sex);
}
定义了一个属性Sex,默认值为"男"
继承的方式就是 子类.prototype = new 父类();
现在我们来让B类继承A类:
B.prototype = new A();
运行这一段代码:
var Obj = new B(); //首先打开警告窗口显示"泣红亭",再显示"男"
可以从上边的结果看出B类继承了A类,拥有了A类的属性Name,并且执行了A类的构造函数,而且A类的构造函数在B类的构造函数执行之前执行。因此我们利用这个可以实现重写父类的方法以及重设置父类某属性的默认值:
function A()
{
this.Name = "泣红亭";
this.Show = function()
{
alert("这是A类的Show方法");
}
alert(this.Name);
}
function B()
{
this.Name = "郑运涛";
this.Show = function()
{
alert("这是B类的Show方法");
}
alert(this.Name);
}
var Obj = new B();
Obj.Show();
结果出现了三次警告窗口,第一个内容为泣红亭,是执行A类的构造函数里的alert(this.Name),那时候Name属性值还为"泣红亭",因为
B类的构造函数还没执行,第二次内容为"郑运涛",这是B类里的alert(this.Name),因为B类的构造函数里给Name重赋值为"郑运涛"。最后是调用了Obj.Show(),执行了不是A类的Show方法里的Show(显示"这是A类的Show方法"),而是执行了B类的Show(显示"这是B类的Show方法"),很明显Show方法被重写了。
*********类作为一个对象时的属性与方法(不知道如何简洁地表达,因此用了这么长的题目)
不知道在这里谈这个话题是否有点混人耳目,但又觉得不谈这篇文章就不算完整,因为文章目的就是要让人搞清楚类的方方面面。
看了这一小节的题目,或许你会觉得奇怪,类就是类,怎么会"作为一个对象"呢?在JavaScript里,一切都是对象,包括类!对象可以有属性,可以有方法,类也同样可以有,但这个非常容易跟前边说到的静态属性与静态方法搞混了,因此要仔细看清楚两者的分别!
定义一个类:
function WuYouUser()
{
this.Name = "泣红亭";
}
定义类作为一个对象时的属性:
WuYouUser.Url = "http://www.51js.com";
//静态属性的定义是:WuYouUser.prototype.Url = "http://www.51js.com";
var Wo = new WuYouUser();
document.write(WuYouUser.Url); //http://www.51js.com
document.write(Wo.Url); //undefined,即未定义!注意这里的未定义
从这里可以看出Url这个属性是WuYouUser自个所有,改变了它与其它类以及它的子类完全无关!
引用类的属性只有一个办法,就是类名.属性名,改变它也一样。
定义类作为一个对象时的方法:
WuYouUser.ChangeUrl = function()
{
this.Url = "http://51js.com";
}
你或许会觉得奇怪,这里的this是什么?因为ChangeUrl这个方法是属于对象WuYouUser的,因此this指的就是WuYouUser本身!
可以运行下边的代码试试:
document.write(WuYouUser.Url); // http://www.51js.com
WuYouUser.ChangeUrl();
document.write(WuYouUser.Url); // http://51js.com
明显ChangeUrl直接修改了WuYouUser.Url的值,因此后边才能输出http://51js.com
如果你这一节看不明白,也不要着急,编程嘛,许多东东都只能意会不能言传,而且我又没口才,说不清楚,只要以后多写写代码,多用用类自然而然会体会到这一些,还有可以去看看JSVM的代码,里边几乎每个类都有用到类作为一个对象时的属性与方法。