js中的继承详解

js中的继承

假设我们有一个Animal类,我们想构造Cat类,Cat类可以继承Animal类的属性和方法。以这个场景为列,我来讲一讲我所理解的js的继承。

  1. 构造继承
function Animal(name){
  this.name = name;
  this.age = 15;
}

function Cat(name){
  Animal.call(this, name);
  this.catName = 'cat';
}

let o1 = new Cat('test1');
console.log(o1);
//Cat {name: "test1", age: 15, catName: "cat"}

这就是构造继承,在子类的构造函数中,调用父类的构造函数,这样父类构造函数中的属性就出现在了子类的构造函数中。
缺点:子类无法继承父类原型上的方法。

  Animal.prototype.say = function(){
     console.log(this.name)
  }
  o1.say;//undefined
  1. 原型继承
  function Cat2(name){
    this.catName = name;
  }
  Cat2.prototype = new Animal();
  let o2 = new Cat2('test2');
  console.log(o2);
  // Cat2 {catName: "test2"}
  o2.name;//undefined
  o2.age;//15
  o2.say;
  //ƒ (){
  //  console.log(this.name)
  //}

这种方法的缺点是无法进行父类传参初始化属性的继承,而且继承的父类的属性是所有子类共享的new Animal()这个对象实例上的属性,这就会导致如果更改了该对象实例的属性,那么这个影响就是所有子类实例共享的。
我们对父类做出如下更改

  function Animal(name){
    this.name = name;
    this.age = 15;
    this.friend = [1,2,3];
  }

  function Cat21(name){
    this.catName = name;
  }

  Cat21.prototype = new Animal();
  
  let o3 = new Cat21('o3');
  let o4 = new Cat21('o4');
  console.log('o3.age',o3.age);
  console.log('o4.age',o4.age);
  console.log('o3.friend',o3.friend);
  console.log('o4.friend',o4.friend);
  
  //更改数组friend
  o3.friend.push(4);
  console.log('o3.friend',o3.friend);
  console.log('o4.friend',o4.friend);
  
  //更改属性age
  o3.age = 19;
  console.log('o3.age', o3.age);
  console.log('o4.age', o4.age);

输出如下

    o3.age 15
    o4.age 15
    o3.friend (3) [1, 2, 3]
    o4.friend (3) [1, 2, 3]
    o3.friend (4) [1, 2, 3, 4]
    o4.friend (4) [1, 2, 3, 4]
    o3.age 19
    o4.age 15

我们可以发现更改friend时,这个更改在子类实例上都发生了改变,而更改age时,只在更改的实例上发生了变化。
我们来看看o3和o4
avatar
可以发现o3上的age属性是直接在实例上的,而o3和o4的实例本身都是没有friend属性的。
这是为什么呢?
这是因为在查找对象的属性和方法时,是沿着原型链进行查找的,而你更改属性时,如果这个属性不是一个引用类型,是会直接为实例对象本身添加一个相应的属性,如果是引用类型,是会改变所引用对象指向的内容的。 而原型链上的方法,是等同于非引用类型的属性的。
因此,如果我们想要修改age,让所有实例共享修改后的结果,我们可以这么修改
o3.__proto__.age = 99;
当然,这个前提是子类实例上还没有新增age属性。

  1. 构造 + 原型继承
     function Animal(name){
       this.name = name;
       this.age = 15;
       this.friend = [1,2,3];
     }
    
     Animal.prototype.say = function(){
         console.log(this.name);
     }
     function Cat3(name){
       Animal.call(this, name);
       this.catName = 'test3';
     }
     Cat3.prototype = new Animal();
    
     let o31 = new Cat3('o31');
     let o32 = new Cat3('o32');
    
     console.log('o31', o31);
     console.log('o32', o32);
     console.log('o31.friend', o31.friend);
     console.log('o32.friend', o32.friend);
    
     o31.friend.push(4);
     console.log('o31.friend', o31.friend);
     console.log('o32.friend', o32.friend);
    
     o31.say();
    

得到的输出如下:
avatar

