JavaScript类的继承全面示例讲解

 更新时间:2022年08月08日 09:20:08   作者:夏安  
在 ES5 中,类的继承可以有多种方式,然而过多的选择有时反而会成为障碍,ES6 统了类继承的写法,避免开发者在不同写法的细节之中过多纠缠,但在介绍新方法之前,还是有必要先回顾下ES5中类的继承方式

1. ES5 中的继承

首先假设我们有一个父类 Person,并且在类的内部和原型链上各定义了一个方法:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greed = function() {
    console.log('Hello, I am ', this.name);
  }
}
Person.prototype.getInfo = function() {
  return this.name + ',' + this.age;
}

1.1 修改原型链

这是最普遍的继承做法,通过将子类的 prototype 指向父类的实例来实现:

function Student() {
}
Student.prototype = new Person();
Student.prototype.name = '夏安';
Student.prototype.age = 18;
const stud = new Student();
stud.getInfo();

在这种继承方式中,stud 对象既是子类的实例,也是父类的实例。然而也有缺点,在子类的构造函数中无法通过传递参数对父类继承的属性值进行修改,只能通过修改 prototype 的方式进行修改。

1.2 调用父类的构造函数

function Student(name, age, sex) {
  Person.call(this);
  this.name = name;
  this.age = age;
  this.sex = sex;
}
const stud = new Student('夏安', 18, 'male');
stud.greed(); // Hello, I am  夏安
stud.getInfo(); // Error

这种方式避免了原型链继承的缺点,直接在子类中调用父类的构造函数,在这种情况下,stud 对象只是子类的实例,不是父类的实例,而且只能调用父类实例中定义的方法,不能调用父类原型上定义的方法。

1.3 组合继承

这种继承方式是前面两种继承方式的结合体。

