javascript 面向对象初探

时间:2020-12-09 20:15:56

javascript拥有强大的prototype机制,它是基于原型和对象的脚本语言。

javascript中,并没有“类”的概念,所以严格来说它并不是面向对象的语言,但是利用prototype机制我们可以通过构造函数生成对象来模拟面向对象机制:

 

function Base()

{

       Base.prototype.c = function()

       {

              alert('c');

       }

       Base.prototype.a = function()

       {

              alert('a in Base');

       }

       Base.prototype.toString = function()

       {

              return ("Base");

       }

       Base.prototype.base = new Object();

       this.d = function()

       {

              alert('d');

       }

}

var a = new Base();

var b = new Base();

上面的代码定义了一个Base“类”并构造了ab两个实例。但是注意到,虽然表面上是通过类构造实例,但实际上javascript是直接构造对象,这其中的区别在于:两次执行new Base()操作的时候,实际上程序对Base.prototype.*进行了两次赋值。这些赋值操作会带来一些额外的开销,使得javascript比真正面向对象的语言在构造对象的时候效率低一些。但是,这样做也会获得一些灵活性。

 

function MyClass()

{

       if (MyClass.instanceCount == null)

       {

              MyClass.instanceCount = 1;

       }

 

       else MyClass.instanceCount++;

      

       MyClass.prototype.instanceCount = MyClass.instanceCount;

}

var a = new MyClass();

alert(a.instanceCount);

var b = new MyClass();

alert(b.instanceCount);

alert(a.instanceCount);

 

上面的代码在构造对象的时候动态改变类和对象(而不仅仅是类!)的instanceCount属性,所以,当构造了两个对象之后,a.instanceCountb.instanceCount属性的值都为2

 

ObjList = new Object();

ObjList.Member = ListMember;

ObjList.MemberCount = 0;

function ListMember(obj)

{

       if (ListMember.prototype.head == null)

       {

              ListMember.prototype.head = this;

       }

       if (ListMember.prototype.end != null)

       {

              this.prev = this.end;

              this.prev.next = this;

       }

       ListMember.prototype.end = this;

       this.object = obj;

       ObjList.MemberCount++;

}

var newMember = new ObjList.Member(new Object(1));

newMember = new ObjList.Member(new Object(2));

newMember = new ObjList.Member(new Object(3));

alert(newMember.object);

alert(newMember.prev.object);

alert(newMember.head.object);

alert(ObjList.MemberCount);

 

这是一段比较有趣的代码,通过一个全局唯一的对象ObjList的构造方法Member()构造出新的成员,并且将这个新成员直接以链表的形式添加到ObjList的表尾。注意到一旦构造出一个新的成员,所有对象的end属性(而不是类的)都将发生改变。从这段代码也可以看出prototypethis的区别。在构造对象时,对prototype属性的赋值不会分配新的内存单元,除非该属性的值为空。而对this的赋值则会分配新的内存单元给当前对象。因此改变prototype引用的属性将使得生存期内的所有该类对象的属性发生改变,而改变this引用的属性将仅仅改变当前对象的属性。

 

function MC2(val)

{

       this.a = val;

       MC2.prototype.b = val;

}

 

var t1 = new MC2("x");

alert(t1.a); //x

alert(t1.b); //x

 

var t2 = new MC2("y");

alert(t1.a); //x

alert(t1.b); //y

 

alert(t2.a); //y

alert(t2.b); //y

 

上面的代码看出,由于t1.bt2.b实际上都引用了MC2.prototype.b属性,所以当t2构造的时候实际上改变了MC2.prototype的值,从而使得t1.b的值也发生了改变。因此,当类的属性在运行期需要随着对象改变时,应当用this定义,反之,当定义常量或者类方法,在运行期不随对象改变时,应当尽量采用prototype以节省空间提高效率。仍然要再次提醒,尽管prototype通常用来定义那些“不易改变”的属性和方法,但是实际上它们仍然可以被改变,并且每一次构造一个新的对象时会被重新赋值。特别需要注意的是,如果在一个类中同时定义了this.xprototype.x属性,前者将覆盖后者。

 

利用prototype可以模拟实现对象的继承。

 

function Base()

{

       Base.prototype.c = function()

       {

              alert('c');

       }

       Base.prototype.a = function()

       {

              alert('a in Base');

       }

       Base.prototype.toString = function()

       {

              return ("Base");

       }

       Base.prototype.base = new Object();

}

 

function Derived()

{

 

       Derived.prototype.a = function()

       {

              alert('a in Derived');

       }

       Derived.prototype.toString = function()

       {

              return ("Derived");

       }

       Derived.prototype.test = function()

       {

              alert(this);

       }

       Derived.prototype.base = new Base();

}Derived.prototype = new Base();

 

上面的代码中,通过对prototype的赋值,实现了Derived类继承Base类。

 

var x = new Base();

var y = new Derived();

y.a(); //a in Dervide

alert(y.toString()); //Derived

y.base.a(); //a in Base

alert(y.base.toString()); //Base

alert(y.base.base.toString()); //[object Object]

 

y.a(), y.toString()覆盖了基类的同名方法,y.base.a()y.c()y.base.toString()分别调用了基类的同名方法 y.base.base.toString()则调用了Object类的toString方法,因为Object类是所有javascript类的超类。

 

从上面的例子可以看出,javascript类继承的本质是通过改写prototype属性来模拟,需要注意的是,这样实现继承也会带来一些“副作用”,例如改变了对象中constructor字段的值,当Derived类继承自Base类时,获取Derived对象的constructor属性将返回Base对象的构造函数。当然,这个特性也可以解释为“Derived对象的构造过程中确实调用了Base对象的构造函数”,而事实上这也不是什么大问题,上面代码中尽管c.constructor返回的是Base,但是c instanceof Derived的值仍然是true。然而,除此以外,由于是改写prototype,所以次序上需要注意,在执行顺序上对prototype本身的改写必须出现在最前面。但是,我发现即使将prototype的赋值放在构造函数内的第一行,也不能正常工作。这显得有点怪异。而我的理解是,在构造函数体内部,prototype属性是只读的。所以我的建议是,将对类prototype的赋值放到函数体外(推荐写在结束花括号之后),而对于其他成员的赋值,还是应当写在函数体内部。

最后,再提醒一个值得注意的事情,那就是似乎javascript并不能完全模拟继承机制,因为要实现类属性的继承比较困难,至少目前我还没有比较好的方案。如果哪位达人研究过此类问题,请多多指教,感激不尽。