| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 561 人关注过本帖
标题:JavaScript 面向对象编程, 第一部分: 继承(续)
只看楼主 加入收藏
linuxpluto
Rank: 4
等 级:贵宾
威 望:13
帖 子:889
专家分:23
注 册:2005-8-14
收藏
 问题点数:0 回复次数:0 
JavaScript 面向对象编程, 第一部分: 继承(续)

无论你是否修改过此属性值, 通过上面语句所取出的属性值都是原始定义值. 让我们沿着这个思路再往下看, 下面的代码涉及到另外一个问题, 这个问题和原型链 (prototype chain) 有关. 代码如下:

function State() {
}

function City() {
}
City.prototype = new State;

function Street() {
}

Street.prototype = new City;

var UniversityAvenue = new Street();

function tryIt() {
alert(UniversityAvenue.__proto__== Street.prototype);
alert(UniversityAvenue.__proto__.__proto__==
City.prototype);
alert(UniversityAvenue.__proto__.__proto__.__proto__
== State.prototype);
alert(UniversityAvenue.__proto__.__proto__.__proto__.
__proto__== Object.prototype);
alert(UniversityAvenue.__proto__.__proto__.__proto__.
__proto__.__proto__== null);
}

当执行 tryIt 函数时, 所有的显示均为 true. 也就是说, 子类对象的 prototype.__proto__ 总是等于超类对象的 prototype 属性; 超类对象的 prototype.__proto__ 总是等于 Object.prototype; Object.prototype.__proto__ 总是为 null; 而实例对象的 __proto__ 总是等于其类对象的 prototype, 这就是为什么任何自定义对象都具有 __proto__ 属性的原因. 对于刚才的叙述, 其对应的代码如下:

Street.prototype.__proto__ == City.prototype // true
State.prototype.__proto__ == Object.prototype // true
Object.prototype.__proto__ == null // true
UniversityAvenue.__proto__ == Street.prototype // true

模拟实现 instanceOf 函数

根据上一节的内容, 我们了解了有关 Netscape 所支持的 __proto__ 特性的内容. 这一节, 我们将利用此特性来创建自己的实例对象检测函数.

许多时候, 我们都需要判断某个对象是否是由某个类来定义的, 在其它的语言里, 你可以通过 instanceOf 函数来实现此判断. 在 JavaScript 中同样提供了一个 instanceof 运行符, 而在 __proto__ 的基础上, 我们完全可以自己定义一个同样的函数, 虽然这看上去是在重复劳动, 但有助于我们更深刻地了解有关 __proto__ 的知识. 下面的代码只是用来说明功能, 在实际的应用中, 你不需要重新定义 instanceOf 函数, 使用 instanceof 运算符即可.

function instanceOf(object, constructorFunction) {
while (object != null) {
if (object == constructorFunction.prototype)
{return true}
object = object.__proto__;
}
return false;
}
function State() {
}

function City() {
}
City.prototype = new State;

function Street() {
}

Street.prototype = new City;

var UniversityAvenue = new Street();
function demo() {
alert("instanceOf(UniversityAvenue, Street) is " +
instanceOf(UniversityAvenue, Street));
alert("instanceOf(UniversityAvenue, City) is " +
instanceOf(UniversityAvenue, City));
alert("instanceOf(UniversityAvenue, State) is " +
instanceOf(UniversityAvenue, State));
}

你会看到所有的运行结果全部为 true, 其原理和上一节的级连相等判断如出一辙. 实际证明, 它的运行结果和 instanceof 运行符的运行结果是一致的.

你可以通过 constructor 属性来检测任意对象的超类, 此属性返回通过 new 运算符创建新对象时所调用的构造函数, 返回值是 Function 对象类型. 因为 Object 内部对象是支持 constructor 属性的, 并且有的对象 (包括内部对象和自定义对象) 都是由 Object 继承而来的, 所以所有的对象都支持此属性. 让我们再看一下下面的例子.

function Employee() {
this.dept = "HR";
this.manager = "John Johnson";
}

function printProp() {
var Ken = new Employee();
alert(Ken.constructor);
}

调用完 printProp 函数之后, 你会看到弹出框中显示的是 Employee 函数的定义文本, 其实 Ken.constructor 的返回值本身是 Function 对象类型, 而在 alert 时被隐含地调用了 toString 方法. 对于类对象本身, 你同样可以调用 prototype.constructor 来取出其构造函数.

对象的分类和打印

JavaScript 支持 3 种主要类型的对象: 内部对象, 宿主对象, 自定义对象, 可能还有特殊的外部对象, 如: ActiveX 对象或 XPCOM 对象. 内部对象被 JavaScript 语言本身所支持, 如: Object, Math, Number 对象等. 所有的内部对象的共同特点是以大写字母开头, 并且它们是大小写敏感的. 如果你想使用数学常量 PI, 必须写成 Math.PI, 你如果写成 math.PI, JavaScript 会显示错误. 宿主对象是被浏览器支持的, 目的是为了能和被浏览的文档交互, 如: document, window 和 frames. 宿主对象的特点是所有对象全部以小写字母开头. 因为 JavaScript 本身就是大小写敏感的, 所以你同样不能将大小写搞混. 剩下要说的就只是自定义对象了, 你可以随便将你的对象定义成小写或大小写, 但是一定要符合基本的命名规范. 如下所示, 这就是一个自定义对象:

function employee() {
this.dept = "HR";
this.manager = "John Johnson";
}

function printProp() {
var ken = new Employee();
for (property in ken) {
alert(property);
}
}

