JavaScript | 继承

—————————————————————————————————————————————————————————

继承 - ECMAScript只支持实现继承(依靠原型链),不支持接口继承(函数没有签名)

原型链

  • 利用原型让一个引用类型继承另一个引用类型的属性和方法,
  • 构造函数、原型、实例的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针。实例包含一个指向原型对象的内部指针,在创建实例之后即指向原型对象
  • 而当A原型对象的指针指向B个原型对象时(此时A原型对象与B实例同级),就形成了一条原型链。
  • 图解:

    原型搜索机制:当读取模式访问一个实例属性时,首先会在实例中搜索该属性,如果没有找到该属性则沿着原型链向上查找

    在例子<Demo-1>中,调用instance.getSuperValue(),先搜索实例instance,再搜索SubType.prototype,再搜索SuperType.protorype,最后一步才找到该方法。

    默认的原型:所有的引用类型默认都继承了Object,所以默认原型的指针都会指向Object.prototype,完整的原型链如下:

    instance SubType.prototype SuperType.prototype Object.prototype

  • p.s.

    必须替换掉实例的原型后才能给实例添加方法

    不能使用对象字面量创建原型方法,这样做会重写原型链,如<Demo-3>

  • 缺点:

    包含引用类型值(Function Object Array)的原型属性会被所有实例共享,在通过原型来实现继承时,原型实际上会变成另一个类型的实例,所以原先的实例属性就变成了现在的原型属性了。<Demo-4>

    在创建子类型的实例时,不能向超类型的构造函数中传递参数。

// "use strict";

// Demo - 1
// SuperType 拥有一个属性和一个方法
// SubType 拥有一个属性和一个方法,又从SuperType那里继承了一个属性一个方法
function SuperType(){
    this.property = "111";
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subproperty = "222";
}
// p.s.new操作之前,SubType.prototype指向的是function,不允许为function()定义.getSubValue方法,所以要将添加方法放在修改原型指向之后
// 操作之后SubType.prototype指向SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){ // 必须在SubType替换原型之后才能定义
    return this.subproperty;
}
var instance = new SubType();
console.log(instance.property); // 111
console.log(instance.getSuperValue()); // 111
console.log(instance.subproperty); // 222
console.log(instance.getSubValue()); // 222
console.log(instance.constructor); // f SuperType(){} 原本SubType中的constructor属性被重写
// 重写SuperType.getSuperValue()
// 如果要重写这个方法,会屏蔽原来的方法
// 换句话说,当通过SubType的实例调用getSuperValue时调用的就是这个重新定义的方法,但通过SuperType的实例调用时还会继续调用原来的方法
var beforeReWrite = new SuperType();
SuperType.prototype.getSuperValue = function(){
    console.log("rewrite");
}
console.log(instance.getSuperValue()); // rewrite,this.property = undefined
console.log(SuperType.prototype.getSuperValue()); // rewrite,this.property = undefined
console.log(beforeReWrite.getSuperValue());

// Demo - 2
// 确认原型和实例的关系
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
// 另一种方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

// Demo - 3
function SuperType2(){
    this.property = "1111";
}
SuperType2.prototype.getSuperValue = function(){
    return this.property;
}
function SubType2(){
    this.subproperty = "2222";
}
SubType2.prototype = new SuperType2();
SubType2.prototype = {
    getSubValue:function(){
        return this.subproperty;
    },
    someOtherMethod:function(){
        return false;
    }
}
var instance2 = new SubType2();
console.log(instance2 instanceof Object); // true
console.log(instance2 instanceof SuperType2); // false,原型链被切断
console.log(instance2 instanceof SubType2); // true
// console.log(instance2.getSuperValue()); // error

// Demo - 4
function SuperType3(){
    this.colors = ["red","blue","green"];
}
function SubType3(){}
SubType3.prototype = new SuperType3();
var instance3 = new SubType3();
instance3.colors.push("black");
console.log(instance3.colors); // ["red", "blue", "green", "black"]
var instance4 = new SubType3();
console.log(instance4.colors); // ["red", "blue", "green", "black"]

借用构造函数(伪造对象 / 经典继承)

  • 在子类型构造函数的内部调用超类型构造函数
  • 优点:

    解决了单独使用原型链共享引用类型值属性的问题

    可以在子类型构造函数中向超类型构造函数传递参数

  • 缺点:

    无法避免构造函数模式存在的问题:方法都在构造函数中定义,无法实现函数复用