function Student(name, age, sex) {
  Person.call(this);
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Student.prototype = new Person();
const stud = new Student('夏安', 18, 'male');
stud.greed();
stud.getInfo();

这种方式结合上面两种继承方式的优点,也是 Node 源码中标准的继承方式。唯一的问题是调用了父类的构造函数两次,分别是在设置子类的 prototype 和实例化子类新对象时调用的,这造成了一定的内存浪费。

1.4 原型继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

function createObject(o) {
  // 创建临时类
  function f() {
  }
  // 修改类的原型为o, 于是f的实例都将继承o上的方法
  f.prototype = o
  return new f()
}

这不就是Object.create吗? createObject对传入其中的对象执行了一次浅复制,将构造函数f的原型直接指向传入的对象。同样也没有解决修改原型链的缺点。

1.5 寄生式继承

在原型式继承的基础上,增强对象,返回构造函数,或者说使用原型继承对一个目标对象进行浅复制,增强这个浅复制的能力。

function Student() {
  const clone = Object.create(Person);
  clone.name = '夏安';
  return clone;
}

同样也可以和之前的方法进行组合,这里就不再赘述。

2. ES6 中的继承

在 ES6 中可以直接使用 extends 关键字来实现继承,形式上更加简洁。我们前面也提到了,ES6 对 Class 的改进就是为了避免开发者过多地在语法细节中纠缠。

我们设计一个 student 类来继承之前定义的 person 类。

class Student extends Person {
  constructor(name, age, sex) {
    super(name, age);
    this.sex = sex;
  }
  getInfo() {
    return super.getInfo() + ',' + this.sex;
  }
  print() {
    const info = this.getInfo();
    console.log(info);
  }
}
const student = new Student('夏安', 18, 'male');
student.print(); // 夏安,18,male

在代码中我们定义了 Student 类,在它的构造方法中调用了 super 方法,该方法调用了父类的构造函数,并将父类中的属性绑定到子类上。

super 方法可以带参数,表示哪些父类的属性会被继承,在代码中,子类使用 super 继承了 Person 类的 name 以及 age 属性,同时又声明了一个 sex 属性。

在子类中,super 方法是必须要调用的,原因在于子类本身没有自身的 this 对象,必须通过 super 方法拿到父类的 this 对象,可以在 super 函数调用前尝试打印子类的 this,代码会出现未定义的错误。

如果子类没有定义 constructor 方法,那么在默认的构造方法内部自动调用 super 方法,并继承父类的全部属性。

同时,在子类的构造方法中,必须先调用 super 方法,然后才能调用 this 关键字声明其他的属性(如果存在的话),这同样是因为在 super 没有调用之前,子类还没有 this 这一缘故。

class Student extends Person {
  constructor(name, age, sex) {
    console.log(this); // Error
    super(name, age);
    this.sex = sex;
  }
}

除了用在子类的构造函数中,super 还可以用在类方法中来引用父类的方法。

class Student extends Person {
  constructor(name, age, sex) {
    super(name, age);
    this.sex = sex;
  }
  print() {
    const info = super.getInfo(); // 调用父类方法
    console.log(info);
  }
}

值得注意的是,super 只能调用父类方法,而不能调用父类的属性,因为方法是定义在原型链上的,属性则是定义在类的内部(就像组合继承那样,属性定义在类的内部)。

class Student extends Person {
  constructor(name, age, sex) {
    super(name, age);
    this.sex = sex;
  }
  getInfo() {
    return super.name; // undefinded
  }
}

此外,当子类的函数被调用时,使用的均为子类的 this(修改父类的 this 得来),即使使用 super 来调用父类的方法,使用的仍然是子类的 this

class Person {
  constructor() {
    this.name = '夏安';
    this.sex = 'male';
  }
  getInfo() {
    return this.name + ',' + this.sex;
  }
}
class Student extends Person {
  constructor() {
    super();
    this.name = '安夏';
    this.sex = 'Female';
  }
  print() {
    return super.getInfo();
  }
}
const student = new Student();
console.log(student.print()); // 安夏,Female
console.log(student.getInfo()); // 安夏,Female

在上面的例子中,super 调用了父类的方法,输出的内容却是子类的属性,说明 super 绑定了子类的 this

到此这篇关于JavaScript类的继承全面示例讲解的文章就介绍到这了,更多相关JS 类的继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解JavaScript数组过滤相同元素的5种方法

    详解JavaScript数组过滤相同元素的5种方法

    本篇文章主要介绍了详解JavaScript数组过滤相同元素的5种方法,详细的介绍了5种实用方法,非常具有实用价值,需要的朋友可以参考下
    2017-05-05
  • JS实现的新闻列表自动滚动效果示例

    JS实现的新闻列表自动滚动效果示例

    这篇文章主要介绍了JS实现的新闻列表自动滚动效果,涉及javascript基于时间函数的页面元素属性动态变换相关操作技巧,需要的朋友可以参考下
    2019-01-01
  • 整理一些最近经常遇到的前端面试题

    整理一些最近经常遇到的前端面试题

    这篇文章主要给大家整理了一些在面试的时候经常遇到的前端面试题,本文总结的这三十道面试题都是一些最近碰到的一些较为有用的前端题目,相信会对大家面试具有一定的参考价值,需要的朋友可以参考学习,下面来一起看看吧。
    2017-04-04
  • JS实现的在线调色板实例(附demo源码下载)

    JS实现的在线调色板实例(附demo源码下载)

    这篇文章主要介绍了JS实现的在线调色板,可实现响应鼠标点击动态改变调色板颜色的功能,涉及JavaScript针对页面元素属性的动态操作与计算技巧,并附带demo源码供读者下载参考,需要的朋友可以参考下
    2016-03-03
  • js获取地址栏中传递的参数(两种方法)

    js获取地址栏中传递的参数(两种方法)

    本文主要介绍了如何获取地址栏中的参数的两种方法。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • javascript 构建一个xmlhttp对象池合理创建和使用xmlhttp对象

    javascript 构建一个xmlhttp对象池合理创建和使用xmlhttp对象

    在我的这篇旧文里曾经发布了一个简单的ajax操作类。我们发现,在旧文里创建xmlhttp对象的时候,每次都要new一个对象。而我们都知道new一个对象的开销是很大的。
    2010-01-01
  • JS实现的文件拖拽上传功能示例

    JS实现的文件拖拽上传功能示例

    这篇文章主要介绍了JS实现的文件拖拽上传功能,涉及javascript事件触发、页面元素属性动态修改等相关操作技巧,需要的朋友可以参考下
    2018-05-05
  • javascript改变position值实现菜单滚动至顶部后固定

    javascript改变position值实现菜单滚动至顶部后固定

    现在很多网站都有这样的一个效果,当页面滚动到一定高度时,菜单栏会固定在页面顶部;该效果在 ie6 下不支持,因为ie6不支持 position:fixed,效果很不错,感兴趣的朋友可以了解下啊
    2013-01-01
  • 基于JavaScript实现十五拼图代码实例

    基于JavaScript实现十五拼图代码实例

    这篇文章主要介绍了基于JavaScript实现十五拼图代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去

    JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去

    这篇文章主要介绍了JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07

最新评论