ES5和ES6的继承方式以及多继承的实现

1 篇文章 0 订阅
1 篇文章 0 订阅


一、JS中的抽象概念

1. 原型
在js中万物皆对象,对象又分为两种:普通对象(Object)和(Function).–null也是对象,但是这里不做考虑。
任何对象都具有隐式原型属性(proto
只有函数对象有显示原型属性(prototype)。

2. 原型链
原型链是针对原型对象的,在查找实例属性时,现在实例中查找,如果没有找到,再到obj.proto(f1.prototype)原型属性中查找,再到f1.prototype._proto_中查找,依次向上,直到Object.prototype。

3. 作用域链
作用域链是针对变量的,先在自己的变量范围内查找,如果找不到,就沿着作用域链向上查找,直到window结束。

4. 构造函数、原型对象和实例之间的联系
在这里插入图片描述

5. 构造函数、原型对象、子类构造函数、子类原型对象和子类实例之间的联系
在这里插入图片描述

二、ES5的继承方式

1.原型链继承

子类的原型为父类的一个实例对象

function Person(name, age) {
        this.name = name;
        this.age = age;
        this.setName = function() {}
    }
    Person.prototype.setAge = function() {};
    //子类型
    function Student(price) {
        this.price = price;
        this.setScore = function() {};
    }
    Student.prototype = new Person;//核心代码
    //子类的原型为父类的一个实例对象
    var s1 = new Student(25);
    var s2 = new Student(35);
    console.log(s1, s2);

在这里插入图片描述
s1可以通过s1. _ proto _ = Student.prototype => s1._ proto _ = new Person。这样实例对象就可以访问到父类的私有方法,然后s1再通过_ proto _指向父类的prototype就可以获得父类原型上的方法。所以做的到了将父类的私有、公有方法和属性都作为子类的公有属性。
这就暴露出了问题,我们都知道在操作基本数据类型的时候操作的是值,在操作引用数据类型的时候操作的是地址,如果说父类的私有属性中有引用类型的属性,它被子类继承的时候会作为公有属性,这样s1操作这个属性的时候,就会影响到s2。

s1.play.push(4)
console.log(s1.play, s2.play)
console.log(s1.__proto__ === s2.__proto__)//true
console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__)//true

s2随着s1变化而变化。

我们需要在子类中添加新的方法或者重写父类方法的时候,切记一定要放在替换原型的语句之后

//Student.prototype.sayHello = function() { }//写在这无效
//因为会改变原型指向,所以要放在之后指定
Student.prototype = new Person();
Student.prototype.sayHello = function() { }
var S1 = new Student(25);
console.log(s1);

//Student {price: 15000, setScore: ƒ}
//price: 15000
//setScore: ƒ ()
//__proto__: Person
	//age: undefined
	//name: undefined
	//sayHello: ƒ ()
	//__proto__: Object

缺点:

  • 无法实现多继承
  • 来自原型对象的所有属性被实例共享
  • 创建子类实例时,无法向父类构造函数传参
  • 要想为子类新增属性和方法,必须放在Student.prototype = new Person()之后执行,不能放在构造器里。

2.构造函数继承

在子类型构造函数中通过call()调用父类构造函数

function Person(name, age) {
       this.name = name;
       this.age = age;
       this.setName = function() { };
   }
Person.prototype.setAge = function() {};
function Student(name, age, price) {
   Person.call(this, name, age);//核心代码
   this.price = price
}
var s1 = new Student('Tom', 20, 4999);
console.log(s1)

//Student {name: "Tom", age: 20, price: 4999, setName: ƒ}
//age: 20
//name: "Tom"
//price: 4999
//setName: ƒ ()
//__proto__: Object

这种方法只能实现部分的继承,如果父类的原型还有方法或者属性,子类是拿不到这些属性和方法的。

console.log(s1.setAge())//Uncaught TypeError: s1.setAge is not a function

特点:

  • 解决了原型链继承中子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call多个父类对象)

缺点:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性个方法,不能继承原型属性和方法
  • 无法实现函数复用,每个子类都有父类实例属性的副本,影响性能。

3.组合继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类的原型,实现函数复用。

function Person(name, age) {
       this.name = name;
       this.age = age;
       this.setAge = function() {}
   }
   Person.prototype.setAge = function() {
       console.log("111");
   }
   function Student(name, age, price) {
       Person.call(this, name, age);//核心代码
       this.price = price;
       this.setScore = function() {}
   }
   Student.prototype = new Person();//核心代码
   Student.prototype.constructor = Student //核心代码
   //组合继承也是需要修复构造函数的指向
   Student.prototype.sayHello = function() {};
   var s1 = new Student('Tom', 20, 344);
   var s2 = new Student('jack', 24, 355);
   var p1 = new Person("mack", 33);
   console.log(s1);
   console.log(s1.constructor);
   console.log(p1.constructor);

