基于原型的继承

在编程中,有时候我们想要对一些代码扩展一些东西。

如:

  • 有个user 对象及属性和方法。
  • 希望将 adminguest 作为基于 user 稍加修改的变体。
  • 想要重用 user 中的内容,而不是复制/重新实现它的方法,只是在这基础上构建一个新的对象。

原型继承便能够实现这样的需求!

Prototype

在JavaScript语言中,对象有个特殊的隐藏属性 Prototype ,这个属性要么为 null ,要么就是对另一个对象的引用。该对象被称为“原型”。

image.png

当想要从 object 中读取一些原本没有的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这种行为称为“原型继承”。

原型在内部是被隐藏的,但我们可以借由 __proto__ 找到 Prototype 如下:

  1. let animal = {
  2. eats: true
  3. }
  4. let rabbit = {
  5. jumps: true
  6. }
  7. rabbit.__protp__ = animal;//将 animal 设置为 rabbit 的原型。
  8. console.log(rabbit.eats);//true,rabbit里面没有eats,就顺着Prototype引用寻找
  9. console.log(rabbit.jumps);//true

在这我们可以说 animalrabbit 的原型,或者 rabbit 的原型是从 animal 继承而来的。

animal 有许多有用的属性和方法,那么它们将自动地变为在 rabbit 中可用。这种属性被称为“继承”。

注意:

  • __proto__ 的值可以是对象,也可以是 null 。其他类型都会被忽略。
  • 只能有一个 Prototype。一个对象不能从其他两个对象获得继承。
  • 引用不能形成闭环。如果我们试图在一个闭环中分配 __proto__,JavaScript 会抛出错误。

写入不使用原型

  • 原型仅用于读取属性。
  • 对于写入/删除操作可以直接在对象上进行。
  • 写入/删除操作不会影响原型对象,只会影响当前对象。
  1. let animal = {
  2. eats: true,
  3. walk() {
  4. /* rabbit 不会使用此方法 */
  5. }
  6. };
  7. let rabbit = {
  8. __proto__: animal
  9. };
  10. rabbit.walk = function() {
  11. console.log('输出成功')
  12. };
  13. rabbit.walk(); // 输出成功

上面的代码,rabbit.walk() 将立即在对象中找到该方法并执行,而无需使用原型。

this的值

例子:

  1. let user = {
  2. name: "John",
  3. surname: "Smith",
  4. set fullName(value) {
  5. [this.name, this.surname] = value.split(" ");
  6. },
  7. get fullName() {
  8. return `${this.name} ${this.surname}`;
  9. }
  10. };
  11. let admin = {
  12. __proto__: user,
  13. isAdmin: true
  14. };
  15. alert(admin.fullName); // John Smith (*)
  16. // setter triggers!
  17. admin.fullName = "Alice Cooper"; // (**)

上面的代码:

  • set fullName(value)this 的值是什么?
  • 属性 this.namethis.surname 被写在哪里:在 user 还是 admin

因为, this 根本就不受原型的影响。在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。

for…in 循环

for..in 循环也会迭代继承的属性。

如:

  1. let animal = {
  2. eats: true
  3. };
  4. let rabbit = {
  5. jumps: true,
  6. __proto__: animal
  7. };
  8. // Object.keys 只返回自己的 key
  9. alert(Object.keys(rabbit)); // jumps
  10. // for..in 会遍历自己以及继承的键
  11. for(let prop in rabbit) alert(prop); // jumps,然后是 eats

基于Class的继承

类继承是一个类扩展另一个类的一种方式。

extends关键字用来创建一个普通类或者内建对象的子类。 继承的.prototype必须是一个Object 或者 null

关键字extends

语法:

  1. class ChildClass extends ParentClass { ... }//ChildClass 继承 ParentClass

例子:

假设有class Animal

  1. class Animal {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. run() {
  6. console.log(`${this.name} 跑起来`);
  7. }
  8. stop() {
  9. console.log(`${this.name} 停下来`);
  10. }
  11. }
  12. let animal = new Animal("My animal");

图形化表示
image.png

创建一个子类 Rabbit :

  1. class Rabbit extends Animal {
  2. hide() {
  3. console.log(`${this.name} 隐藏!`);
  4. }
  5. }
  6. let rabbit = new Rabbit("Rabbit");
  7. rabbit.run(); // Rabbit跑起来
  8. rabbit.hide(); //Rabbit隐藏

Class Rabbit 的对象可以访问例如 rabbit.hide()Rabbit 的方法,还可以访问例如 rabbit.run()Animal 的方法。

图示:
image.png
在查找 rabbit.run 方法的过程:

  • 查找对象 rabbit(没有 run)。
  • 查找它的原型,即 Rabbit.prototype(有 hide,但没有 run)。
  • 查找它的原型,即(由于 extendsAnimal.prototype,在这儿找到了 run 方法。

Prototype 类似,class的 class Child extends ParentChild.prototype.__proto__=== Parent.prototype 差不多。

在子类调用父类方法

调用方式为使用super:
当不想重写父类方法,又想扩展父类的方法,就可以如下写发

  1. //重写Rabbit类
  2. class Rabbit extends Animal {
  3. hide() {
  4. super.run() //调用父类的run方法 //##########################这行
  5. console.log(`${this.name} 隐藏!`);
  6. }
  7. }
  8. let rabbit = new Rabbit("Rabbit");
  9. rabbit.hide() //Rabbit 跑起来 //Rabbit 隐藏!

同时需要注意,箭头函数是没有自己的super,如下:

  1. //重写Rabbit类
  2. //例子1:如果使用了箭头函数
  3. class Rabbit extends Animal {
  4. hide() {
  5. setTimeout(() => {
  6. //代码没有报错
  7. super.run() //调用父类的run方法 //##########################这行
  8. }, 1000)
  9. ....
  10. }
  11. }
  12. //例子2:如果使用了普通函数
  13. class Rabbit extends Animal {
  14. hide() {
  15. setTimeout(function(){
  16. //代码报错: 意料之外的 super
  17. super.run() //调用父类的run方法 //##########################这行
  18. }, 1000)
  19. ....
  20. }
  21. }

在子类获取父类的constroctor

同样使用super

  1. //重写Rabbit类
  2. class Rabbit extends Animal {
  3. constroctor(name, sex) {
  4. super(name) //特别注意,super必须在this的上面 //##########################这行
  5. this.sex = sex
  6. }
  7. }