JavaScript继承详讲

在JavaScript中,继承是非常核心重要的,今天我要和大家分享一下JavaScript中的继承都有那些,以及对应的一些优缺点。

1. 原型链继承

原型链继承是JavaScript中实现继承的一种基本方式, 它利用原型机制让一个对象能够访问另一个对象的属性和方法。

原型链継承的核心思想是: 将父类的实例作为子类的原型对象,这样,子类实例就可以通过原型链访问父类的属性和方法

示例:

js 复制代码
function Animal(name) {
    this.name = name || 'Animal';
    this.colors = ['red', 'blue'];
}

Animal.prototype.sayName = function() {
    console.log('My name is ' + this.name);
};

function Cat() {
    this.type = 'cat';
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function() {
    console.log('Meow!');

const cat1 = new Cat();
cat1.sayName();
cat1.meow();   

为什么constructor要进行修复:

js 复制代码
const cat = new Cat();
console.log(cat.constructor === Animal); // true - 错误!
console.log(cat.constructor === Cat);    // false - 不正确

1.1 原型链结构分析

cat1 (Cat实例)

├── 自身属性: type = 'cat'

└── proto → Cat.prototype (Animal实例)

├── constructor: Cat

├── meow: function

└── proto → Animal.prototype

├── constructor: Animal

├── sayName: function

└── proto → Object.prototype

1.2原型链继承的特点

优点

  1. 实现简单:代码简洁,易于理解
  2. 纯粹的继承关系:子类是父类的实例,也是父类的子类的实例
  3. 方法复用:父类方法可以被所有子类实例共享

缺点

  1. 引用类型属性共享问题
js 复制代码
const cat1 = new Cat();
const cat2 = new Cat();

cat1.colors.push('green');
console.log(cat2.colors); // ['red', 'blue', 'green'] - 被影响了!
  1. 无法向父类构造函数传参
js 复制代码
// 无法在创建Cat实例时给Animal传参
const cat = new Cat('Tom'); // 这里的参数无法传递给Animal
  1. 无法实现多继承
js 复制代码
Child.prototype = new Parent1(); 
// 无法同时设置:Child.prototype = new Parent2();

验证继承关系的方法 :

js 复制代码
const cat = new Cat();

console.log(cat instanceof Cat);     // true
console.log(cat instanceof Animal);  // true
console.log(cat instanceof Object);  // true

console.log(Cat.prototype.isPrototypeOf(cat));     // true
console.log(Animal.prototype.isPrototypeOf(cat));  // true

2. 构造函数继承

构造函数继承(Constructor Inheritance)是JavaScript中实现继承的重要方式之一,它通过在子类构造函数中调用父类构造函数来实现继承。

在子类构造函数中,使用call()apply()方法调用父类构造函数,将父类的属性和方法绑定到子类实例上:

javascript 复制代码
function Child() {
    Parent.call(this); // 关键步骤
    // 子类自己的属性初始化
}

2.1示例

javascript 复制代码
function Animal(name) {
    this.name = name || 'Animal';
    this.colors = ['red', 'blue'];
    this.sayName = function() {
        console.log('My name is ' + this.name);
    };
}

function Cat(name) {
    // 调用父类构造函数,继承属性
    Animal.call(this, name);
    this.type = 'cat';
    this.meow = function() {
        console.log('Meow!');
    };
}

const cat1 = new Cat('Tom');
cat1.sayName(); 
cat1.meow(); 

2.2构造函数继承的特点

2.2.1优点
  1. 解决引用类型共享问题
js 复制代码
  const cat1 = new Cat('Tom');
  const cat2 = new Cat('Jerry');
    
  cat1.colors.push('green');
  console.log(cat1.colors); // ['red', 'blue', 'green']
  console.log(cat2.colors); // ['red', 'blue'] - 互不影响
  1. 支持向父类传递参数
js 复制代码
  function Cat(name) {
      Animal.call(this, name); // 传递name参数
      // ...
  }
  1. 可实现多继承
javascript 复制代码
  function FlyingAnimal() {
      this.canFly = true;
      this.fly = function() {
          console.log('Flying!');
      };
  }
  
  function Cat(name) {
      Animal.call(this, name);
      FlyingAnimal.call(this); // 多继承
      // ...
  }
2.2.2缺点
  1. 无法继承父类原型上的方法
js 复制代码
    // 将方法定义在原型上
    Animal.prototype.sayName = function() {
        console.log('My name is ' + this.name);
    };
    
    const cat = new Cat('Tom');
    cat.sayName(); // TypeError: cat.sayName is not a function
  1. 方法无法复用

每个实例都会创建自己的方法副本:

javascript 复制代码
  const cat1 = new Cat();
  const cat2 = new Cat();
  console.log(cat1.sayName === cat2.sayName); // false
  1. 无法使用instanceof检查继承关系
javascript 复制代码
  console.log(cat1 instanceof Animal); // false

3.组合继承详解:JavaScript 经典继承模式

组合继承(Combination Inheritance)是 JavaScript 中最常用的继承模式,它结合了原型链继承构造函数继承的优点,同时规避了它们的缺点。

3.1核心概念:

组合继承的核心思想是:

  1. 使用构造函数继承来继承父类的实例属性
  2. 使用原型链继承来继承父类的原型方法

3.2实现步骤:

javascript 复制代码
function Animal(name) {
    this.name = name || 'Animal';
    this.colors = ['red', 'blue'];
}
Animal.prototype.sayName = function() {
    console.log('My name is ' + this.name);
};

function Cat(name, age) {
    Animal.call(this, name); 
    this.age = age || 1;
    this.type = 'cat';
}

Cat.prototype = new Animal(); 
// 7. 修复constructor指向
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function() {
    console.log('Meow! I am ' + this.age + ' years old.');
};

3.3组合继承的优势:

3.3.1. 解决引用类型共享问题
javascript 复制代码
const cat1 = new Cat('Tom', 2);
const cat2 = new Cat('Jerry', 1);

cat1.colors.push('green');
console.log(cat1.colors); // ['red', 'blue', 'green']
console.log(cat2.colors); // ['red', 'blue'] - 互不影响
3.3.2. 支持向父类传递参数
javascript 复制代码
const cat = new Cat('Garfield', 5);
cat.sayName(); // "My name is Garfield"
3.3.3. 方法复用
javascript 复制代码
console.log(cat1.sayName === cat2.sayName); // true (共享原型方法)
3.3.4. 正确的继承关系检查
javascript 复制代码
console.log(cat1 instanceof Cat);    // true
console.log(cat1 instanceof Animal); // true
console.log(cat1.constructor === Cat); // true

3.4组合继承的缺点

3.4.1. 父类构造函数被调用两次
javascript 复制代码
function Animal(name) {
    console.log('Animal constructor called');
    // ...
}

function Cat(name) {
    Animal.call(this, name); // 第一次调用
}

Cat.prototype = new Animal(); // 第二次调用
3.4.2. 原型对象上存在冗余属性
javascript 复制代码
const cat = new Cat('Tom');
console.log(cat); // 实例自身有name和colors
console.log(Object.getPrototypeOf(cat)); // 原型上也有name和colors

4.原型式继承详解:纯粹的基于对象的继承模式

原型式继承(Prototypal Inheritance)是一种不涉及构造函数的继承方式,它直接基于现有对象创建新对象,是JavaScript中最纯粹的面向对象继承方式。

原型式继承不关注构造函数,而是关注对象之间的关系。它的核心是:创建一个新对象,并将一个已有的对象作为这个新对象的原型

4.1示例:

js 复制代码
const animalPrototype = {
  isAlive: true,
  colors: ['black', 'white'],
  sayHello: function() {
    console.log(`Hello, I have ${this.colors.join(' and ')} fur.`);
  }
};

const cat1 = Object.create(animalPrototype);

cat1.name = 'Tom';
cat1.age = 2;

cat1.sayHello();

console.log(cat1.isAlive);

console.log(cat1.name); 

4.2 原型式继承的特点:

4.2.1优点
  1. 更符合原型本质:它完美地体现了 JavaScript 的原型思想------任何对象都可以是另一个对象的原型,而无需"类"作为中介。

  2. 语法简单直接 :一行 Object.create() 即可建立继承关系,非常清晰。

  3. 可以继承普通对象:继承的来源可以是一个简单的字面量对象,而不仅限于构造函数的实例。

4.2.2缺点
  1. 引用类型属性共享问题:这是它与"原型链继承"共有的致命弱点。所有继承自同一个原型的实例,都会共享原型上的引用类型属性。如果一个实例修改了该属性,会影响到所有其他实例。

    js 复制代码
    const cat2 = Object.create(animalPrototype);
    
    cat1.colors.push('yellow');
    
    // cat2 的 colors 属性也受到了影响
    console.log(cat2.colors); // 输出: ['black', 'white', 'yellow']
  2. 属性初始化复杂 :由于没有构造函数来统一处理初始化,每创建一个新对象后,都需要手动为其添加新的实例属性(如 cat1.name = 'Tom'),如果属性多,会比较繁琐。

5.寄生式继承详解:增强型原型继承

寄生式继承(Parasitic Inheritance)是 JavaScript 中一种特殊的继承模式,它基于原型式继承,通过"寄生"的方式增强对象的功能。

5.1核心概念:

寄生式继承的核心思想是:

  1. 基于现有对象创建新对象(使用原型式继承)
  2. 增强新对象的功能(添加新属性和方法)
  3. 返回增强后的对象

5.2基本实现:

js 复制代码
function createEnhancedObject(original) {
    const clone = Object.create(original);
    clone.sayHello = function() {
        console.log('Hello, I am ' + this.name);
    };
    
    return clone;
}
// 使用示例
const person = {
    name: 'John',
    age: 30
};

const enhancedPerson = createEnhancedObject(person);
enhancedPerson.sayHello();  
console.log(enhancedPerson.age);  

5.3寄生式继承的优缺点:

5.3.1 优点

  1. 简单灵活:不需要定义构造函数
  2. 功能增强:可以自由添加新功能
  3. 对象定制:每个对象可以有独特的增强
  4. 兼容性好:适用于各种JavaScript环境

5.3.2 缺点

  1. 方法无法复用
javascript 复制代码
  const obj1 = createEnhancedObject({});
  const obj2 = createEnhancedObject({});
  console.log(obj1.sayHello === obj2.sayHello); // false
  1. 引用类型共享问题
javascript 复制代码
  const original = { list: [1] };
  const obj1 = createEnhancedObject(original);
  obj1.list.push(2);
  const obj2 = createEnhancedObject(original);
  console.log(obj2.list); // [1, 2]
  1. 无法使用instanceof检查类型
javascript 复制代码
  console.log(enhancedPerson instanceof Object); // true
  // 但无法检查是否是特定"类型"

6.寄生组合式继承详解

寄生组合式继承(Parasitic Combination Inheritance)是 JavaScript 中最完善的继承模式,它结合了构造函数继承原型链继承的优点,同时完美规避了它们的缺点。

6.1核心概念:

寄生组合式继承的核心思想是:

  1. 使用构造函数继承来继承父类的实例属性
  2. 使用寄生式继承来继承父类的原型方法
  3. 避免调用父类构造函数两次(解决组合继承的主要缺点)

6.2 示例:

javascript 复制代码
function Animal(name) {
    this.name = name || 'Animal';
    this.colors = ['red', 'blue'];
}

Animal.prototype.sayName = function() {
    console.log('My name is ' + this.name);
};

function Cat(name, age) {
    Animal.call(this, name); 
    
    this.age = age || 1;
    this.type = 'cat';
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function() {
    console.log('Meow! I am ' + this.age + ' years old.');
};

6.3 组合继承的优势:

6.3.1. 解决引用类型共享问题

javascript 复制代码
const cat1 = new Cat('Tom', 2);
const cat2 = new Cat('Jerry', 1);

cat1.colors.push('green');
console.log(cat1.colors); // ['red', 'blue', 'green']
console.log(cat2.colors); // ['red', 'blue'] - 互不影响

6.3.2. 支持向父类传递参数

javascript 复制代码
const cat = new Cat('Garfield', 5);
cat.sayName(); // "My name is Garfield"

6.3.3. 方法复用

javascript 复制代码
console.log(cat1.sayName === cat2.sayName); // true (共享原型方法)

6.3.4. 正确的继承关系检查

javascript 复制代码
console.log(cat1 instanceof Cat);    // true
console.log(cat1 instanceof Animal); // true
console.log(cat1.constructor === Cat); // true

6.4 组合继承的缺点:

6.4.1. 父类构造函数被调用两次

javascript 复制代码
function Animal(name) {
    console.log('Animal constructor called');
    // ...
}

function Cat(name) {
    Animal.call(this, name); // 第一次调用
}

Cat.prototype = new Animal(); // 第二次调用

6.4.2. 原型对象上存在冗余属性

javascript 复制代码
const cat = new Cat('Tom');
console.log(cat); // 实例自身有name和colors
console.log(Object.getPrototypeOf(cat)); // 原型上也有name和colors

7.Class継承

class 语法是 JavaScript 现有原型继承模型的"语法糖"(Syntactic Sugar)。它并没有引入新的继承机制,而是提供了一套更清晰、更简洁的语法来操作原型和构造函数。

ES6 Class 继承的核心思想是:使用 extends 关键字来实现类之间的继承关系。它极大地简化了继承的写法,并解决了传统原型链继承的一些痛点。

7.1 示例:

js 复制代码
class Animal {
    constructor(name) {
        this.name = name || 'Animal';
        this.colors = ['red', 'blue'];
    }
    sayName() {
        console.log('My name is ' + this.name);
    }
}
class Cat extends Animal {
    constructor(name, type) {
        super(name); 
        this.type = type || 'cat';
    }
    meow() {
        console.log('Meow!');
    }
}
const cat1 = new Cat('Tom', '橘猫');
cat1.sayName(); 
cat1.meow();  
console.log(cat1.name); 
console.log(cat1.type); 

7.2Class 继承的特点:

7.2.1 优点:
  1. 语法清晰,符合直觉classextends 的写法让代码更易于阅读和理解,对有其他面向对象语言(如Java、C++、Python)背景的开发者非常友好。

  2. 解决了原型链继承的核心痛点

    • 完美解决引用属性共享问题 :父类的实例属性(如 this.colors)是在子类 constructor 中通过 super() 调用时才创建的,每个子类实例都有自己的一份,互不影响。

      js 复制代码
      const cat1 = new Cat('Tom');
      const cat2 = new Cat('Jerry');
      
      cat1.colors.push('green');
      
      console.log(cat1.colors); // ['red', 'blue', 'green']
      console.log(cat2.colors); // ['red', 'blue'] - 未受影响!
  3. 内置 super 关键字super 提供了简洁的方式来调用父类的构造函数和方法,代码更健壮。

  4. 类内部默认使用严格模式class 和模块的内部,默认就是严格模式 ('use strict'),所以不需要再显式声明,有助于编写更规范、更安全的代码。

7.2.2 缺点 :
  1. 并非新的继承机制:它本质上还是原型继承,对于不了解原型机制的开发者来说,可能会对一些底层行为感到困惑。它是一个强大的"语法糖",但不是一个全新的模型。

  2. 兼容性问题class 语法是 ES6 (2015年) 的标准,在一些非常老旧的浏览器(如 IE11)上不支持。但在现代前端开发中,通常会使用 Babel 等工具将其转换为 ES5 兼容的代码,所以这已经不是一个主要障碍。

相关推荐
立方世界2 小时前
【Leaflet.js实战】地图标记与弹窗:从基础到高级的完整实现
开发语言·javascript·ecmascript
saber_andlibert4 小时前
【Linux】深入理解Linux的进程(一)
linux·运维·服务器·开发语言·c++
你的电影很有趣5 小时前
lesson70:jQuery Ajax完全指南:从基础到4.0新特性及现代替代方案引言:jQuery Ajax的时代价值与演进
javascript·ajax·jquery
2503_928411565 小时前
9.26 数据可视化
前端·javascript·信息可视化·html5
yanqiaofanhua6 小时前
C语言自学--数据在内存中的存储
c语言·开发语言
知识分享小能手8 小时前
React学习教程,从入门到精通,React 单元测试:语法知识点及使用方法详解(30)
前端·javascript·vue.js·学习·react.js·单元测试·前端框架
计算机软件程序设计9 小时前
基于Python的二手车价格数据分析与预测系统的设计与实现
开发语言·python·数据分析·预测系统
꒰ঌ 安卓开发໒꒱11 小时前
Java面试-并发面试(二)
java·开发语言·面试
Min;11 小时前
cesium-kit:让 Cesium 开发像写 UI 组件一样简单
javascript·vscode·计算机视觉·3d·几何学·贴图