Javacript OOP

目录:

一、公有/变量及方法

二、继承

三、附录

 

内容:

一、      公有/私有的变量及方法

本部分使用例子 Person演示如何在 Javascript 类中创建属于类的变量及方法。

部分二阐述Javascript 中类的继承。

摘要
私有变量 使用关键字 ‘var’ 在对象内部定义, 变量可访问范围为对象内部、内部私有函数,特有方法。

私有函数 为对象内部定义的函数, 或使用 var functionName=function(){...} 定义的函数

特有方法 使用 this.methodName=function(){...} 定义,可供对象外及内调用。

公有属性 使用 this.variableName 定义,在对象外部可读/写公有属性。

公有方法 使用 Classname.prototype.methodName = function(){...} 定义,可供外部调用。

公有属性 使用 Classname.prototype.propertyName = someValue 定义。

静态属性 使用 Classname.propertyName = someValue 定义。

 

 

范例
在下面例子中,一个人的名字和种族依赖于创建实例时的设置。

当创建实例时,一个人的年龄是1岁和一个不可见的寿命岁数。

一个人有 weight[体重] 变量,可从“eating[吃东西] 方法和 exercising[运动] 方法中更改.

每执行 eating 一次, weight 增加三倍 weight*=3

每执行 exercising 一次, weight 减少两倍,即 weight/=2

 

每当人进行吃东西和运动时, 他们的年龄增加一岁。

Person 对象有一个所有人可更改的公有属性 clothing[着衣], 和另一个公有属性 ‘dirtFactor[污垢值].

每当人吃东西和运动时,dirtFactor 增加,当执行 shower()[沐浴] 方法时, dirtFactor 减少。

范例源码

<script type="text/javascript">
/*<![CDATA[*/
function Person(n, race)
{
 
this.constructor.population++ ;
 
// ************************************************************************ 
 
// 私有变量及方法 
 
// 只有 特有方法 可访问编辑及执行 
 
// *********************************************************************** 
 
var alive = true, age = 1;
 
var maxAge = 70 + Math.round(Math.random() * 15) + Math.round(Math.random() * 15);
 
 
function makeOlder()
 
{
  
return alive = (++ age <= maxAge)
 
}
 
var myName = n ? n : "John Doe";
 
var weight = 1;
 
// ************************************************************************ 
 
// 特有方法
 
// 可供外部及内部执行
 
// ************************************************************************ 
 
this.toString = this.getName =
  
function()
  
{
   
return myName
  
}
 
 
this.eat =
  
function()
  
{
   
if (makeOlder())
   
{
    
this.dirtFactor++ ;
    
return weight *= 3;
   
}
   
else alert(myName + " can't eat, he's dead!");
  
}
 
 
this.exercise =
  
function()
  
{
   
if (makeOlder())
   
{
    
this.dirtFactor++ ;
    
return weight /= 2;
   
}
   
else alert(myName + " can't exercise, he's dead!");
  
}
 
 
this.weigh =
  
function()
  
{
   
return weight
  
}
 
 
this.getRace =
  
function()
  
{
   
return race
  
}
 
 
this.getAge =
  
function()
  
{
   
return age
  
}
 
 
this.muchTimePasses =
  
function()
  
{
   age 
+= 50;
   
this.dirtFactor = 10;
  
}
 
// ************************************************************************ 
 
// 公有属性  可供任何人读写
 
// ************************************************************************ 
 
this.clothing = "nothing/naked";
 
this.dirtFactor = 0;
}

// ************************************************************************ 
// 公有方法  可供任何人读写
// ************************************************************************ 
Person.
prototype.beCool =
 
function()
 
{
  
this.clothing = "khakis and black shirt"
 
}

Person.
prototype.shower =
 
function()
 
{
  
this.dirtFactor = 2
 
}

Person.
prototype.showLegs =
 
function()
 
{
  alert
(this + " has " + this.legs + " legs")
 
}

Person.
prototype.amputate =
 
function()
 
{
  
this.legs--
 
}
 
// ************************************************************************ 
// 元型属性 -- 可供任何人读写(但不可叠加)
// ************************************************************************ 
Person.
prototype.legs = 2;
// ************************************************************************ 
// 静态属性  可供任何人读写 
// ************************************************************************ 
Person.population 
= 0;

