SlideShare a Scribd company logo
由浅到深了解 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 的代码,里边几乎每个类都有用到类作为一个对象时的
属性与方法。




  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
后言
首先感谢你能够有耐心看到这里,我也没想到写了这么多才能够写得像样一点,
请别介意。

  不管是哪种语言,只要是支持类的,类都在这种语言中占了非常重要的地位,
但不是谁都能够掌握它,为了让无忧还没学过类以及对类这个东东还搞不清楚的网
友能够清楚一点了解类的概念以及用法,也为了对无忧做点贡献,我写了这篇文章,
希望大家能够喜欢。



下面是 wch3116 的跟贴:

    我们最近常谈 javascript 的 OO,但请大家要格外记住,javascript 不是“面
向对象”的语言,最多只能说是“基于对象”。
“面向对象”和“基于对象”之间的差别,我一时很难找到 简单且又准确 的词汇
来描述。
谈到 “面向对象”,我们首先可能会想到 c++,其次是 java,后来再就是 dot
net ,(其他的偏门语言我们这里不多讨论)
其实 c++ 在贯彻 “面向对象”思想方面 是不及 java 的,因为它还存在大量 “
过程”型的东西。 java 放弃了多重继承,重载算子等“繁索不实用”的东西,把
设计重点放在 interface(接口)上,不仅简化的编程者的工作繁琐度,而且让整
个框架看上去更加清晰。最重要的是 java 中所有的东西都是以类的形式存在的,
没有第二种形式。至于后来 dotnet 中的 c# ,看起来就像是 ms 牌子的 java.

    扯远了,回到 javascript 上来
说 Javascript 不是面向对象 不仅仅是 说 它没有真正意义上实现:抽象对象、 继
承、重载等等面向对象的功能
而是说 javascript 中的“类” 并不是真正广义上“类”的概念。类原本是只是一
个抽象定义,而 javascript 中通过“Function” 定义的类,本质上却是一个“对
象”!

   而且 javascript 的语法域并不是整个 IE 进程,而是以 Window 对象为单位的。

不同 Window 对象下相同的 Function 定义,并不是同一个“类”。

    比如:
a.htm 中你定义了一个 A 类 function A(){} , b.htm 中你也相同定义了这个 A
类 function A(){}

    在 a.htm 中你创建了一个实例: var a = new A();
你在 b.htm 中得到了 a.htm 的句柄 winAhandle
然后你得到 a.htm 中 a 实例的引用
var a = winAhandle.a;
你会发现 a instanceof A 是 false,换成 a instanceof winAhandle.A 便是
true 了
原因很简单,b.htm 中的 A 类并不等同于 a.htm 中的 A 类,这种“类”的语法域
只限于 一个相同的 Window 对象下(同一个 Window 对象并不仅是指同一个页面)

   这显然是有悖于 类是一个广义上的抽象定义 这种概念了

  会 VB 的人,也应该了解:VB4 之后,VB.NET 之前的 VB(包括 VBS)中的类,
也是这种情况,虽然它是通过 Class 的方式定义的。
比如你 new A 放到 session 里,下一次从 session 中取出来,便不是原先那个
对象了。
其实就是因为语法域不同,前一次定义的类,并不能保留到这一次,解析器不知道
他是什么东西,故不能还原了

  顺便提一下 javascript 中继承方式是采用的 原型(prototype)继承,详细
的介绍,大家可以去找这本书看看

  《Design Patterns Elements of Reusable Object Oriented Soffware》
中文版好像叫 《可复用面向对象的设计模式》
一本好书!!!

    javascript 中没有多重继承。多重继承在面向对象中并不是必须的。虽然多重
继承的重用性更好,但会导致类之间的关系过于复杂。
一般来说,一样事物,我们通常可以认为它主要是某一类事物的衍生物,单一继承
就够用了,至于其他的特性,我们可以借助接口来定义。
javascript 中也没有接口概念,javascript 不需要接口,因为它是一门解释型的
语言,不对实参的类型做预校验。作为一个参数对象,有没有某个方法,加载过程
中并不去检查,直到运行时,有则调用,无则异常。不需要强制性声明继承了哪个
接口才能做为参数调用。

  但实际中,我们还是需要设计一些接口,主要是出于 view 的考虑,整个框架
容易被读懂!

More Related Content

PDF
电子杂志(试刊)
PPT
Javascript之昨是今非
PPT
Spry框架的简单使用小结
PPT
javascript的分层概念 --- 阿当
PDF
Browser vs. Node.js Jackson Tian Shanghai
PDF
Ajax新手快车道
PPT
Ajax Transportation Methods
PDF
面向开发的前端性能优化
电子杂志(试刊)
Javascript之昨是今非
Spry框架的简单使用小结
javascript的分层概念 --- 阿当
Browser vs. Node.js Jackson Tian Shanghai
Ajax新手快车道
Ajax Transportation Methods
面向开发的前端性能优化

Viewers also liked (6)

PDF
Mediabarcamp 2013
PDF
Top Services For Online Work
PDF
Hcf4017 be
PDF
Champs behavior managementsigns
Mediabarcamp 2013
Top Services For Online Work
Hcf4017 be
Champs behavior managementsigns
Ad

Similar to 由浅到深了解Java Script类 (9)

PPT
Javascript OOP
PPT
面向对象的Js培训
PDF
Javascript进阶编程
PDF
JavaScript 教程
PPTX
Ecma script edition5-小试
PDF
Ecmascript
PDF
Js is js(程劭非) (1)
PPT
Javascript Training
PPT
第十期 阿甘Javascript开发思想(入门篇)
Javascript OOP
面向对象的Js培训
Javascript进阶编程
JavaScript 教程
Ecma script edition5-小试
Ecmascript
Js is js(程劭非) (1)
Javascript Training
第十期 阿甘Javascript开发思想(入门篇)
Ad

