目录:
一、公有/私有的变量及方法
二、继承
三、附录
内容:
一、 公有/私有的变量及方法
本部分使用例子 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 = 访问父对象关键字