// 这是一个使用 Person 类的例子
function RunGavinsLife()
{
 
var gk = new Person("Gavin""caucasian");
 
//创建一个新的 Person 实例
 
 
var lk = new Person("Lisa""caucasian");
 
//创建一个新的 Person 实例
  
 alert
("There are now " + Person.population + " people");
 
 gk.showLegs
()
 lk.showLegs
()
 
// Gavin  Lisa 共享元型属性 Person.prototype.legs 使用 this.legs 访问
 
 gk.race 
= "hispanic"
 
//写入公有变量 race 但并非覆盖私有变量 race
 
 alert
(gk + "'s real race is " + gk.getRace())
 
//从私有变量 race 返回变量值 caucasian 私有 race 变量在创建实例时设置
 
 gk.eat
();
 gk.eat
();
 gk.eat
();
 
//eat 方法返回 weight(体重), 每次 * 3  3, 9, 27
 
 alert
(gk + " weighs " + gk.weigh() + " pounds and has a dirt factor of " + gk.dirtFactor);
 gk.exercise
();
 
//执行锻炼方法, 每次 weight/=2
 
 gk.beCool
();
 
//赶时髦方法。。。 
 
 gk.clothing 
= "Pimp Outfit";
 
//clothing 是个公有变量,可供外部更新。。。 
 
 gk.shower
();
 alert
("Existing shower technology has gotten " + gk + " to a dirt factor of " + gk.dirtFactor);
 gk.muchTimePasses
();
 
//五十年后 
 
 Person.
prototype.shower =
  
function()
  
{
   
//为所有人重设 shower(沐浴方法,洗掉身上的污垢。。。
   
this.dirtFactor = 0;
  
}
 
 gk.beCool 
=
  
function()
  
{
   
//更新着衣流行
   
this.clothing = "tinfoil";
  
};
 
 gk.beCool
();
 gk.shower
();
 alert
("Fashionable " + gk + " at "
  
+ gk.getAge() + " years old is now wearing "
  
+ gk.clothing + " with dirt factor "
  
+ gk.dirtFactor);
  
 gk.amputate
();
 
//截肢函数。。。
 
 gk.showLegs
();
 lk.showLegs
();
 
//Gavin 被截去一条腿,Lisa 完好无损
  
 gk.muchTimePasses
();
 
// 50 年过去,Gavin 现在超过100
  
 gk.eat
();
 
//Gavin 已百年, 不能再吃东西。。。
}

RunGavinsLife
();
/*]]*/
</script>

 

备注:
maxAge 是一个私有变量,且没提供特有方法的访问,因此外部无法访问。

race
是一个私有变量,只从对象参数进行定义。
变量从对象构造器传递可视为私有变量。

值为 “tinfoil” 的方法“beCool()” 仅供 gk 对象使用,主要为 Gavin老年时的着衣选择, 并非提供给 Person 类。
其他 Person 实例依旧使用旧的 “beCool()” 方法,也就是着衣元素为 “khakis black shirt”

注意隐式调用,即 gk.toString() 方法,它使对象实例可以像字串那样拼加。
也就是说, alert(gk+' is so cool.') 时, gk 输出为 Gavin, alert(gk.toString()+' is so cool.') 的作用相同。
所有类型的所有对象默认都有 .toString() 方法,但你可以重定义之。

你不能把公有方法赋值给主对象,必须使用 prototype 属性,如同 beCool() shower() 方法[注意:原作者说是在他印象中]

就像我尝试使用 amputate() 函数,和显示 Person.prototype.legs 一样,prototype 属性是为所有“对象实例”所共享。访问 lk.legs 依旧是“2”。无论如何, 尝试更改实例的 .legs 属性,可使用 gk.legs=1 或在 Person类中使用 this.lengs=1。这就是为什么,调用 gk.amputate() 方法只除去了 Gavin的一条腿,而不是 Lisa。要更改原型属性,你必须使用诸如 Person.prototype.legs=1 this.constructor.prototype.legs=1

可以这样定义匿名函数 foo = function(p1,p2){ some code }
使用 new Function() 如:foo = new Function('p1','p2','code');
在全局中定义可防止函数访问内部变量。

就象在前面代码中注释的那样,给 gk.race 设置某值并非重写私有变量 race的值。
你可以有公有和私有变量名为相同的变量,也就是两个名字相同但并非一样的变量, 如下面代码, foo this.foo 并非一样。

function StupidClass()
{
 
var foo = "internal";
 
this.foo = "external";
 
this.yell =
  
function()
  
{
   alert
("Internal foo is " + foo + "/nExternal foo is " + this.foo)
  
}
}


