js面向对象-继承

156 阅读3分钟

js没有类的概念,js里万物皆是对象,对象是无序属性的集合,其属性可以包含基本值,对象或者函数

曾经一段时间因为js关于类实现继承的不规范,导致各种各样实现继承的代码;而实际上不管代码怎么变,继承都基于两种方式:

  1. 通过原型链,即子类的原型指向父类的实例从而实现原型共享。
  2. 借用构造函数,即通过js的applycall实现子类调用父类的构造方法;
  • 原型链继承

function SuperType(){ 
    this.colors = ["red", "blue", "green"];
} 
function SubType(){ 
} 
//继承了 SuperType 
SubType.prototype = new SuperType(); 
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors); //"red,blue,green,black" 
var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green,black"

原理:子类构造函数的原型对象指向父类的实例
缺点:

  1. 因为子类共享父类的属性,如上例父类属性为引用类型时,一个子类更改数据,会反应到其他子类。
  2. 创建子类型的实例时,不能向超类型的构造函数中传递参数。
  • 借用构造器函数

function SuperType(name){ 
    this.name = name;
    this.colors = ["red", "blue", "green"]; 
} 
function SubType(name){ 
    //继承了 SuperType 
    SuperType.call(this, name); 
}
var instance1 = new SubType('name'); 
instance1.colors.push("black"); 
alert(instance1.colors); //"red,blue,green,black" 
var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green"

原理:通过在子类中调用父类的构造方法,这样每个子类都能拥有父类的属性副本。 优点: 解决上原型链继承的所有缺点。 缺点: 显而易见,无法继承父类原型链上的属性。

  • 组合继承

function SuperType(name){ 
    this.name = name;
    this.colors = ["red", "blue", "green"]; 
} 
function SubType(name){ 
    //继承了 SuperType 
    SuperType.call(this, name); 
}
SubType.prototype = new SuperType();
var instance1 = new SubType('cjz'); 
instance1.colors.push("black"); 
alert(instance1.colors); //"red,blue,green,black" 
var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green"

原理:结合上面两种模式,仍有明显缺点,一般直接用最后一种寄生组合式继承。

缺点:

  1. 调用了两次父类的构造函数,浪费内存。
  2. 子类实例的原型链上有重复属性,比如上述例子子类中有属性name,父类上也有属性name
  • 原型式继承

function object(o){ 
    function F(){} 
    F.prototype = o; 
    return new F(); 
}
var person = { 
    name: "Nicholas", 
    friends: ["Shelby", "Court", "Van"] 
}; 
var anotherPerson = object(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob"); 
var yetAnotherPerson = object(person); 
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie"); 
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

原理:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。简单了解即可,一般不会单独使用。

  • 寄生式继承

function object(o){ 
    function F(){} 
    F.prototype = o; 
    return new F(); 
}

function createAnother(original) {
    let clone = objectCopy(original);
    clone.getName = function () {
            console.log(this.name);
    };
    return clone;
}

let person = {
    name: "yhd",
    friends: ["rose", "tom", "jack"]
}

let person1 = createAnother(person);
person1.friends.push("lily");
console.log(person1.friends);
person1.getName(); // yhd

let person2 = createAnother(person);
console.log(person2.friends); // ["rose", "tom", "jack", "lily"]

原理:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。简单了解即可,一般不会单独使用。

  • 寄生组合式继承

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(){ 
    alert(this.age); 
};

只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。