javascript类的实现及原理的理解

javascript类的实现及原理的理解

类,是面向对象编程中一个很重要的概念.一直很遗憾JavaScript中没有实质的类的定义,但这并不会影响我们在JavaScript实现类的操作.在学习JavaScript的过程中,我也了解了JavaScript中对一些高级语言中的类的模拟实现方式,实现的形式不一,但是不管是怎样的形式,它们实质上都是用构造函数模拟类,如

var aClass=function(){};

var anInst=new aClass();

虽然知道如此,但是对于这些实现的方式,还是很困惑,原因在于,它们的区别是什么?都有什么各自的优缺点?能否真正实现类的模拟?是否有更好的方式来实现?这些问题一直困扰着我,也是本文中要思考的问题.

为了更直观的进行对比,我们先来看一段C++中类的定义和实例化的代码

C

12345678910111213141516171819202122232425262728#include « iostream »using namespace std;/**Person类的定义*其中,name是该类的私有成员变量*Person(char *name)是类的构造函数*say()方法是类的成员方法*/class Person{private: char *name;public: void say();//成员函数的声明 Person(char *name){ this->name=name; };};void Person::say(){//成员函数的实现 ::cout<<« hi,我是 »<<this->name<<endl;};int main(){ Person *wanz=new Person(« 丸子 »);//类的实例化 wanz->say();//类的实例访问成员函数,输出:hi,我是丸子 Person *rekey=new Person(« rekey,丸子的师傅 »);//类的另一个实例 rekey->say();//hi,我是rekey,丸子的师傅 return 0;}

从这个例子中,我们可以大概看出高级语言中(C++)是如何对类进行定义(包括成员变量的保护,成员函数的声明等)以及如何对类进行实例化,并调用其成员函数的.下面我们来看一下,在JavaScript中如何来实现这个过程以及以及在实现的过程中会出现的问题和解决办法.

二,JavaScript类的实现

前面说过js是通过构造函数来模拟类的,我们就用一个例子来说明这一切.同样是猥琐的Person类:

   提示:你可以先修改部分代码再运行。

仔细观察这段代码,与上面的代码对比一下,你会发现:啊,这就是类!没错,我们简单的模拟了一个类.

三,静态属性与方法

当类的某些属性或方法不想被实例化对象调用时,这里就要实现类的静态属性或方法,而在JavaScript里要实现类的静态属性或方法是非常简单的,看代码.

   提示:你可以先修改部分代码再运行。

到这里,我们已经实现了类的的定义与实例化,好戏还在后头,类提供了一个很重要的机制:继承.这一特性使得编程更加的灵活简洁,这也是面向对象的一个重要特征,我们已经模拟了类,那么要如何来实现类的继承呢?

四,类的继承

类的继承实际上就是属性或方法的复制(个人观点),有了这个思路,我们就可以来尝试实现类之间的继承.因此,我们要做的就是将父类的属性赋值给子类.

   提示:你可以先修改部分代码再运行。

这是一个简单的类的继承形式,在例子中,你可以看到我们通过new实例化Person类来实现属性和方法的复制,而实际上类的实现与继承有多种方式,当有很多的类相互继承时,这样的书写方式会使得代码变得十分臃肿混乱,因此,我们需要将继承的方法封装,实现统一的继承管理机制.

   提示:你可以先修改部分代码再运行。

我们把继承的过程封装成函数,这样的写法是不是更直观,可读性更好?是的,这是一个简单的类之间的继承实现,但问题还没这么简单,就像前面说的,继承要实现的东西不只是复制原型属性或方法.这个我们在后面继续讨论,原因很简单,到目前为止,我们实现了类的定义,继承和实例化的过程,但是这些过程存在很多问题,他们看似简单,但在实际的应用中,代码量往往大得多,可能是千代码行,这时,我们定义类的方式和管理及继承的管理将是乱七八糟的:类的定义混乱,代码可读性差,类的维护与被继承变得更加复杂等等,使得我们之前讨论的这些完全不能满足于实际的需求.因此,接下来要做的工作就是将我们知道的这些进行优化,使其更直观,更易于扩展与维护.

