彻底征服JavaScript继承

本文详细介绍了JavaScript中的六种继承方式:类式继承、构造函数继承、组合继承、寄生组合式继承、extends继承等,每种方式都附带示例代码,便于理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章主要介绍JavaScript实现继承的方式:

  • 类式继承
  • 构造函数继承
  • 组合继承
  • 寄生组合式继承
  • extends继承

1、类式继承

简单的类式继承:

// 声明父类function Animal() {  this.name = 'animal';  this.type = ['pig', 'cat'];}
// 为父类添加共有方法Animal.prototype.greet = function(sound) {  console.log(sound);}
// 声明子类function Dog() {  this.name = 'dog';}
// 继承父类Dog.prototype = new Animal();
var dog = new Dog();dog.greet('汪汪');  //  "汪汪"console.log(dog.type); // ["pig", "cat"]复制代码

在上面的代码中,我们创建了两个类Animal和Dog,而且给Animal.prototype原型上添加了一个greet共有方法,然后通过new命令实例化一个Animal,并且赋值给Dog.prototype原型。

原理说明:在实例化一个类时,新创建的对象复制了父类的构造函数内的属性与方法并且将原型__proto__指向了父类的原型对象,这样就拥有了父类的原型对象上的属性与方法。

不过,通过类式继承方式,有两个缺点。

第一个是引用缺陷:

dog.type.push('dog');var dog2 = new Dog();console.log(dog2.type);  // ["dog", "cat", "dog"]复制代码

通过上面的执行结果,我们看到当通过dog实例对象修改继承自Animal中的数组type(引用类型)时,另外一个新创建的实例dog2也会受到影响。

第二个是我们无法为不同的实例初始化继承来的属性,我们可以修改一下上面的例子:

function Animal(color) {  this.color = color;}...Dog.prototype = new Animal('白色');...console.log(dog.color); // "白色"console.log(do2.color); // "白色"复制代码

通过上面的代码可以看到,我们无法为不同dog赋值不同的颜色,所有dog只能同一种颜色。


2、构造函数继承

构造函数继承方式可以避免类式继承的缺陷:

// 声明父类function Animal(color) {  this.name = 'animal';  this.type = ['pig','cat'];  this.color = color;}
// 添加共有方法Animal.prototype.greet = function(sound) {  console.log(sound);}
// 声明子类function Dog(color) {  Animal.apply(this, arguments);}
var dog = new Dog('白色');var dog2 = new Dog('黑色');
dog.type.push('dog');console.log(dog.color);  // "白色"console.log(dog.type);  // ["pig", "cat", "dog"]
console.log(dog2.type);  // ["pig", "cat"]console.log(dog2.color);  // "黑色"复制代码

首先要知道apply方法的运用,它是可以更改函数的作用域,所以在上面的例子中,我们在Dog子类中调用这个方法也就是将Dog子类的变量在父类中执行一遍,这样子类就拥有了父类中的共有属性和方法。

相关文章:JS中的call、apply、bind方法

但是,构造函数继承也是有缺陷的,那就是我们无法获取到父类的共有方法,也就是通过原型prototype绑定的方法:

dog.greet();  // Uncaught TypeError: dog.greet is not a function复制代码

3、组合继承

组合继承其实就是将类式继承和构造函数继承组合在一起:

// 声明父类   function Animal(color) {      this.name = 'animal';      this.type = ['pig','cat'];      this.color = color;   }     
// 添加共有方法  Animal.prototype.greet = function(sound) {      console.log(sound);   }     
// 声明子类   function Dog(color) {   // 构造函数继承      Animal.apply(this, arguments);   }   // 类式继承Dog.prototype = new Animal();   
var dog = new Dog('白色');   var dog2 = new Dog('黑色');     
dog.type.push('dog');   console.log(dog.color); // "白色"console.log(dog.type);  // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]console.log(dog2.color);  // "黑色"dog.greet('汪汪');  // "汪汪"
复制代码

在上面的例子中,我们在子类构造函数中执行父类构造函数,在子类原型上实例化父类,这就是组合继承了,可以看到它综合了类式继承和构造函数继承的优点,同时去除了缺陷。

可能你会奇怪为什么组合式继承可以去除类式继承中的引用缺陷?其实这是由于原型链来决定的,由于JavaScript引擎在访问对象的属性时,会先在对象本身中查找,如果没有找到,才会去原型链中查找,如果找到,则返回值,如果整个原型链中都没有找到这个属性,则返回undefined。

也就是说,我们访问到的引用类型(比如上面的type)其实是通过apply复制到子类中的,所以不会发生共享。

这种组合继承也是有点小缺陷的,那就是它调用了两次父类的构造函数。

5、寄生组合式继承

寄生组合式继承强化的部分就是在组合继承的基础上减少一次多余的调用父类的构造函数:

 function Animal(color) {  this.color = color;  this.name = 'animal';  this.type = ['pig', 'cat'];}
Animal.prototype.greet = function(sound) {  console.log(sound);}
function Dog(color) {  Animal.apply(this, arguments);  this.name = 'dog';}
/* 注意下面两行 */Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {  console.log(this.name);}
var dog = new Dog('白色');   var dog2 = new Dog('黑色');     
dog.type.push('dog');   console.log(dog.color);   // "白色"console.log(dog.type);   // ["pig", "cat", "dog"]
console.log(dog2.type);  // ["pig", "cat"]console.log(dog2.color);  // "黑色"dog.greet('汪汪');  //  "汪汪"复制代码

在上面的例子中,我们并不像构造函数继承一样直接将父类Animal的一个实例赋值给Dog.prototype,而是使用Object.create()进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。

Object.create()的浅拷贝的作用类式下面的函数:

function create(obj) {  function F() {};  F.prototype = obj;  return new F();}复制代码

这里还需注意一点,由于对Animal的原型进行了拷贝后赋给Dog.prototype,因此Dog.prototype上的constructor属性也被重写了,所以我们要修复这一个问题:

Dog.prototype.constructor = Dog;复制代码

6、extends继承

Class和extends是在ES6中新增的,Class用来创建一个类,extends用来实现继承:

class Animal {     constructor(color) {       this.color = color;     }     greet(sound) {       console.log(sound);     }  }   
class Dog extends Animal {     constructor(color) {       super(color);       this.color = color;     }  }   
let dog = new Dog('黑色');  dog.greet('汪汪');  // "汪汪"console.log(dog.color); // "黑色"
复制代码

在上面的代码中,创建了父类Animal,然后Dog子类继承父类,两个类中都有一个constructor构造方法,实质就是构造函数Animal和Dog。

不知道你有没有注意到一点,我在子类的构造方法中调用了super方法,它表示父类的构造函数,用来新建父类的this对象。

注意:

      子类必须在constructor方法中先调用super方法才可以使用this ,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

       ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

      在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值