【笔记】【Javascript】javascript实现继承

前言

  • 之前写过关于面向对象编程的文章,通过阅读别人的博客了解了一下Javascript实现继承的方法,并且使用图画的形式帮助了解,图是自己做的,若有偏差请读者帮忙指出,谢谢。
  • 笔记中有些个人理解后整理的笔记,可能有所偏差,也恳请读者帮忙指出,谢谢。

免责声明

  • 为了方便,本文中使用的部分图片来自于网络,如有侵权,请联系博主进行删除,感谢其他博主提供的图床。
  • 此笔记用于记录本人对于该知识的汇总。以方便日后的工作与学习。
  • 如有侵权请告知,马上删除。

javascript如何实现继承?

【什么是继承?】

  • 子类拥有父类的特征行为

    • 特征==属性
    • 行为==方法

【怎么做?】

//父类
function Person(name){
    
    //给构造函数添加了参数
    this.name = name;
    this.msg = function(){
    
    
        alert(this.name);
    }
}
Person.prototype.age = 10;//给构造函数添加了原型属性

方法一:原型链继承

步骤1:创建一个子类,并把子类的原型对象指向父类的实例**(关键点)**

//创建子类
function Sub(){
    
    
    this.name = 'roy';
}
//子类的原型对象指向父类实例
Sub.prototype = new Person();

步骤2:创建子类实例,此时子类实例已经完成继承

var sub1 = new Sub();

步骤3:验证是否继承成功(其实真正实现继承仅上面两步即可,这一步属于调用父类,检验是否继承成功)

console.log(sub1.age);//10
//instanceof 判断元素是否在另一个元素的原型链上
//sub1继承了Person的属性,返回true
console.log(sub1 instanceof Person);//true

【分析】

  • 优点
    • 实现了继承
      • √ 实例的构造函数的属性(即子类的构造函数属性)
      • √ 父类的构造函数的属性
      • √ 父类原型的属性
  • 缺点
    • 新实例无法向父类构造函数传参
      • 【原因】步骤二时,是通过子类来创建新实例,而不是通过父类创建实例
    • 继承单一
      • 【原因】如图(1)一个子类仅继承一个父类
    • 修改子类实例的属性会改到父类的属性。
      • 【原因】如图(1)

方法二:构造函数继承

步骤1:在子类构造函数内回调父类构造函数(关键点)

function Sub(){
    
    
    Person.call(this,'roy');
    this.age = 12;
}

步骤2:创建子类实例

var sub1 = new Sub();

步骤3:检验是否继承成功

console.log(sub1.name);//roy
console.log(sub1.age);//12
console.log(sub1 instanceof Person);//false

【分析】

  • 优点
    • 实现了继承:
      • √ 继承父类构造函数的属性
      • ✕ 继承父类原型上的属性(如图(3))
    • 可以向父类构造函数传参
  • 缺点
    • 无法实现父类构造函数的复用
      • 【原因】用一次,重新调用父类构造函数一次
    • 每个新实例都有父类构造函数的副本,臃肿
      • 【原因】子类构造函数中产生了闭包,闭包导致内存泄露
    • 无法继承父类原型上的属性
      • 【原因】如图(3)

方法三:组合式继承

步骤1:在子类构造函数内回调父类构造函数**(关键点)**

function Sub(name){
    
    
    Person.call(this,name);//构造函数继承
}

步骤2:创建一个子类,并把子类的原型对象指向父类的实例**(关键点)**

Sub.prototype = new Person();//原型链继承

步骤3:创建子类实例,检验是否继承成功

var sub = new Sub('roy');
console.log(sub.name);//roy 继承了构造函数属性
console.log(sub.age);//10 继承了父类原型的属性

【分析】

  • 优点:
    • 实现了继承:
      • √ 继承父类原型上的属性
    • 可以传参,复用
    • 每个新实例引入的构造函数属性是私有的
      • 【原因】步骤1产生了闭包
  • 缺点:
    • 调用了两次父类构造函数,耗内存
    • 子类构造函数会代替原型上的父类的构造函数(不懂的点,欢迎小伙伴在评论区讨论一下)

方法四:ES6 Class类继承

为了统一使用ES6格式父类也进行改动一下(不改动直接用前面的父类也行)

//父类
class Person {
    
    //给构造函数添加
  constructor(name) {
    
    
    this.name = name;
  }
  msg() {
    
    
    alert(this.name);
  }
}
Person.prototype.age = 10;//给构造函数添加了原型属性