五,进阶式类的实现

让我们回到原点,js用构造函数模拟类,它实际是一个函数,当代码增多时,你可能分不清什么是类什么是函数,刚入门的时候,更是容易混淆,觉得难以理解,那是因为js的实现方式跟我们传统的理解不太一样,实际上js并没有将它们区分,为了能使类的定义更加清晰化,可读性更好,更易于维护,我们来封装一个类构造器,所有的类都由这个构造器来创建,并且让构造函数成为”真正的”构造函数.我们再观察下那段C++代码,创建一个类实际只要这样的代码.

Class Person{}

这就相当于一个类构造器,在C++中(其他高级编程语言也是如此),这样声明了一个类,这个类实际还隐藏了两个隐形的函数:构造函数和析构函数;它们的作用分别是初始化对象和对象销毁的处理.注意,此时我们所说的构造函数不完全等同于js中提及的,为什么需要构造函数?我认为使用构造函数有三个好处:一,极大的方便了对象的初始化,还使得对象的封装成为可能,没有构造函数你如何完成私有数据的初始化呢?二,在创建一个类时,没有特殊要求,我们可以不必手动写初始化代码,这实质是一个托管机制.三,在我们需要手动修改初始化的细节时,我们只需要重载构造函数即可以实现所有数据的初始化,问题的关键在于,这个类可能继承了多个父类,因此我们需要一个能处理所有参数的函数,而不是将各个父类的初始化方法添加进来,它就是这里提到的构造函数,这也是为什么我们需要托管初始化的一个重要原因.考虑到这些,我们便有了一个类构造器的模型了.

var Class={
create:function(){
return function(){
this.initialize.apply(this,arguments);//将参数列表初始化转交给initialize处理,并且闭包实现了成员变量的封装,外界无法直接访问.
}
}
}
var Person=Class.create();//创建了一个类

我们成功的通过类构造器创建了一个类,对比下Class Person{},我们的实现方式是var Person=Class.create();二者异曲同工,同样声明了一个Person类,同样拥有一个隐形的负责初始化工作的构造函数,不同的是,C++里的构造函数是与类同名的一个函数,而这里这个构造函数是initialize.接下来,我们来完成这个类要实现的其他工作.步骤跟之前一样,从实现C++的例子开始.

   提示:你可以先修改部分代码再运行。

我们做到了,那么继承呢?我们先将前面写的Extend加进来.

   提示:你可以先修改部分代码再运行。

细心的话,你会发现,这里存在一个问题,如果FE本身的prototype已经定义了一些成员,这样的继承不就被覆盖了吗?

   提示:你可以先修改部分代码再运行。

如同注释中那样,由于prototype被所谓的继承覆盖,使得FE类中的hi成员函数无法被继续使用,因此这个继承方式有缺陷,需要改进.有一种办法(参考prototype.js源码)就是用for遍历对象,将父类的prototype复制到子类中.

   提示:你可以先修改部分代码再运行。

到这里,我们已经大概将类的封装,继承及实例化实现了.如果把最后的这段代码再跟前面的C++中的代码进行对比,你会发现,它们形似神也似.但是,我们并不是一味的模仿高级语言中类的形式,而是学习高级语言中普遍采用的方式及其优点,毕竟它们是经历了无数的考验的,因地制宜加以应用,当然这里所演示的代码都是demo,不一定符合实际的要求,不过也能够说明一些问题.

六,总结

以上只是个人对js面向对象编程中类的模拟的一些理解及实现,由于还是个新手,肯定会有很多不足之处,希望看官拍砖,对于开始说的那些疑惑,其实已经在对这些问题的思考中解决了.对于类的实现,不管是以什么形式来做,我们要把握的原则是:代码的可读性,可维护(或扩展)性以及性能的代价.最后要注意的是闭包可能带来的内存泄露问题.考虑到本文还有很多缺陷,我会找时间写续篇,内容涉及继承函数的增强(增强可扩展性,解决属性冲突问题,防止原型链污染以及继承性能的研究与优化等),子类访问父类保护的成员变量的实现.(感谢CE提出的补充修改建议)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.