class 的继承和 prototype 继承 是完全一样 ?

99 阅读2分钟

类继承和原型继承在JavaScript中是两种不同的继承机制, 在概念、实现方式和适用场景上都有显著的区别。

总结

主要区别

  • 概念:类继承强调的是类之间的层次关系,而原型继承强调的是对象之间的直接关联。

  • 实现方式:类继承依赖于classextends,而原型继承依赖于prototype和原型链。

  • 灵活性:原型继承更灵活,可以动态地修改对象的行为;类继承则更适用于需要固定类层次结构的场景

适用场景

类继承的结构清晰,通过类继承,可以实现多层继承和复杂的层次关系,提供更强的支持和可读性,适合需要长期维护的大型软件项目。

  • 类继承适合需要明确类层次结构和静态类型的场景,例如需要多态性和接口约束的复杂系统。

  • 原型继承适合需要动态扩展和修改对象行为的场景,例如简单的脚本开发或需要快速实现功能的项目

原型链继承对于对象功能的扩展非常直观,适合脚本型代码或快速原型开发。例如,在JavaScript中,通过原型链继承可以轻松地为对象添加新的属性和方法。

const Person = {
    say() {
        console.log(`hello,my name is ${this.name}.`);
    }
};

const july = Object.create(Person);  // july 原型 => Person
july.name = "july";
july.say();  // hello,my name is july.

类继承和多态性是通过多种方式实现的。

ES6的class关键字和extendssuper等语法糖使得类继承更加清晰和易于理解。 而多态性(同一行为具有多个不同表现形式的能力)则主要通过方法重写和鸭子类型来实现,使得代码更加灵活和可扩展:

  1. 方法重写(Method Overriding) :子类可以重写父类的方法,使得同样的接口在不同的子类中表现出不同的行为。这是多态性最常见和直接的实现方式。
//子类可以通过'extends'继承父类,并使用'super'关键字调用父类的构造函数和方法
//在这个示例中,‘Dog‘类继承了‘Animal‘类,并重写了‘speak‘方法。
//‘super(name)‘调用了父类的构造函数,确保‘name‘属性被正确初始化
class Animal {
    constructor(name) {
        this.name = name;
    }
 
    speak() {
        console.log(‘${this.name} makes a sound.‘);
    }
}
 
 
class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 调用父类的构造函数
        this.breed = breed;
    }
 
    speak() {//重写
        console.log(‘${this.name} barks.‘);
    }
}
 
 
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // 输出:Buddy barks.
  1. 鸭子类型(Duck Typing)() :通过检查对象是否具有特定的方法或属性来判断其类型,而不是依赖其具体的类型。这种方式使得代码更加灵活,避免了类型检查的冗余。

【注】:鸭子类型有这样一个故事:国王要组建一个100只鸭子组成的合唱团,找到99只鸭子了,还差一只。最后发现有一只非常特别的鸡,叫声跟鸭子一模一样,最后把这只鸡加入了合唱团。通俗的说,“如果它走起来像鸭子,叫起来也是鸭子,那么它就是鸭子”。鸭子类型指导思想是说“应该关注对象的行为,而不是对象的本身。也就是说要关注HAS-A,而不是IS-A”。(注意单词has 和 is)

  1. 抽象类和抽象函数:通过定义抽象类和抽象函数,强制子类实现特定的方法。这种方式可以确保所有子类都具有相同的行为。

原型继承

【注】:__proto__ 属性,它是对象所独有的,可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。 它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到原型链顶端null(可以理解为原始人),此时若还没找到,则返回undefined(就不再找了,到此为止),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链

  1. 基于构造函数和原型对象的实现
// 定义一个构造函数 父类
function Person(name) {
  this.name = name;
}

// 在父类 原型对象上定义一个方法
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
}

// 定义一个子类
function Student(name, grade) {
  Person.call(this, name);
  this.grade = grade;
}

// 将子类的原型对象指向父类的实例
Student.prototype = new Person();

// 在子类的原型对象上定义一个方法
Student.prototype.sayGrade = function() {
  console.log(`My grade is ${this.grade}.`);
}

// 创建一个Person对象
const person = new Person('John');
person.sayHello(); // Hello, my name is John.

// 创建一个Student对象
const student = new Student('Mike', 8);
student.sayHello(); // Hello, my name is Mike.
student.sayGrade();//My grade is 8.

【先学到这里吧】