由浅到深了解Java Script类

  • 1. 由浅到深了解 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 哥,不好意思 。 。 。 嘿嘿) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 类的属性
  • 2. 这个 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 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 类的方法
  • 3. 方法这个词不大好理解,我觉得说成行为会更容易理解。一个人具有许多共同 的行为,比如睡觉、吃饭、走路等等,现在我们给 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.属性名 = 属性值;
  • 4. 比如给 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 这样的方式,以做到万无一失! >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 静态方法 与静态属性相似,它也有个另称:公共方法,同样属于类本身的。
  • 5. 静态方法的定义方式是: 类名.方法名 = 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 {
  • 6. 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 {
  • 7. 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); //泣红亭 构造函数不需要返回值,但如果你设置了返回值,可以把它当成一个函数来使 用。
  • 8. 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,因此不打算讲,只 是说说它的一点点思想以供参考。
  • 9. 在上边男孩的继承问题中,男孩其实不仅仅是继承自男人,还继承自孩子(有 男孩子,也有女孩子)这个类,因此,它同时继承了两个类:男人与男孩,这就是 所谓的多重继承。 好,这个问题打住,我们还是回归主题。 >>>>>>>>>>>>>>>>>>>> 先看第一个类的定义: 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() {
  • 10. 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 = "泣红亭"; }
  • 11. 定义类作为一个对象时的属性: 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 的代码,里边几乎每个类都有用到类作为一个对象时的 属性与方法。 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 后言
  • 12. 首先感谢你能够有耐心看到这里,我也没想到写了这么多才能够写得像样一点, 请别介意。 不管是哪种语言,只要是支持类的,类都在这种语言中占了非常重要的地位, 但不是谁都能够掌握它,为了让无忧还没学过类以及对类这个东东还搞不清楚的网 友能够清楚一点了解类的概念以及用法,也为了对无忧做点贡献,我写了这篇文章, 希望大家能够喜欢。 下面是 wch3116 的跟贴: 我们最近常谈 javascript 的 OO,但请大家要格外记住,javascript 不是“面 向对象”的语言,最多只能说是“基于对象”。 “面向对象”和“基于对象”之间的差别,我一时很难找到 简单且又准确 的词汇 来描述。 谈到 “面向对象”,我们首先可能会想到 c++,其次是 java,后来再就是 dot net ,(其他的偏门语言我们这里不多讨论) 其实 c++ 在贯彻 “面向对象”思想方面 是不及 java 的,因为它还存在大量 “ 过程”型的东西。 java 放弃了多重继承,重载算子等“繁索不实用”的东西,把 设计重点放在 interface(接口)上,不仅简化的编程者的工作繁琐度,而且让整 个框架看上去更加清晰。最重要的是 java 中所有的东西都是以类的形式存在的, 没有第二种形式。至于后来 dotnet 中的 c# ,看起来就像是 ms 牌子的 java. 扯远了,回到 javascript 上来 说 Javascript 不是面向对象 不仅仅是 说 它没有真正意义上实现:抽象对象、 继 承、重载等等面向对象的功能 而是说 javascript 中的“类” 并不是真正广义上“类”的概念。类原本是只是一 个抽象定义,而 javascript 中通过“Function” 定义的类,本质上却是一个“对 象”! 而且 javascript 的语法域并不是整个 IE 进程,而是以 Window 对象为单位的。 不同 Window 对象下相同的 Function 定义,并不是同一个“类”。 比如: a.htm 中你定义了一个 A 类 function A(){} , b.htm 中你也相同定义了这个 A 类 function A(){} 在 a.htm 中你创建了一个实例: var a = new A(); 你在 b.htm 中得到了 a.htm 的句柄 winAhandle 然后你得到 a.htm 中 a 实例的引用 var a = winAhandle.a; 你会发现 a instanceof A 是 false,换成 a instanceof winAhandle.A 便是 true 了
  • 13. 原因很简单,b.htm 中的 A 类并不等同于 a.htm 中的 A 类,这种“类”的语法域 只限于 一个相同的 Window 对象下(同一个 Window 对象并不仅是指同一个页面) 这显然是有悖于 类是一个广义上的抽象定义 这种概念了 会 VB 的人,也应该了解:VB4 之后,VB.NET 之前的 VB(包括 VBS)中的类, 也是这种情况,虽然它是通过 Class 的方式定义的。 比如你 new A 放到 session 里,下一次从 session 中取出来,便不是原先那个 对象了。 其实就是因为语法域不同,前一次定义的类,并不能保留到这一次,解析器不知道 他是什么东西,故不能还原了 顺便提一下 javascript 中继承方式是采用的 原型(prototype)继承,详细 的介绍,大家可以去找这本书看看 《Design Patterns Elements of Reusable Object Oriented Soffware》 中文版好像叫 《可复用面向对象的设计模式》 一本好书!!! javascript 中没有多重继承。多重继承在面向对象中并不是必须的。虽然多重 继承的重用性更好,但会导致类之间的关系过于复杂。 一般来说,一样事物,我们通常可以认为它主要是某一类事物的衍生物,单一继承 就够用了,至于其他的特性,我们可以借助接口来定义。 javascript 中也没有接口概念,javascript 不需要接口,因为它是一门解释型的 语言,不对实参的类型做预校验。作为一个参数对象,有没有某个方法,加载过程 中并不去检查,直到运行时,有则调用,无则异常。不需要强制性声明继承了哪个 接口才能做为参数调用。 但实际中,我们还是需要设计一些接口,主要是出于 view 的考虑,整个框架 容易被读懂!