// "use strict";

function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}

function SubType() {
    SuperType.call(this, "name1");
    this.age = 20;
}

var instance = new SubType();
instance.colors.push("444");
console.log(instance.colors); // ["111", "222", "333", "444"]
console.log(instance.name); // name1
console.log(instance.age); // 20
var instance2 = new SubType();
console.log(instance2.colors); // ["111", "222", "333"]

组合继承(伪经典继承)

  • 将原型链和借用构造函数组合,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
  • 对应创建对象 <组合使用构造函数模式和原型模式>
  • 优点:最常用
  • 缺点:需要调用两次超类型构造函数,一次在创建子函数原型时,另一次在子函数构造函数内部。调用子类型构造函数时需要重写属性
// "use strict";
function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name); // 继承属性
    this.age = age;
}
SubType.prototype = new SuperType(); // 继承方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    console.log(this.age);
}
var instance1 = new SubType("hugh", 20);
instance1.colors.push("444");
console.log(instance1.colors); // ["111", "222", "333", "444"]
instance1.sayName(); // hugh
instance1.sayAge(); // 20
var instance2 = new SubType("dong", 21);
console.log(instance2.colors); // ["111", "222", "333"]
instance2.sayName(); // dong
instance2.sayAge(); // 21

原型式继承

  • 对应创建对象 <动态原型模式>
  • 没有使用严格意义上的构造函数,借助已有的对象创建新对象
  • 优点:

    在不想创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以胜任

  • 缺点:

    包含引用类型值的属性始终都会共享,就像原型模式一样

// "use strict";
function object(o){
    function F(){} // 创建临时性构造函数
    F.prototype = o; // 将传入的对象作为构造函数的原型
    return new F(); // 返回临时类型的一个新实例
}

var person = {
    name:"hugh",
    friends:["111",'222','333']
};

var anotherPerson = object(person);
anotherPerson.name = "dong";
anotherPerson.friends.push("444");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "hehe";
yetAnotherPerson.friends.push("555");

console.log(person.friends); // ["111", "222", "333", "444", "555"]
console.log(person.name); // hugh
console.log(anotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(anotherPerson.name); // dong
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(yetAnotherPerson.name); // hehe

// 使用Object.create()规范化原型式继承
// 以这种方式指定的任何属性都会覆盖原型对象上的同名属性
var otherPerson1 = Object.create(person);
otherPerson1.friends.push("666");
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555", "666"]
var otherPerson2 = Object.create(person,{
    name:{
        value:"test"
    }
});
console.log(otherPerson2.name);

寄生式继承

  • 对应创建对象 <寄生构造函数 / 工厂模式>
  • 创建一个仅用于封装继承过程的函数,在内部增强对象,最后返回对象
  • 示范集成模式时使用的object()函数不是必须的,任何能够返回新对象的函数都适用于此模式
  • 使用场景:在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
  • 缺点:无法做到函数复用,类似于构造函数模式
// "use strict";
function object(o) {
    function F() {} // 创建临时性构造函数
    F.prototype = o; // 将传入的对象作为构造函数的原型
    return new F(); // 返回临时类型的一个新实例
}

function createAnother(original) { // 接收的函数作为新对象基础的对象
    var clone = object(original);
    clone.sayHi = function() { // 添加新方法
        console.log('hi');
    };
    return clone;
}
var person = {
    name: "hugh",
    friends: ['111', '222', '333']
};
var person1 = createAnother(person);
person1.sayHi();
console.log(person1.name);
console.log(person1.friends);

寄生组合式继承

  • 优点:

    最理想的继承范式

    解决组合继承重写属性的问题,只调用了一次SuperType构造函数

    避免了在SubType.prototype上创建不必要的属性

    原型链保持不变

    能够正常使用instanceofisPrototypeOf()

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

// 1.创建超类型原型的一个副本
// 2.为创建的副本添加constructor属性,弥补因重写原型而失去的属性
// 3.将新创建的对象(即副本)赋值给子类型的原型
function inheritProtoType(subType,superType){
    var prototype = object(superType.prototype); // 创建对象
    prototype.constructor = subType; // 增强对象
    subType.prototype = prototype; // 指定对象
}
function SuperType(name){
    this.name = name;
    this.colors= [1,2,3,4];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
inheritProtoType(SubType,SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

原文地址:https://www.cnblogs.com/hughdong/p/7264122.html