前面我们已经提到过, 所有的内部对象和自定义对象都是从 Object 对象继承而来的, 它是所有对象的超类对象. 你可建立一个 Object 对象的实例. 如下:

var myObject = new Object();

Object 类型的对象有许多的属性和方法, 你可以查看相关的手册. 上面只是定义了一个最简单的空对象, 你还可以为 Object 构造函数传入参数, 它会返回相应类型值的实例化对象. 记住, 返回值的类型是某种对象类型 (如: String, Number 或 Object). 这种方式和通过直接赋值字符串或数值常量不同, 主要表现在类型方面. 如下所示:

var myObject = new Object("foo"); // 返回值类型为 object
var myObject = new String("foo"); // 返回值类型为 object, 效果同上

var myObject = "foo"; // 返回值类型为 string

你可以从调试器的 type 列中看出这个细微的差别, 它是简单类型与对象类型之间的区别. 但是, 你通过 alert 调用是看出不这些内部差别的, 因为在调用 alert 的过程中, 所有的对象类型值都会被自动调用 toString 方法进行字符串类型转换, 转换规则在 JavaScript 手册中有说明. 如果你 alert 的是某个自定义对象, 并且它没有定义 toString 方法, 那么它的返回值将为 "[object Object]". 对于 Math 对象, 当你查看其 Math.constructor 属性时, 你会得到一个不同于其它内部对象的内容为 "function Object()..." 的对象构造函数, 这与其它对象返回 "function Function()..." 的构造函数很不相同. 原因很简单, 因为 Math 对象是不能通过 new 运算符进行创建的.

另外, 如果传入 Object 构造函数中的值是一个对象, 它将原封不动地将该对象返回. 记住, 此操作只是一个引用, 而不是复制.

请求对象的属性

在前面的示例代码中, 已经出现过以循环方式枚举对象属性的示例. 其实, 通过 for...in 语句, 无论是任何对象和数组, 其下的元素, 属性和方法都可以遍历出来. 示例如下:

function employee() {
this.dept = "HR";
this.manager = "John Johnson";
}

function printProp() {
var ken = new employee();
for (property in ken) {
alert(property + " : " + ken[property]);
}
}

在遍历测试过程中, 你会发现, 对于自定义对象和宿主对象一般都可以枚举出其下的属性, 而对于内部对象, 几乎没有什么属性可以遍历出来, 为什么要说几乎呢? 因为对于 Mozilla 内核的浏览和 IE 内核的浏览器, 其 JavaScript 引擎有所不同, Mozilla 下可以枚举出部分内容, 而枚举的原则不得而知.

对于每一个对象, 你还可以使用 hasOwnProperty 方法来检测其是否具有某个属性或方法. 由于 hasOwnProperty 是 Object 对象下的方法, 因此所有的对象都具有此方法. 但是, 需要注意的是, 此方法只能检测通过 this 关键字定义的成员, 如果某个成员是通过原型定义的, 那么此方法将返回 false. 也就是说, 通过 prototype 继承来的属性和方法, 及其通过 prototype 定义的属性和方法, 都是不能通过 hasOwnProperty 进行检测的. 由此, 我们可以看出, 通过 this关键字定义的属性和方法是同对象本身处于同一个地址空间内的; 而通过 prototype 定义的属性和方法, 是通过所谓的 "原型链" 进行管理的, 其下的属性和方法不位于同一个地址空间之间. 当其调用这种属性或方法时, 必须通过 "链表" 才能索引到其下的某个属性或方法. 也就说, 调用以原型方式定义的属性和方法会有一个类似于链表的 "回溯" 操作.

和 hasOwnProperty 差不多, 对于对象中的每个属性, 我们还可以通过 propertyIsEnumerable 来测试它是否可以被枚举出来. 如下所示:

function Employee1() {
this.dept = "HR";
this.manager = "John Johnson";
this.month = new Array("jan", "feb", "mar");
}

var Ken = new Employee1();

Ken.month.propertyIsEnumerable(0);

我们可以看到, 其语法是 propertyIsEnumerable 后跟数组元素的索引或对象的成员名称. 同样, 对于原型链中的属性和方法它是不予考虑的, 结果当然是返回 false.

对于 JavaScript 和 Java 的比较

与 Java 这种基于类的语言不同, JavaScript 是一种基于原型的语言, 这个特点影响着它的每个方面. 如术语 instance 在基于类的语言中有着特殊的意义, 它表示某个实例是隶属于某个特殊类的独立个体, 是对类定义的真实实现; 而在 JavaScript 中, 术语 instance 没有这个意思, 因为在它的语法里面, 类和实例是没有区别的, 都是被当成对象来看待的. 虽然, 实例可以用来说明某个对象是使用某个特殊的构造函数生成的. 如下所示:

function superClass() {
this.bye = superBye;
this.hello = superHello;
}

function subClass() {
this.bye = subBye;
}
subClass.prototype = new superClass;

function superHello() {
return "Hello from superClass";
}

function superBye() {
return "Bye from superClass";
}

function subBye() {
return "Bye from subClass";
}

var newClass = new subClass();

newClass 是 subClass 的实例, 它是通过 subClass 的构造函数生成的. 而如果使用基于类的语言呢, 如下所示是 Java 的等价实现.

public class superClass {
public superClass () {
this.bye = superBye;
this.hello = superHello;
}
}
public class subClass extends superClass {
public subClass () {
this.bye = subBye;
}
}

[此贴子已经被作者于2006-1-2 5:41:43编辑过]

搜索更多相关主题的帖子: 继承 JavaScript 面向对象 
2006-01-02 05:31
快速回复:JavaScript 面向对象编程, 第一部分: 继承(续)
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.014369 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved