前端八股文-继承

779 阅读4分钟

系列文章

前端八股文-类型判断

前端八股文-原型链

前端八股文-手写new、bind、call、apply的三两事

类式继承

原理:

Son.prototype = new Father(),即解释为将父类的实例化对象赋值给子类的原型

子类可以访问到父类原型上的属性和方法,也能访问构造函数中的属性和方法

实现:

// 声明父类
function Father() {
    this.fatherValue = true;
}
// 父类添加原型方法
Father.prototype.getFatherValue = () => this.fatherValue

// 声明子类
function Son() {
    this.sonValue = false;
}
// **********  继承父类  ********** 
Son.prototype = new Father(); 
// 子类添加原型方法
Son.prototype.getSonValue = () => this.sonValue;

解惑:

new Son.()__proto__ => Son.prototype === new Father() // 即Father实例 

new Father().__proto__ => Father.prototype

弊端:

  • 父类中共有属性存在引用类型,子类更改会对父类产生影响,从而影响其他子类
  • 子类初始化,无法向父类传递参数,因而实例化父类的时候无法对父类构造函数内的属性进行初始化

构造函数继承

原理:

申明子类,执行Father.call(this, ...agruments)

只继承父构造函数中的属性和方法

实现:

// 声明父类
function Father(id) {
    this.id = id;
    this.books = ['javascript', 'java', 'css'];
}
// 父类添加原型方法
Father.prototype.showBooks = () => this.books

// 声明子类
function Son(id) {
    Father.call(this,id)
}
var instance1 = new Son(10);
var instance2 = new Son(11);
instance1.books.push('html');
console.log(instance1.books ) // ['javascript', 'java', 'css', 'html']
console.log(instance2.books ) // ['javascript', 'java', 'css']
instance1.showBooks()  // TypeError

解惑:

弊端:

  • 此类型的继承没有涉及原型prototype
  • 若要继承,必须将方法属性放到构造函数中用this绑定

组合继承

原理:

申明子类,执行Father.call(this, ...agruments)

Son.prototype = new Father(),

// 声明父类
function Father(id) {
    this.id = id;
    this.books = ['javascript', 'java', 'css'];
}
// 父类原型共有方法
Father.prototype.getId = () => {
    console.log(this.id)
}
// 声明子类
function Son(id, time) {
    // 构造函数继承
    Father.call(this,id)
    this.time = time;
}
// 类式继承
Son.prototype = new Father();
Son.prototype.getTime = () => {
    console.log(this.time);
}
// 测试用例
var instance1 = new Son(1, 2020);
instance1.books.push('html') 
console.log(instance1.books)// ['javascript', 'java', 'css', 'html']
instance1.getId(); // 1
instance1.getTime(); // 2020

var instance2 = new Son(2, 2021);
console.log(instance2.books); //  ['javascript', 'java', 'css'];
instance1.getId(); // 2
instance1.getTime(); // 2021

解惑:

弊端:

  • 使用构造函数继承,调用了一次父类的构造函数,子类原型的类式继承又调用了一次父类构造函数。(父类构造函数调用了两次)

原型式继承

原理:

创建一个纯净的函数,将父类对象赋值给纯净函数的原型

实现:

function inheritObject(o) {
    // 申明一个过渡函数
    function F() {}
    // 过渡对象的原型继承父对象
    F.prototype  = o;
    // 返回过渡对象的一个实例,该实例的原型继承了父对象
    return new F();
}
// 测试代码
var animals = {
    name: '动物集合',
    specialList: ['dog', 'cat', 'mouse']
} 

var newAnimals = inheritObject(animals);
newAnimals.name = '神话动物';
newAnimals.specialList.push('dragon');

var otherAnimals = inheritObject(animals);
otherAnimals.name = '其他动物';

console.log(newAnimals.name) // 神话动物
console.log(otherAnimals.name) // 其他动物

console.log(newAnimals.specialList) // ['dog', 'cat', 'mouse', 'dragon']
console.log(otherAnimals.specialList) // ['dog', 'cat', 'mouse', 'dragon']

解惑:

弊端

  • 与类式继承相同,父类中共有属性存在引用类型,子类更改会对父类产生影响,从而影响其他子类

寄生式继承

原理:

原型继承的二次封装

实现:

function inheritObject(o) {
    // 申明一个过渡函数
    function F() {}
    // 过渡对象的原型继承父对象
    F.prototype  = o;
    // 返回过渡对象的一个实例,该实例的原型继承了父对象
    return new F();
}
// 申明基础对象
var animals = {
    name: '动物集合',
    specialList: ['dog', 'cat', 'mouse']
} 

function createAnimals(obj) {
    var o = inheritObject(obj);
    o.getNmae = () => {console.log(name)}
    return o;
}

解惑:

弊端:

寄生组合继承

原理:

复制一份父类的原型副本(将父类原型赋值给纯净的全新构造函数),然后再赋值给子类原型,同时修正增强constructor

实现方案一:

function inheritObject(o) {
    // 申明一个过渡函数
    function F() {}
    // 过渡对象的原型继承父对象
    F.prototype  = o;
    // 返回过渡对象的一个实例,该实例的原型继承了父对象
    return new F();
}
function inheritPrototype(SonClass, FatherClass) {
    // 干净的FatherClass实例,只带了原型上的方法和属性   
    let p = inheritObject(FatherClass.prototype) 
    console.log('检验p是不是FatherClass的实例', p instanceof FatherClass)
    console.log('实例化后的洁净实例对象', p)
    p.constructor = SonClass // 此时p为了作为SonClass的原型对象,需要更改constructor的指向,p的_proto_指向FatherClass的原型
    SonClass.prototype = p
}
// 测试代码
// 声明父类
function Father(id) {
    this.id = id;
    this.books = ['javascript', 'java', 'css'];
}
// 父类原型共有方法
Father.prototype.getId = () => {
    console.log(this.id)
}
// 声明子类
function Son(id, time) {
    // 构造函数继承
    Father.call(this,id)
    this.time = time;
}
inheritPrototype(Son, Father);
Son.prototype.getTime = () => {
    console.log(this.time)
}

实现方案二(Object.create)

function inheritPrototype(subType, superType){
    var protoType = Object.create(superType.prototype);    //创建对象
    protoType.constructor = subType;                    //增强对象
    subType.prototype = protoType;                        //指定对象
}

解惑:

与组合继承最大的不同在于,父类构造函数不执行两遍。