js-继承

64 阅读5分钟

说明

  • 几种继承的方式

  • 立超老师视频

  • 立超老师笔记

  • 原型链继承

  • 可以继承父原型属性,也可以继承父实例属性

  • 缺点

  • 所有子类会共享父的实例的属性,如何该属性是引用类型的话,比如数组,一个子类修改数据中,其他子都会都到影响

  • 创建子类的时候不能向超类(父的构造函数)传递参数

  • 借用构造函数继承

  • 可以继承父实例的属性,不能继承父的原型属性

  • 解决:创建子类型时可以向超类型传递参数

  • 缺点

  • 无法实现父中方法的复用,每次都要创建新的方法

  • 因为不能继承父的原型属性,所以想要父中的方法被子类继承,就得定义在实例属性上

  • 这样每次子类创建对象,就会重新创建这个方法的,浪费内存;

  • 超类型原型上定义的方法,子类不能访问

  • 组合继承

  • 可以继承父实例属性,也可以继承父原型的属性

  • 解决:上述两种方式各自的缺点

  • 缺点:

  • 但是父的构造函数会执行两次,累加操作有影响

  • 子的实例上和子继承父的原型上有重复的属性

  • 原型式继承

  • Object.create(proto,option)

  • 优点:可以实现基于一个对象的简单继承,不必创建构造函数

  • 缺点:与原型链方式相同,一个是传参的问题,一个是属性共享的问题。

  • 寄生继承

  • 优点:在主要考虑对象而不是自定义类型和构造函数的情况下,实现简单的继承。

  • 缺点:

  • 不能用instanceof判断出父类

  • 没有办法实现函数的复用

  • 寄生组合继承(类式继承)

  • 优点:效率高,避免了在 Fu.prototype 上创建不必要的属性。与此同时还能保持原型链不变,开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

为什么有这么多的继承方式呢?那是因为最终的结果都是一步一步的在向正真的面向对象OOP靠齐。

最终的期望是:

  • 子类可以继承父类的实例属性,并可以传参

  • 所有子类都是新的实例属性,不会共享;

  • 子类可以继承父类的原型属性

原型链继承

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>01_原型链继承</title>
</head>
<body>
<!--
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,
在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。
还有就是在创建子类型的时候不能向超类型传递参数。

方式1: 原型链继承
  1. 套路
    1. 定义父类型构造函数
    2. 给父类型的原型添加方法
    3. 定义子类型的构造函数
    4. 创建父类型的对象赋值给子类型的原型
    5. 将子类型原型的构造属性设置为子类型
    6. 给子类型原型添加方法
    7. 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    1. 子类型的原型为父类型的一个实例对象
-->
<script type="text/javascript">
// 父类
 function Fu(name){
   this.name = name | '张三'
   this.list = []
 }
 Fu.prototype.say = function(){
   console.log(this.name);
 }

//  子类
 function Son(params){
  this.params = params
 }
 Son.prototype = new Fu()
 Son.prototype.constructor = Son // 修复constructor指向

 Son.prototype.showSelf = function(){
   console.log(this.params);
 }

   
 // 测试1
 // 创建子类的时候不能向超类(父的构造函数)传递参数

 let s = new Son('ppp')

 console.log(s.params);
 console.log(s.name);
 s.say()
  
  
</script>
</body>
</html>

借用构造函数继承

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_借用构造函数继承</title>
</head>
<body>
<!--
(2)第二种方式是使用借用构造函数的方式,
这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,
这一种方法解决了不能向超类型传递参数的缺点,
但是它存在的一个问题就是无法实现函数方法的复用,
并且超类型原型定义的方法子类型也没有办法访问到。

方式2: 借用构造函数继承(假的)
1. 套路:
  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造
2. 关键:
  1. 在子类型构造函数中通用super()调用父类型构造函数
-->
<script type="text/javascript">

 // 父类
 function Fu(name){
   this.name = name | '张三'
   this.list = []
 }
 Fu.prototype.say = function(){
   console.log(this.name);
 }

//  子类
 function Son(params,name){
  Fu.call(this,name) // 关键:借用构造函数,就相当于把父类的实例的值复制过来执行了;
  this.params = params
 }

 let s = new Son(123,"李四")
 
</script>
</body>
</html>