私有函数和特有方法跟私有变量和公共属性其实差不多,他们都是在 new object[创建新实例]时产生。所以,每当 new Person时,都有一份 makeOlder(), toString(), getName(), eat(), exercise(), weigh(), getRace(), getAge(), muchTimePasses() 的新拷贝。对比公有方法[beCool shower只有一份拷贝],公共方法可以节省 内存和提高效率。

注意,使用公有替代私有的代价,私有成员外部无法访问,因此代码结构比较强壮,但增加了内存和效率的负担;公有成员外部可访问和更改,因此代码不够强壮,但节省了内存的开销和提高了效率。

例如,在上面例子中的 age maxAge为私有变量; 在外部只能通过公有方法 getAge访问[只读] age,在外部无法访问 maxAge。当把它们更改为公有属性时,如 gk.maxAge=1; gk.age=200;那样的话在外部可以直接更改它们的值,但是 alive 变量将无法得到有效的控制。

二、      继承
在前面部分,我们看到怎样创建JS 类,包括 私有、特有、公有 的属性和方法。
本部分阐述 Javascript 中的继承。

摘要
你将看到如何继承, 如:
ChildClassName.prototype = new ParentClass();

你应当记住,要重置构造器,如:
ChildClassName.prototype.constructor=ChildClassName.

你只要在子类中使用 Function.call() 方法, 就可调用被继承类的方法,
Javascript
不支持受保护(protected)方法。

范例
下面的例子,演示两个类间的继承操作:

<script type="text/javascript">
/*<![CDATA[*/

function Mammal(name)
{
 
this.name = name;
 
this.offspring = [];
}

Mammal.
prototype.haveABaby =
 
function()
 
{
  
var newBaby = new Mammal("Baby " + this.name);
  
this.offspring.push(newBaby);
  
return newBaby;
 
}

Mammal.
prototype.toString =
 
function()
 
{
  
return '[Mammal "'+this.name+'"]';
 
}

function Cat(name)
{
 
this.name = name;
}

Cat.
prototype = new Mammal();
// 在这里发生继承 

Cat.
prototype.constructor = Cat;
// Cat 继承 Mammal 的构造器 

Cat.
prototype.toString =
 
function()
 
{
  
return '[Cat "'+this.name+'"]';
 
}

var someAnimal = new Mammal('Mr. Biggles');
alert
('someAnimal is ' + someAnimal);
// 结果为 someAnimal is [Mammal "Mr. Biggles"] someAnimal 隐含为 someAnimal.toString()

var myPet = new Cat('Felix');
alert
('myPet is ' + myPet);
// 结果为 myPet is [Cat "Felix"] myPet 隐含为 myPet.toString()

myPet.haveABaby
();
// 调用 继承自 Mammal 的方法 .haveABaby

alert
(myPet.offspring.length);
// shows that the cat has one baby now 

alert
(myPet.offspring[0]);
// 结果为 '[Mammal "Baby Felix"]',注意,这里 Mammal 如果正确的话,应当为 Cat

/*]]*/
</script>

 

使用 .constructor 属性
看看上面代码中的末行。Cat 的子类应当是 Cat[例子中为 Mammal]
当执行 haveABaby() 方法时,这个方法创建一个新的 Mammal
虽然我们可为 Cat 类创建一个新的 haveABaby() 方法,但是更好的方法是在被继承类中修正 haveABaby() 方法。

所有 JS 的对象实例都有一个 constructor 属性,他指向该对象的类。
如:someAnimal.constructor==Mammmal 为真。
有了这个知识,我们可以重写 haveABaby() 方法,如:

Mammal.prototype.haveABaby =
 
function()
 
{
  
var newBaby = new this.constructor("Baby " + this.name);
  
this.offspring.push(newBaby);
  
return newBaby;
 
}

...
myPet.haveABaby
();
// Same as before: calls the method inherited from Mammal 
alert
(myPet.offspring[0]);
// Now results in '[Cat "Baby Felix"]'


调用’super[基类关键字]’方法
现在,让我们扩展一下上面的例子,如:每当小猫出生时,它会”mew[]”的叫一下。
为了这个,我们写一个 Cat.prototype.haveABaby() 方法,并且该方法可以调用 Mammal.prototype.haveABaby() 方法,代码如下:

Cat.prototype.haveABaby =
 
function()
 
{
  Mammal.
prototype.haveABaby.call(this);
  alert
("mew!");
 
}


上面的代码看起来或许很奇怪,因为Javascript 对象中没有任何可访问父对象的关键字,但可使用函数的call() 方法替代之。
有关 Function.call() 方法的详细用法,请见 MSDN docs for call()

