类继承和原型继承在JavaScript中是两种不同的继承机制, 在概念、实现方式和适用场景上都有显著的区别。
总结
主要区别
-
概念:类继承强调的是类之间的层次关系,而原型继承强调的是对象之间的直接关联。
-
实现方式:类继承依赖于
class和extends,而原型继承依赖于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关键字和extends、super等语法糖使得类继承更加清晰和易于理解。
而多态性(同一行为具有多个不同表现形式的能力)则主要通过方法重写和鸭子类型来实现,使得代码更加灵活和可扩展:
- 方法重写(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.
- 鸭子类型(Duck Typing)() :通过检查对象是否具有特定的方法或属性来判断其类型,而不是依赖其具体的类型。这种方式使得代码更加灵活,避免了类型检查的冗余。
【注】:鸭子类型有这样一个故事:国王要组建一个100只鸭子组成的合唱团,找到99只鸭子了,还差一只。最后发现有一只非常特别的鸡,叫声跟鸭子一模一样,最后把这只鸡加入了合唱团。通俗的说,“如果它走起来像鸭子,叫起来也是鸭子,那么它就是鸭子”。鸭子类型指导思想是说“应该关注对象的行为,而不是对象的本身。也就是说要关注HAS-A,而不是IS-A”。(注意单词has 和 is)
- 抽象类和抽象函数:通过定义抽象类和抽象函数,强制子类实现特定的方法。这种方式可以确保所有子类都具有相同的行为。
原型继承
【注】:__proto__ 属性,它是对象所独有的,可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到原型链顶端null(可以理解为原始人),此时若还没找到,则返回undefined(就不再找了,到此为止),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。
- 基于构造函数和原型对象的实现
// 定义一个构造函数 父类
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.
【先学到这里吧】