js继承的方式及其优缺点

js继承方法

 

前因:ECMAScript不支持接口继承,只支持实现继承

一、原型链

概念:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,让这个原型对象(子的原型)等于要继承的引用类型(父)的实例,由于引用类型(父)的实例包含一个指向(父)原型对象的内部指针,以此类推,层层递进,便构成实例与原型的链条,即原型链。

基本思想:利用原型让(子)引用类型继承(父)引用类型的属性和方法

基本模式的代码(参考红宝书):

 

/*原型链继承*/
//父的引用类型,拥有一个property属性
function SuperType() {   
    this.property = true; 
}

//为(父)引用类型的原型对象添加一个getSuperValue方法
//由于原型对象都包含一个指向构造函数的指针(constructor指向其引用类型的指针),
//故其方法可以取到property属性的值
SuperType.prototype.getSuperValue = function () {
    return this.property;    //返回property的值
}

//子的引用类型,拥有一个subproperty属性
function SubType() {   
    this.subproperty = false; 
}

//继承父类型,即SuperType,让子引用类型的原型对象指向父类型的实例
SubType.prototype =  new SuperType();

//为(子)引用类型的原型对象添加一个getSubValue方法
SubType.prototype.getSubValue = function () {
    return this.subproperty;    //返回subproperty的值
}

//实例示范
var instance = new SubType();   //该对象同时拥有SubType和SuperType里面的所有方法和属性
alert(instance.getSuperValue);   //true 
/*原型链继承*/

 默认原型:所有函数的默认原型都是Object实例,所以都会继承toString()和valueOf()等默认方法

确定原型和实例的关系方法:

(1)通过使用instanceof()
如:alert(instance instanceOf Object); //true
(2)通过使用isPrototypeOf()

如:alert(Object.prototype.isPrototyOf(instance)); //true

注意点:

(1)给原型添加方法一定要放在替换原型的语句之后
(2)在通过原型链实现继承时,不能使用对象字面量创建原型方法

原型链存在的问题:

(1)子类型的所有实例都可以共享父类型的属性
(2)子类型的实例无法在不影响所有对象的情况下,给父类型的构造函数传递参数

二、借用构造函数继承(伪造对象或经典继承)

基本思想:在子类型构造函数的内部调用超类型构造函数(通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数);

代码(参考红宝书):

 

/*借助构造函数继承*/
// 父类型
function SuperType(){
    this.colors = ["red","blue","green"];
}

//子类型
function SubType(){
    // 继承SuperType
    SuperType.call(this);     
}

/*每执行new SubType()操作,都会在其实例环境下调用一次SuperType构造函数,
都会执行SuperType()函数中所定义的所有对象初始化代码,
因此,SubType的每个实例都会有自己的colors属性的副本*/
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);          //"red,blue,green,black"

var instance2 = new SubType();
alert(instance1.colors);          //"red,blue,green"

/*借助构造函数继承*/

存在问题:方法都在构造函数中定义,函数复用变得没有意义

补充:通过call()或者appla()调用父类型时,可以进行传参

三、组合继承(伪经典继承,原型链和借用构造函数技术组合)

基本思想:使用原型链实现对原型属性和方法的继承(主要想继承方法),而通过借用构造函数来实现对实例属性的继承(子类型的实例内部存在同名属性,从而对父类型的同名属性进行屏蔽);最后同时避免了原型链会被继承时会共享同一个父类型属性和借用构造函数的函数复用的缺陷

代码(参考红宝书):

 1 /*组合继承*/
 2 // 父类型
 3 function SuperType(name){
 4 this.name = name;
 5 this.colors = ["red","blue","green"];
 6 }
 7  
 8 //设置父类型的sayName方法
 9 SuperType.prototype.sayName = function () {
10 alert(this.name);
11 }
12  
13 // 子类型
14 function SubType(name,age) {
15 // 借用构造函数的方法继承"属性",该代码会在新建的实例里面添加和父类型相同的属性
16 SuperType.call(this,name);
17 this.age = age;
18 }
19  
20 // 通过原型链继承方法
21 SubType.prototype = new SuperType();
22 SubType.prototype.costructor = SubType; //原型对象指向构造函数的指针(constructor)
23 SubType.prototype.sayAge = function () {
24 alert(this.age);
25 }
26  
27 // 实例
28 var instance1 = new SubType("Jack",29);
29 instance1.colors.push("black");
30 alert(instance1.colors); //"red,blue,green,black"
31 instance1.sayName(); //"Jack"
32 instance1.sayAge(); //29
33  
34 var instance2 = new SubType("Greg",27);
35 alert(instance2.colors); //"red,blue,green"
36 instance2.sayName(); //"Greg"
37 instance2.sayAge(); //27
38 /*组合继承*/