步骤1:使用关键字extends继承父类方法和属性,关键字super继承父类构造函数属性(关键点)

class Sub extends Person {
    
      //继承父类方法
  constructor(name) {
    
    
    super(name);        //继承父类构造函数属性
    this.sex = 'boy';
  }
}

步骤2:创建子类实例,检验是否继承成功

const sub = new Sub('lao');
console.log(sub.age);    	//10	//证明继承父类构造函数原型属性
console.log(sub.name);   	//lao
console.log(sub.sex); 		//boy
console.log(sub.msg());
console.log(sub instanceof Person);   //true

【分析】

  • 优点:

    • 实现继承
      • √ 继承父类原型上的属性
      • √ 继承父类构造函数的属性
    • 写法更加的简洁
  • 缺点:

    • 函数申明不能自动提升(必须先声明再使用)

      【示例】

      let sub1 = new Sub()	//报错ReferenceError
      class Sub{
              
              }
      

方法五:原型式继承

步骤1:通过一个函数来模拟子类构造函数(关键点)

function content(obj){
    
    
    function F(){
    
    };			//模拟子类的构造函数
    F.prototype = obj;	//将子类构造函数原型对象设为父类实例
    return new F();			//创建子类构造函数实例
}

步骤2:创建父类的实例

var person = new Person();

步骤3:调用模拟子类构造函数来模拟创建子类实例

var sub1 = content(person);

步骤4:检验是否继承成功

console.log(sub1.age);//10 继承了父类函数的属性

【分析】

  • 优点:
    • 无需定义子类,反而使用一个现成的类直接继承。
  • 缺点:
    • 所有实例都会继承父类原型上的属性(如图(4)所示)
    • 无法实现复用(新实例属性都是后面添加的)

PS:Object.create()的原理就是通过原型式继承实现的

【简写】上述原型式继承可如下简写

var person = new Person();
var sub1 = Object.create(person);
console.log(sub1.age);//10 继承了父类函数的属性

方法六:寄生式继承

步骤1:通过一个函数来模拟子类构造函数(关键点)

function content(obj){
    
    
    function F(){
    
    };
    F.prototype = obj;//继承了传入的参数
    return new F();//返回函数对象
}

步骤2:创建父类实例

var person = new Person();

步骤3:创建一个仅用于封装继承过程的函数,函数的内部以某种方式来继承父类

function subObject(obj){
    
    
    var sub = content(obj);//通过调用函数
    sub.name = 'roy';//以某种方式来增强这个对象
  	sub.sayHi=function(){
    
    
      console.log('hello');
    }
    return sub;//返回子类
}

步骤4:调用函数来模拟创建子类实例

var sub1 = subObject(person);

步骤5:

console.log(typeof subObject);//function
console.log(typeof sub1);//object
console.log(sub1.name);//'roy' 返回了个sub对象,继承了sub的属性
sub1.sayHi();	//'hello'

【分析】

  • 优点:
    • 为对象添加函数
  • 缺点:
    • 效率低
      • 【原因】不能做到函数复用

方法七:寄生组合继承

步骤1:创建父类,给父类添加方法

function Parent6() {
    
    
  this.name = 'parent6';
  this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    
    
  return this.name;
}

步骤2:创建子类,回调继承父类属性(关键点)

function Child6() {
    
    
  Parent6.call(this);	//继承服父类的属性
  this.friends = 'child6';
}

步骤3:借助函数,父子类关联(关键点)

function clone(parent, child) {
    
    
  // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
}

clone(Parent6, Child6);

PS:子类构造器等于父类对象

【证明】

console.log(child.prototype.constructor == parent); //true

步骤4:给子类追加方法

Child6.prototype.getFriends = function () {
    
    
  return this.friends;
}

步骤5:验证继承

let person6 = new Child6();
console.log(person6);	//输出:带有父类属性的对象>>>>>>证明:实现子类继承父类属性
console.log(person6.getName());	//输出:parent6>>>>证明:实现子类继承父类的方法
console.log(person6.getFriends());//输出:child6>>>证明:子类可以追加方法

文献参考

[1] 贪吃的猫,《JS继承》,稀土掘金,2021-04-29

[2] 小小操作手,《js继承》,稀土掘金,2020-06-13

猜你喜欢

转载自blog.csdn.net/weixin_45944495/article/details/130959368