本文详细解析JavaScript中Object.extend方法的实现原理,涵盖静态成员与实例成员的定义方式。通过剖析prototype对象机制、apply函数的作用以及for-in枚举,阐明如何利用extend方法实现类与对象间的成员复制及继承逻辑,并提供完整代码示例展示具体应用。
JavaScript Object.extend 实现原理与继承机制详解
在面向对象编程概念中,类包含抽象类与具体类,并涉及类的继承关系。同时,类的成员可分为实例成员和静态成员。接下来将探讨如何利用prototype机制实现这些特性。
首先观察prototype中涉及的以下代码片段:
具体代码实现如下:
var Abstract = new Object();
Object.extend = function(destination, source) {
for (property in source) {
destination[property] = source[property];
}
return destination;
}
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
} 静态成员与实例成员的定义方式
上述代码首先声明了一个名为Abstract的对象。由于Object本质是一个函数且初始状态下没有任何成员,因此它被视为一个空类,进而Abstract也是一个没有成员的空对象。这一点暂且不论,后续内容会揭示其作为抽象类基础的作用。此处先解释相关语法:
function.member=function(){}
在此语法结构中,function通常指已定义的函数。该语句的作用是为该函数添加一个名为member的静态成员,其值为等号右侧的内容。例如前文提到的Object.extend=……,即为Object类增加了一个名为extend的静态方法。明确了如何定义类的静态成员后,接下来介绍实例成员的定义方法,即在类名与成员名之间插入prototype关键字:
function.prototype.member=function(){}
prototype的使用方式不仅限于此,还可以采用以下形式:
具体代码如下:
function.prototype={
member1:function(){……},
member2:"abc",
member3:function(){……}
} 这种写法实现了实例成员的定义。那么prototype究竟代表什么?在前文曾提及,直接使用{}包裹表示一个对象,如Prototype、Class等全局对象均以此方式定义。观察上述用法,prototype后跟随一个{}结构,这是否意味着它也是一个对象?答案是肯定的,prototype确实是一个对象。在JavaScript中,我们可以任意为对象添加成员,语法如下:
object.member=function(){……};
只要执行此类定义,对象便立即拥有了member方法。JavaScript的设计机制使得这一过程变得简单而神奇。
既然已知prototype是一个对象,而function是一个函数或类,我们可以将prototype视为每个类(函数)内部保留的一个静态成员。其功能是存储该类所有成员的指针,但这些成员仅作为原型存在,未经初始化,这也契合“prototype”一词的原意。你可以随时通过prototype对象扩充成员。当使用new关键字实例化类时,prototype中的成员经过初始化后赋值给实例对象。
前文代码中的Object.prototype.extend=……即为Object类增加了一个实例方法extend。在实例方法中,可以引用this指针,它指向由该类实例化的具体对象。因此,该对象便具备了extend成员。
核心语法:枚举与apply方法应用
在继续深入之前,先了解两个关键语句:
for(var p in object){}
method.apply(object,arguments);
第一条语句用于列举变量的所有成员:若变量是函数,则列举所有静态成员;若是对象,则列举所有实例成员。其中p为字符串类型,表示成员名称。引用成员时,既可以使用variabel.member形式,也可以使用variabel["member"]形式。反之,赋值操作也遵循相同逻辑。这为枚举变量成员提供了极大便利。
第二条语句将method方法应用于object执行,参数为arguments数组。需注意,method并非object的成员。但可理解为该语句执行的效果等同于:object.method(arguments)。这是一个重要方法,后续将频繁使用,需逐渐熟悉。
extend方法的静态与实例双重角色
继续探讨extend方法,它极为重要,既是Object类的静态成员,也是其实例成员。其作用是什么?让我们来看:它接收destination和source两个参数。若两者均为类,则功能是将source的所有静态成员复制给destination;若两者均为对象,则是将所有实例成员复制过来。此时,若destination中已存在同名成员,该成员将被覆盖。换言之,使destination获得source的所有成员,并返回destination。接下来看extend作为Object实例成员的情况:
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
}
初看可能令人困惑,但仔细分析仍可理解。apply语法前文已述,其调用者是一个方法,而Object.extend是静态方法,被应用到this上,即Object的实例(假设为obj)。方括号内为包含两个成员的数组:this和object。该数组实际上构成了Object静态成员extend的arguments参数。因此,该语句等效于执行:
obj.extend(this,object);
此处this表示对象本身,无需解释。object是参数,即实例方法extend传入的参数,注意不要混淆。obj并未定义extend实例成员,但通过apply,它可以调用Object的静态成员extend。再次查看extend函数体:
Object.extend = function(destination, source) {
for (property in source) {
destination[property] = source[property];
}
return destination;
}
由于obj是对象,object也是对象,即destination和source均为对象,函数作用便是使obj拥有object的所有成员,并返回obj。逻辑虽略显拗口,但核心清晰:让obj“继承”object!至此看到了继承的概念,或许你会疑惑,此前一直讨论类的继承,对象的继承首次出现。确实,目前尚未展示真正的类继承,但已近在咫尺:类拥有prototype,而prototype正是对象!
基于prototype的对象继承实现
想到这一点,类的继承语法看似简单:
b.prototype.extend(a.prototype);
这表示让b继承a。
然而事实并非如此简单:prototype存放的是方法原型指针,extend方法未经初始化,无法直接使用。要使用extend,必须实例化一个对象。不妨参考prototype的实现方式:
b.prototype=(new a()).extend(b.prototype);
这是一种巧妙的解决方案,充分说明了函数本质上也是变量的道理。首先实例化a对象,在此基础上调用extend,将b.prototype的所有成员覆盖到a的对象中,然后将该a对象赋值给b.prototype,从而完成b从a的继承。实际使用中,常见用法为:
b.prototype=(new a()).extend({});
因为让b继承a时,b通常是一个未定义的类,所以{}中可以定义类的成员。当然,你也可以先定义再继承,只是与传统概念略有不同。
今日内容至此结束,想必读者也感到疲惫。目前我们已基本掌握prototype的类开发框架,接下来可探讨一些高级应用,下回再见。
完整代码示例与测试
具体代码示例如下:
<script language="javascript">
//给Object对象增加静态方法extend,该方法的作为复制source有所有属性和方法到destination
Object.extend = function(destination, source) {
for (property in source) {
destination[property] = source[property];
}
return destination;
}
var dog = function(name)
{
this.name = name;
}
//将printName方法复制给dog.prototype
Object.extend(dog.prototype,
{
printName:function()
{
alert(this.name);
}
}
);
var a = new dog("dog");
a.printName();
</script>