重操JS旧业第八弹:面向对象与继承

时间:2024-05-18 17:05:44

js里面没有语言语法层面的继承机制,但这并不意味着js就不能实现继承,利用js属性和方法动态性来模拟实现继承,通过总结大概有如下方法实现:

1 原型链继承

我们知道原型在对象中扮演着重要的角色,函数本身自带原型对象,通过对象创建便让对象拥有指向原型对象的指针,再次原型属性和方法的搜索覆盖机制,以及原型属性和方法的动态性

实现原理:手动将对象的原型对象修改为要继承的对象,如:

function Person(name,sex){

  this.name=name;

  Person.prototype.say=function(){

    console.log('说中国话');

  }

}

原型继承

function Child(){

  Child.prototype=new Person();

}

var child=new Child();child.name;child.say();

1)手动修改对象的原型对象来达到继承目的

2)由于原型的静态性,这里也就具有原型静态性的优势和缺点

3)原型链,所有对象都来自Object,自然原型链也就会搜索到Object级别,这种自底向上的搜索机制构成了原型链;

4)缺点嘛:原型共享和没有构造函数向基类传递参数;

2 借用构造函数

既然原型链没办法像基类传递参数,这里使用函数的call,apply,bind方法来这样实现:

借用构造函数继承

function Child(name,sex){

  Person.call(this,name,sex);

}

1)这样讲this传入了基类之中,本质上仅仅是基类的扩展

2)现在子类child的原型并没有被改变,所以不会搜索到基类的原型方法;

3)借用构造函数的实现过程就知道,丢掉了基类的接口方法,在避免原型链继承的同时又丢掉了原型链继承的优势;

3 组合继承

既然原型链主要目的是继承了原型方法,而借用构造实现了属性的顺利继承,那么将两则结合起来各取所长;

function Child(name,sex){

  Person.call(this,name,sex);

  Child.prototype=new Person();

}

1)很好的实现了借用构造和原型链的优势,因此成为非常常用的继承模式

2)但是通过2次调用基类构造函数,因此基类的构造函数会被执行2次;

4 原型式继承

这个东西的意思是说,将对象作为其他对象的原型对象,这里需要与原型链继承加以区别;

function Object(o){

  function F();

  F.prototype=o;

  return new F();//通过构造函数复制了一份o的指针;

}

1)这个过程中并没有创建新的类型

2)这个过程是将现有对象进行封装扩展

3)适合那些不需要有类型的对象,适合相对临时创建的对象;

var obj={

  name:'zhangsan',

  friends:['lishi','wangwu']

};

var person=Object(obj);

person.friends.push('maliu');

相当于将obj这个对象进行了一次复制这样:var person=复制obj;得到与obj一样的副本,再创建扩展方法,也可以直接对之前拥有的成员操作;但是这里的复制仅仅是指针的复制并没有改变对象本身;

因此呢,obj对于子对象来说都是共享的;

4)这种继承方式使用面很少,因为没有明显优势

5 寄生式继承

寄生继承其本质上跟上面的原型式继承一样的,只不过扩展了点方法再返回

function Object(o){

  function F();

  F.prototype=o;

  var f= new F();//通过构造函数复制了一份o的指针;

  //返回之前扩展下方法,这样我们就叫寄生式,概念很高级吗,本质却很简单,不是么;

  f.say=function(){console.log('xxxx')};

  return f;

}

不用多说跟原型式继承应用返回也差不多;

6 寄生组合式继承

这种是解决组合继承模式下缺点而提出来的,由于组合继承模式会2次调用构造函数,且对于父类的属性会存在2份,自然的在重复调用构造函数的同时,又造成了内存的浪费,因为放在原型上的总会被实例属性所覆盖

解决这种问题,怎么办?

这是组合继承

function Child(name,sex){

  Person.call(this,name,sex);//第二次调用构造函数

  Child.prototype=new Person();//第一次调用构造函数

}

这个过程我们知道原型上的属性终会被第二次调用借用构造函数得到的属性所覆盖,那么也就意味着原型中我们只需要父类的方法即可,而原型的方法可以通过函数的原型属性访问到即

Person.prototype;那么我们只需要将原型属性丢给子类即可

其原理过程如下

function Child(name,sex){

  Person.call(this,name,sex);//第二次调用构造函数

  Child.prototype=Person.prototype;//直接将原型丢给子类嘛,但是有个问题由于原型拥有一个constructor属性会指向Person

  //这里还需要手动修改下

  Child.prototype.constructor=Child;

}

我们也可以这样理解,伪代码

function(superType,subType){

  subType.prototype=superType.prototype;//顺利的得到了父类的原型对象

  subType.constructor=subType;//由于可能丢失类型,这里再次指向回来

}

再进一步封装

function(superType,subType){

  //使用原型式得到一个临时对象

  var obj=Object(superType.prototype);//看吧这里只是把原型取出来了

obj.constructor=subType;

  subType.prototype=obj;//顺利的得到了父类的原型对象

}

js的继承与面向对象创建对象的思路一脉相承,都有临时和经典模式,只是看使用场合而选择哪种模式。