可以发现父类的属性和方法子类实例都可以继承,但是还是有一个问题,那就是父类的构造函数多执行了一次,而这个多余的操作是不必要的。

  1. 原型+构造+优化1
    function Cat4(name){
      Animal.call(this, name);
      this.catName = name;
    }
    
    Cat4.prototype = Object.create(Animal.prototype);
    
     let o41 = new Cat4('o41');
     let o42 = new Cat4('o42');
    
     console.log('o41', o41);
     console.log('o42', o42);
     console.log('o41.friend', o41.friend);
     console.log('o42.friend', o42.friend);
    
     o41.friend.push(4);
     console.log('o41.friend', o41.friend);
     console.log('o42.friend', o42.friend);
    
     o41.say();
    

如此,我们就少进行了一个父类构造函数的执行,但是这还是有问题的

  o41 instanceof Cat4; //true
  o41 instanceof Animal; //true
  o41.constructor;//
  //ƒ Animal(name){
  //  this.name = name;
  //  this.age = 15;
  //  this.friend = [1,2,3];
  //}

也就是无法通过instanceof来确认实例对象是由父类构造还是子类构造。

  1. 原型+构造函数+优化2
    由于instanceof的本质就是在原型链上进行constructor属性的查找 ,我们可以做如下优化
 function Cat5(name){
   Animal.call(this, name);
   this.catName = name;
 }

  Cat5.prototype = Object.create(Animal.prototype);
  Cat5.prototype.constructor = Cat5;

  let o51 = new Cat5('o51');
  o51.constructor;
  //ƒ Cat5(name){
  //Animal.call(this, name);
  //this.catName = name;
  //}

以上是个人总结的继承相关的知识点,欢迎老铁们在评论区进行补充。

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 JavaScript 继承是一个非常常见的概念。ES6 引入了 Class 语法糖,让继承更加易于理解和实现。在 Class ,我们可以使用 extends 关键字来创建一个子类,使其继承父类的属性和方法。 下面我们来详细了解一下如何在 JavaScript 使用 extends 实现继承。 ### 基础语法 首先,我们需要定义一个父类。在 ES6 ,我们可以使用 Class 来定义一个类。例如: ```javascript class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } ``` 这个 Animal 类有一个构造函数和一个 speak 方法。构造函数会在创建实例时被调用,而 speak 方法则可以让动物发出一些声音。 接下来,我们来创建一个子类。使用 extends 关键字来创建子类,并使用 super() 方法调用父类的构造函数。例如: ```javascript class Dog extends Animal { constructor(name) { super(name); } speak() { console.log(this.name + ' barks.'); } } ``` 这个 Dog 类继承了 Animal 类,并覆盖了其 speak 方法。在构造函数,我们通过 super() 方法来调用父类的构造函数,并将传递的参数传递给它。 现在,我们可以创建一个 Dog 的实例,并调用其 speak 方法: ```javascript let d = new Dog('Mitzie'); d.speak(); // Mitzie barks. ``` ### 继承父类的方法 当一个子类继承了一个父类时,它会继承父类的属性和方法。例如,在上面的例子,Dog 类继承了 Animal 类,因此它继承了 Animal 类的 speak 方法。 当我们调用子类的方法时,如果子类没有实现该方法,它会自动调用父类的方法。例如,在上面的例子,如果我们不覆盖 Dog 类的 speak 方法,它将调用 Animal 类的 speak 方法。 ### 覆盖父类的方法 如果一个子类需要覆盖父类的方法,我们可以在子类重新定义该方法。例如,在 Dog 类,我们覆盖了 Animal 类的 speak 方法,使其输出“barks”而不是“makes a noise”。 ### 调用父类的方法 有时候,我们需要在子类调用父类的方法。我们可以使用 super 关键字来调用父类的方法。例如,在 Dog 类,我们可以通过调用 super.speak() 来调用 Animal 类的 speak 方法。 ### 总结 继承是一个非常常见的概念,也是面向对象编程的重要概念之一。在 JavaScript ,我们可以使用 extends 关键字来实现继承。通过继承,子类可以继承父类的属性和方法,也可以覆盖父类的方法,并且可以调用父类的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值