组合继承

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>03_组合继承</title>
</head>
<body>
<!--
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数
组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,
通过将子类型的原型设置为超类型的实例来实现方法的继承。
这种方式解决了上面的两种模式单独使用时的问题,
但是由于我们是以超类型的实例来作为子类型的原型,
所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用call()借用父类型构建函数初始化相同属性
缺点:
1.会调用两次父类的构造函数(first:子的原型对象指向父的实例,实例化的时候,
second:call的时候)
-->
<script type="text/javascript">

 // 父类
 function Fu(name){
   this.name = name | '张三'
   this.list = []
 }
 Fu.prototype.say = function(){
   console.log(this.name);
 }

//  子类
 function Son(params,name){
  Fu.call(this,name)  // 得到父类型的属性
  this.params = params
 }
 Son.prototype = new Fu(); // 得到父类型的方法
 Son.prototype.constructor = Fu

 Son.prototype.show = function() {
   console.log(this.params);
 }

//  测试
 let s = new Son(123,"李四")

</script>
</body>
</html>

原型式继承

可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型
简单来说这个函数的作用就是,传入一个对象,返回一个原型对象为该对象的新对象。

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

// ES6 新增了Object.create()方法

寄生继承

寄生式继承,寄生式继承的思路是创建一个用于封装继
承过程的函数,通过传入一个对象,然后复制一个对象的副本,
然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一
种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不
是我们的自定义类型时。缺点是没有办法实现函数的复用。

// 配合工厂函数来执行,其实就是给一个构造函数的原型扩展方法
function object(o){
  function F(){} // 寄生函数
  F.prototype = o; 
  return new F(); 
}

function createAnother(obj){ 
  var f = object(obj); //通过调用函数创建一个新对象
  
  f.say = function(){ //给这个对象添加方法
  	alert("hi"); 
  }; 
  return f; //返回这个对象
}


var personFather = {
  name: "lh", 
  loves: ["a", "b", "c"] 
}; 
var p = createAnother(person); 
p.say();  //"hi"

使用es6 Object.create() 简化写法

// 父类
 function Fu(name){
   this.name = name | '张三'
   this.list = []
 }
 Fu.prototype.say = function(){
   console.log(this.name);
 }

//  子类
 function Son(params){
  this.params = params
 }
 Son.prototype = Object.create(Fu.prototype, {
  constructor: {
    value: Son,
    enumerable: false,
    writable: true,
    configurable: true
  }
})
 Son.prototype.showSelf = function(){
   console.log(this.params);
 }

 let s = new Son('ppp')
 
 // 测试1:无法给超类型传参
 console.log(s.params);
 console.log(s.name);
 s.say()

寄生组合继承【最佳实践】

前面我们提到过了组合继承的缺点,由于调用了两次超类的构造函数,
导致基类的原型对象中增添了不必要的超类的实例对象中的所有属性。

寄生式组合继承就是用来解决这个问题,它与组合继承不同的地方主要是,
在继承原型时,我们继承的不是超类的实例对象,而是原型对象是超类原
型对象的一个实例对象,这样就解决了基类的原型对象中增添了不必要的
超类的实例对象中的所有属性d的问题。

寄生式组合继承,组合继承的缺点就是使用超类型的
实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继
承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免
了创建不必要的属性。

// 父类
function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

// 子类
function Child(params,value) {
  Parent.call(this, value)  // 继承父类的实例
  this.params = params
}
// 继承父类的原型
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1,2)

child.getValue() // 2
child instanceof Parent // true

封装起来

// 封装起来
function inheritPrototype(Son, Father){ 
  var prototype = Object.create(Father.prototype); 
  Son.prototype = prototype;   
  prototype.constructor = Son;
}

// 父类
function Father(name){ 
 this.name = name; 
} 
Father.prototype.sayName = function(){ 
 alert(this.name); 
}; 

// 子类
function Son(name, age){ 
 Father.call(this, name);  // 别忘了
 this.age = age; 
} 
inheritPrototype(Son, Father);

多继承

  • www.cnblogs.com/lj915/p/378…

    function Parent1(name,age){ this.name = name; this.age = age; this.height=180; } Parent1.prototype.say = function(){ alert('hi...'); } function Parent2(name,age,weight){ this.name = name; this.age = age; this.weight = weight; this.height = 170; this.skin='yellow'; } Parent2.prototype.walk = function(){ alert('walk...'); }

    function Child(name,age,weight){ Parent1.call(this,name,age); Parent2.call(this,name,age,weight); }

    for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]} for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]}

    var c1 = new Child('xiaoming',10,8); console.log(c1); //Child { name="xiaoming", age=10, height=170, 更多...}

    console.log(c1.constructor);//Child(name,age,weight)

Class和寄生继承的区别