js是一门基于原型的面向对象语言,与传统的面向对象如Java,C#相比,它在对象创建及继承上有自己独特的实现方式,本文主要描述js中对象创建及继承的一些实践。
1.对象创建
方式一:工厂模式创建对象
<script> function createPerson(name,age,job) { var o=new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name); }; return o; } //这就是工厂方式创建对象 var a=createPerson(‘my‘,27,‘student‘); a.sayName(); </script>
这种方式就是将对象属性作为参数传入函数,然后在函数中创建一个新对象再返回给外部使用,属于典型的输入处理输出模式,优点在于简单易于理解也很好的实现了对象的创建和隔离,缺点在于不符合典型OOP语言创建对象实例的做法,而且这样创建出来的对象都属于object类型而不能进一步确定其属于哪个具体的对象类型。
方式二:构造函数创建对象
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name); } } var p1=new Person(‘my‘,27,‘stu‘); // 这是使用构造函数来创建对象 p1.sayName();
这种方式使用function来模拟一个类,通过调用这个类的构造函数通过new的方式实例化一个对象,这符合经典OOP创造实例对象的做法,同时也可以确定这个实例属于哪个具体的类型,这里是Person类的实例
,但是这种方式造成的缺点在于每个实例的方法都要在创建该对象时被创建一次,function本质上是一个object,这样做对内存浪费较大,实际上,对于多个不同实例他们的方法是共同引用一个function的关系。
方式三:基于原型创建对象
<script> function Person(){} Person.prototype.name=‘my‘; Person.prototype.age=27; Person.prototype.job=‘stu‘; Person.prototype.sayName=function() { alert(this.name); } var p1=new Person(); p1.sayName(); </script>
这种方式利用每个函数都具有一个原型并且它的多个实例都共享该原型的特性,将该类的属性和方法都挂载到了其原型上,这样做的好处是让多个实例共享了一个方法定义,但是其确定也很明显,即不能通过传参去调用构造函数,构造函数内部的属性都是硬编码的,同时任一实例对其自身的引用类型的属性修改都会影响到其他实例
方式四:构造函数结合原型方式
//属性放在构造函数 //实例间不会相互影响 function Person(name,age,job){ this.name=name; this.age=age; this.job=job; } //方法放在原型中 //节省内存 Person.prototype.sayName=function() { alert(this.name); } //使用组合方式创建对象 var p1=new Person(‘my‘,27,‘stu‘); p1.sayName();
这种方式综合了构造函数和原型的优点,将属性等定义5放在构造函数中,实现了不同实例间的隔离,使之不会互相影响,同时又将方法定义挂载到了原型上,使多个实例共享一个方法定义(方法体),节省了内存空间,但是这种方式实际上将两种方式人为分开了,调用构造函数之后还要再去操作其原型,不符合经典OOP语言创建对象的方式。
方法五:动态原型方式
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; if(typeof this.sayName!=‘function‘){ this.sayName=function(){ alert(this.name); } } } var p1=new Person(‘my‘,27,‘stu‘); p1.sayName();
该方式将方法四操作原型的代码直接放入到了构造函数中,同时在new该类型的实例的时候进行判断,如果存在function实例,就不创建,否则创建function,实现了多个实例共享一个方法体的单例模式,该模式也是目前广为使用的,比较好的一种对象实例创建方式。
在创建对象较好实现的基础上,我们来讨论js中的继承。
方式一:基于原型链的继承
function SuperType(){ this.color=[‘a‘,‘b‘,‘c‘]; } function SubType(){ } SubType.prototype=new SuperType(); var a1= new SubType(); alert(a1.color);//a,b,c a1.color.push(‘d‘); var a2=new SubType(); alert(a2.color);//a,b,c,d
该方法是将子类的原型指向父类的一个实例,子类就具有了父类的方法和属性,但是存在两个缺点,其一是对于父类引用类型的属性来讲,对于其子类任何实例对该属性的操作,其他实例都会受影响,其二是子类继承无法传参到父类,继承的属性无法控制,只能通过修改完成;
方法二:对象冒充继承
function Person(name){ this.name=name; this.sayName=function(){ alert(this.name); } } function Worker(name,age){ Person.call(this,name); this.age=age; } var w1=new Worker(‘my‘,27); //alert(w1.name); w1.sayName();
该种继承方式实现了通过传参去初始化子类的实例,达到了多个实例之间属性方法的隔离,即不会相互影响,但是同样对于方法的继承由于是调用call方法执行父类构造函数中的代码,相当于每次都要创造一个function,这样是对内存的浪费。
方法三:组合继承
function SuperType(name){ this.name=name; } SuperType.prototype.sayName=function(){ alert(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age=age; } SubType.prototype=new SuperType(); SubType.prototype.sayAge=function(){ alert(this.age); } var s1=new SubType(‘my‘,27); s1.sayAge(); s1.sayName();
该种类型的方法是将对属性的继承采用对象冒充的方式,将对方法的继承基于原型链方式,同时对于父类自身其定义也采取上面所讲的属性定义在构造函数中,方法定义在原型中,这样才能够配合这两种不同方式完成组合继承。