//Student {name: "Tom", age: 20, price: 344, setAge: ƒ, setScore: ƒ}age: 20name: "Tom"price: 344setAge: ƒ ()setScore: ƒ ()__proto__: Person
//VM776:21 ƒ Student(name, age, price) {
//       Person.call(this, name, age);
//       this.price = price;
//       this.setScore = function() {}
   }
//VM776:22 ƒ Person(name, age) {
 //      this.name = name;
 //      this.age = age;
 //      this.setAge = function() {}
   }

组合继承是js中最常用的继承模式。
缺点:

  • 会调用两次构造函数(一次在创建子类型原型的时候,另一次是在子类型构造函数内部)

优点:

  • 可以继承实例属性、方法,也可以继承原型属性方法
  • 不存在引用属性共享问题
  • 可以传参
  • 函数可复用

2.寄生组合式继承

借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。

function Person(name, age) {
       this.name = name;
       this.age = age;
   }
   Person.prototype.setAge = function() {
       console.log("111");
   }
   function Student(name, age, price) {
       Person.call(this, name, age)//核心代码
       this.price = price;
       this.setScore = function() {}
   }
   Student.prototype = Object.create(Person.prototype) //核心代码
   Student.prototype.constructor = Student //核心代码
   var s1 = new Student("Tom", 20, 377);
   console.log(s1 instanceof Student, s1 instanceof Person) //true true
   console.log(s1.constructor) //Student
   console.log(s1)
   
//true true
//VM784:17 ƒ Student(name, age, price) {
//       Person.call(this, name, age)//核心代码
//       this.price = price;
//       this.setScore = function() {}
//   }
//VM784:18 Student {name: "Tom", age: 20, price: 377, setScore: ƒ}

目前公认最完美的方法!!!

三、ES6中class的继承

ES6引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的继承方法。
ES5的继承,实质是创造子类的实力对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面,然后再用子类的构造函数修改this。

class Person{
       //调用类的构造函数
       constructor(name, age) {
           this.name = name;
           this.age = age;
       }
       //定义一般的方法
       showName() {
           console.log("调用父类的方法");
           console.log(this.name, this.age);
       }
   }
   let p1 = new Person('jack', 20);
   console.log(p1);
   //定义一个子类
   class Student extends Person {
       constructor(name, age, salary) {
           super(name, age)//调用父类的构造方法
           this.salary = salary;
       }
       //在子类自身定义方法
       showName() {
           console.log("调用子类的方法");
           console.log(this.name, this.age, this.salary)
       }
   }
   let s1 = new Student('one', 38, 100000)
   console.log(s1);
   s1.showName();

四、实现多继承

1.ES5方法

 function BaseClass1() {
       this.age = 10;
       this.sex = 'male'
   }
   BaseClass1.prototype.run = function() {
       console.log("run");
   }
   function BaseClass2() {
       this.name = 'jack';
       this.salary = 5999;
   }
   BaseClass2.prototype.walk = function() {
       console.log("walk");
   }

   function Child() {
       this.city = "beijing";
       this.hobby = "dance";
       //实例属性继承
       BaseClass1.call(this);
       BaseClass2.call(this);
   }

   //原型链继承
   for(let prop in BaseClass1.prototype) {
       Child.prototype[prop] = BaseClass1.prototype[prop];
   }
   for(let prop in BaseClass2.prototype) {
       Child.prototype[prop] = BaseClass2.prototype[prop];
   }

   //Child.prototype.constructor = Child;
   var childInstance = new Child();
   childInstance.run();
   childInstance.walk();
   console.log(childInstance.name);
   console.log(childInstance.age);
   
//run
//walk
//jack
//10

2.ES6方法

// 共同的特性
  class Base {
      constructor() {
          console.log("Base");
      }
  }

// 鱼类的特性【创建一个鱼类并继承Base】
  const FishMixin = (superClass) => class extends superClass {
      constructor() {
          super();
          console.log("FishMixin");
      }
  };

// 狗的特性【创建一个狗类并继承Base】
  const DogMixin = (superClass) => class extends superClass {
      constructor() {
          super();
          console.log("DogMixin");
      }
  }

// FishMixin 和 DogMixin 是没有任何继承关系的,如何Test都继承,就是多继承了
  class test extends DogMixin(FishMixin(Base)) {

  }
var child = new test();

//Base
//FishMixin
//DogMixin

总结

重点理解原理

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值