JavaScript之继承

继承

很多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScritpt中无法实现接口继承。ECMAScript只支持实现继承,而且实现继承主要依靠原型链来实现。

原型链继承

原型链继承主要是让新实例的原型等于父类的实例

优点:实例可继承的属性包括实例构造函数的属性、父类构造函数的属性、父类原型的属性,但不会继承父类实例的属性

缺点:1. 新实例无法向父类构造函数传参,因为与父类构造函数的关系已经断开

​ 2. 继承单一,不能实现多继承

​ 3. 所有新实例都会共享父类实例的属性(原型上的属性是共享的,构造函数中的属性不共享,因为一个实例修改了原型属性, 另一个实例的原型属性也会被修改),这是原型链继承带来的主要问题,也有办法解决。

    //定义父类
    function SuperType(){
        this.value = 'super';
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    //原型链继承
    function SubType(){
        this.value = 'sub';
        this.subproperty = false;
    }
    SubType.prototype = new SuperType();   //SubType的原型对象等于SuperType的实例
    SubType.prototype.getSubValue = function(){
        return this.subproperty;
    };
    let instance = new SubType();
    console.log(instance.getSuperValue());  //true
    console.log(instance instanceof SuperType);  //true

借用构造函数继承

借用构造函数也叫伪造对象或经典继承。思路很简单,即在子类型构造函数的内部调用超类型构造函数。通过apply()和call()方法可以在新创建的对象上执行构造函数。这种方法解决了原型链继承中的缺点1(无法向父类构造函数传参)、2、3

优点:1. 只继承了父类构造函数的属性,没有继承父类原型的属性

​ 2. 在子实例中可向父实例传参

​ 3. 可以继承多个构造函数属性(call多个)

缺点: 1. 只能继承父类构造函数的属性

​ 2. 无法实现构造函数的复用

​ 3. 每个新实例都有父类构造函数的副本,内存消耗大

function SuperType(property){
    this.property = property;
    this.colors = ['red','green','blue'];
}
function subType(){
    //继承了SuperType,同时还传递参数
    SuperType.call(this, true);  //传给property
}
let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); //'red','green','blue','black'

let instance2 = new SubType();
console.log(instacne2.colors); //'red','green','blue'
console.log(instance2.property);  //true

如果借用构造函数,无法避免构造函数模式存在的问题—方法都在构造函数中定义,因此无法实现函数复用。这种方式很少用

组合继承(常用)

组合继承也叫伪经典继承,是将原型链和借用构造函数组合到一块。其技术原理是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

优点:1. 可以继承父类原型上的属性,可以传参,也可以复用

​ 2. 每个新实例引入的构造函数属性是私有的

缺点: 调用了两次父类构造函数(耗内存)(一次在创建子类型原型的时候,一次在子类型构造函数内部),子类构造函数会代替原型上的那个父类构造函数

function SuperType(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
};
function SubType(name, age){
    //继承属性
    SuperType.call(this,name);   //第二次调用SuperType()
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();  第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
    console.log(this.age);
};

let instance1 = new SubType('Simon', 24);
instance1.colors.push('black');
console.log(instance1.colors);  //'red','blue','green','black'
instance1.sayName();  //simon
instance1.sayAge(); //24
console.log(instance1 instanceof SuperType);  //true
console.log(instance1 instanceof SubType);  //true

let instance2 = new SubType('simoner', 23);
console.log(instance2.colors); //'red','blue','green'
instance2.sayName();  //simoner
instance2.sayAge(); //23    
console.log(instance2 instanceof SuperType);  //true
console.log(instance2 instanceof SubType);  //true

组合继承避免了原型链和借用构造函数的缺陷,是JavaScript最常用的继承模式。

原型式继承

用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意添加属性的实例或对象。

优点: 1. 相当于复制了一个对象

缺点: 1. 所有实例都会继承原型上的属性

​ 2. 无法实现复用

//原型式继承
function object(obj){
    function F(){}
    F.prototype = obj;
    return new F();
}
let Person = {
    name: 'simon',
    friends: ['bob','jack','tom'],
};
let anotherPerson1 = object(Person);
anotherPerson1.name = 'simoner';
anotherPerson1.friends.push('jerry');

let anotherPerson2 = object(Person);
anotherPerson2.name = 'alice';
anotherPerson2.friends.push('jan');

console.log(Person.friends);  //"bob", "jack", "tom", "jerry", "jan"

ECMAScript5通过新增Object.create()方法来规范化了原型式继承。也就是封装了object()方法

let Person = {
    name: 'simon',
    friends: ['bob','jack','tom'],
};
let anotherPerson1 = Object.create(Person);   //使用Object.create方法
anotherPerson1.name = 'simoner';
anotherPerson1.friends.push('jerry');

let anotherPerson2 = Object.create(Person);
anotherPerson2.name = 'alice';
anotherPerson2.friends.push('jan');

console.log(Person.friends);  //"bob", "jack", "tom", "jerry", "jan"

寄生式继承

寄生式继承是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。这种继承方式与原型式继承紧密相关,相当于给原型式继承外面套了一个函数

优点:没有创建自定义类型

缺点:没有用到原型,无法复用

//寄生式继承
function createAnother(originalObj){
    let clone = Object.create(originalObj);   //通过调用函数创建一个新对象
    clone.sayHi = function(){   //以某种方式来增强对象
        console.log('Hi');
    };
    return clone;  //返回这个对象
}
let Person = {
    name: 'simon',
    friends: ['bob','jack','tom'],
};
let anotherPerson = createAnother(Person);
anotherPerson.sayHi();  //Hi

寄生组合式继承—常用

寄生组合式继承是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

//寄生组合式继承
function inheritProperty(subType, superType){
    let prototype = Object.create(superType.prototype);  //创建对象
    prototype.constructor = subType;  //增强对象,也可以增加其他属性和方法
    subType.prototype = prototype;  //指定对象
}
function SuperType(name) {
    this.name = name;
    this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
};
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
inheritProperty(SubType,SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
};

let instance = new SubType('simon', 24);
instance.sayName();
instance.sayAge();

ES6中的继承

通过class关键字来创建类,并使用extend关键字来实现类继承

//ES6中的继承
class Person {
    // 在constructor()传参
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    /*下面的写法就等同于把方法挂在原型上*/
    static sayHi(){// 加了static表示静态方法,只给类用的方法
        console.log('Hi');
    }//方法和方法之间不用加逗号
    sayName(){//动态方法,给实例使用的方法
        console.log(this.name);
    }
}

let person = new Person('simon',24);
person.sayName();

class Simon extends Person {
    //一般把子类属性放在前面,父类属性放后面并用...arg代替,这样就直接继承父类的所有属性
    constructor(job, ...arg){
        super(...arg); //等同于调用父类,把多余的参数(和父类一样的属性)放到super中,达到继承父类属性的目的
    	//在继承里,写constructor必须写super
        //super下面才能使用this,super有暂存死区(super上面不能使用this,用了就报错)
    	this.job = job;
    }
    running(){
        console.log('I am running');
    }
    sayJob(){
        console.log(this,job);
    }
}
let simon = new Simon('FontEnd','simoner',24);
simon.sayName();  //simoner
simon.running(); // I am running
simon.sayJob();  //FontEnd
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值