创建我们自己的“super”属性
虽然 使用 Mammal.prototype.haveABaby.call(this) 访问父类是一个解决方法,但这并非最好的方法,下面是另一个解决方法:

Cat.prototype = new Mammal();
Cat.
prototype.constructor = Cat;
Cat.
prototype.parent = Mammal.prototype;
...
Cat.
prototype.haveABaby =
 
function()
 
{
  
var theKitten = this.parent.haveABaby.call(this);
  alert
("mew!");
  
return theKitten;
 
}


戏仿 抽象类
某些OOP语言有一个叫 抽象类 的概念,意为该对象不能自己创建实例,只能通过继承者创建实例。
比如,你有一个名为 LivingThing 的类,你不希望某人不经继承从 LivingThing 直接创建新实例,你可使用如下代码创建抽象类:

LivingThing =
{
 beBorn 
:
  
function()
  
{
   
this.alive = true;
  
}
}
...
Mammal.
prototype = LivingThing;
Mammal.
prototype.parent = LivingThing;
//Note: not 'LivingThing.prototype' 
Mammal.
prototype.haveABaby =
 
function()
 
{
  
this.parent.beBorn.call(this);
  
var newBaby = new this.constructor("Baby " + this.name);
  
this.offspring.push(newBaby);
  
return newBaby;
 
}


在上面的代码中,使用诸如 var spirit = new LivingThing() 的语法将产生错误,因为 LivingThing() 没有构造器。

简化继承
相对于每次都写几行代码继承一个类,我们可直接从 Function 对象进行扩展,如:

<script type="text/javascript">
/*<![CDATA[*/

Function.prototype.inheritsFrom =
 
function(parentClassOrObject )
 
{
  
if (parentClassOrObject.constructor == Function )
  
{
   
//常规继承
   
this.prototype = new parentClassOrObject;
   
this.prototype.constructor = this;
   
this.prototype.parent = parentClassOrObject.prototype;
  
}
  
else
  
{
   
//从抽象类继承 
   
this.prototype = parentClassOrObject;
   
this.prototype.constructor = this;
   
this.prototype.parent = parentClassOrObject;
  
}
  
return this;
 
}
 
// 抽象类
LivingThing 
=
{
 beBorn 
:
  
function()
  
{
   
this.alive = true;
  
}
}

function Mammal(name)
{
 
this.name = name;
 
this.offspring = [];
}

Mammal.inheritsFrom
(LivingThing );

Mammal.
prototype.haveABaby =
 
function()
 
{
  
this.parent.beBorn.call(this);
  
var newBaby = new this.constructor("Baby " + this.name );
  
this.offspring.push(newBaby);
  
return newBaby;
 
}
 
function Cat(name )
{
 
this.name = name;
}

Cat.inheritsFrom
(Mammal );
Cat.
prototype.haveABaby =
 
function()
 
{
  
var theKitten = this.parent.haveABaby.call(this);
  alert
("mew!");
  
return theKitten;
 
}

Cat.
prototype.toString =
 
function()
 
{
  
return '[Cat "'+this.name+'"]';
 
}
 
var felix = new Cat("Felix" );
var kitten = felix.haveABaby();
// mew! 
alert
(kitten );
// [Cat "Baby Felix"]

/*]]*/
</script>


受保护方法
某些 OOP 语言有一个叫 “protected” 方法的概念,意为声明为 protected的方法只供对象内部及子对象访问,外部不可访问。JS 不支持这个。如果你非常希望有这个功能,那你应当写一个你自己的框架,确保每个类都有一个 “parent” 属性,每次访问对象时,都遍历关系树检测权限。虽然有办法实现,但可能不很明智。

 

 

三、附录
1.
原文链接
1.1 OOP in JS, Part 1 : Public/Private Variables and Methods
http://phrogz.net/JS/Classes/OOPinJS.html

 

1.2 OOP in JS, Part 2 : Inheritance
http://phrogz.net/JS/Classes/OOPinJS2.html

2.
译者群组
http://groups.google.com/group/shawlqiu?hl=en

 

3. 编码参考
3.1  XContextMenu2
http://shawlqiu.googlegroups.com/web/XContextMenu2.7z

 

4. 本文相关附件
http://shawlqiu.googlegroups.com/web/OOPInJS.7z

5. 相关术语中英对照
pure virtual class =
抽象类
public
公有
private
私有
privileged
特有

Protected 受保护

 

6. 相关术语解释
super
访问父对象关键字

你可能感兴趣的:(Javacript OOP)