缺点:两次调用父类构造函数:(第一次是在创建子类原型的时候,第二次是在子类构造函数内部) ;从而造成子类继承父类的属性,一组在子类实例上,一组在子类原型上(即在子类原型上创建不必要的多余的属性) 

四、原型式继承

基本思想:借助原型可以基于已有的的对象创建新对象,即如在一个函数Object内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型(实际上执行了浅复制),最后返回这个临时类型的新实例。

代码(参考红宝书):

/*原型式继承*/
function object(o){
    function F(){};   //创建临时性的构造函数(对象)
    F.prototype = o;  //浅复制(只将地址地址进行赋值)
    return new F(); 
}

// 例子如下:
var person = {
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
}

//新建一个anothorPerson对象(空的),原型指向person
var anothorPerson = object(person);  
anothorPerson.name = "Greg";  //直接在anothorPerson里面创建name(基本类型),再进行赋值
 /*在anothorPerson查找friends(引用类型),
 没有找到则顺着原型向上(即继承的父类型)找,直到找到,否则报undefind*/
anothorPerson.friends.push("Rob");

var yetAnothorPerson = object(person);  
yetAnothorPerson.name = "Linda";  
yetAnothorPerson.friends.push("Barbie");
// anothorPerson和yetAnothorPerson共享person的friends
alert(person.friends);  //"Shelby,Court,Van,Rob,Barbie"
/*原型式继承*/

补充:ES5新增的Object.create()方法拥有和object()方法同样的效果,但是Object.create()方法接受两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性(会覆盖作新对象原型的对象上的同名属性)的对象

举例子如下:

// Object.create()
var person = {
    name: "Nicholas",
    friends:["Shelby","Court","Van"]
};

var anothorPerson = Object.create(person,{
    name:{
        value:"Greg"
    }
});

alert(anothorPerson.name);   //"Greg"
五、寄生式继承

基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象

代码(参考红宝书):

 

/*寄生式继承*/
function object(o){
    function F(){};   //创建临时性的构造函数(对象)
    F.prototype = o;  //浅复制(只将地址地址进行赋值)
    return new F(); 
}

function createAnother(original){
    var clone = object(original);  //通过调用函数创建一个新对象
    clone.sayHi = function () {    //以某种方式来增强这个对象
        alert("Hi");
    };
    return clone;     //返回这个对象
}

var person = {
    name: "Nicholas",
    friends:["Shelby","Court","Van"]
};

var anothorPerson = createAnother(person);
anothorPerson.sayHi();    //"Hi"
/*寄生式继承*/

缺点:不能做到函数复用导致降低效率

六、寄生组合式继承

概念:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

基本思想:没有必要为了指定子类型的原型而调用超类型的构造函数(函数复用),我们需要的是超类型原型的副本。本质:使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

代码(参考红宝书):

 

/*寄生组合式继承*/
function object(o){
    function F(){};   //创建临时性的构造函数(对象)
    F.prototype = o;  //浅复制(只将地址地址进行赋值)
    return new F(); 
}

function inheritPrototype(subType,superType){
    //创建对象,使用寄生式继承来继承超类型的原型
    var prototype = object(superType.prototype);  
    prototype.constructor = subType;  //增强对象
    subType.prototype = prototype;  //指定对象,将结果指定给子类型的原型
}

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}

SuperType.prototype.sayName = function () {
    alert(this.name);
};

function SubType(name,age){
    SuperType.call(this.name);  //借用构造函数来继承属性
    this.age = age;
}

inheritPrototype(SubType,SuperType);   //使用寄生式继承来继承方法

SubType.prototype.sayAge = function(t) {
    alert(this.age);
};
/*寄生组合式继承*/

 

原文地址:https://www.cnblogs.com/DDongX/p/8594785.html