在JavaScript中,实现继承的方法主要有:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、和寄生组合式继承。这些方法各具特点,适用于不同的继承需求场景。其中,原型链继承是最基本的继承方式,它通过重写子类的原型对象,让其指向父类的一个实例,从而实现继承。原型链继承的核心在于利用原型让一个引用类型继承另一个引用类型的属性和方法。
一、原型链继承
在JavaScript中,每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,同时也包含了一个原型链。当尝试访问一个对象的某个属性时,如果该对象本身不存在这个属性,则会查找其原型对象是否有这个属性,这一查找过程会沿着原型链向上进行,直至找到属性或达到原型链的顶端。原型链继承正是基于这一机制。
要实现原型链继承,可以将子类的原型设置为父类的一个实例:
function Parent() {
this.parentProperty = true;
}
Parent.prototype.getParentProperty = function() {
return this.parentProperty;
};
function Child() {
this.childProperty = false;
}
// 继承自Parent
Child.prototype = new Parent();
var instance = new Child();
console.log(instance.getParentProperty()); // true
这种方法简单易行,但存在两大缺点:一是原型中包含的引用值会被所有实例共享;二是子类型在实例化时不能给父类型的构造函数传参。
二、构造函数继承
构造函数继承是通过在子类的构造函数中调用父类的构造函数来实现的,借助call()
或apply()
可以在新创建的对象上执行构造函数:
function Parent(name) {
this.name = name || 'Parent';
this.colors = ['red', 'blue', 'green'];
}
function Child(name) {
// 继承自Parent,并传参
Parent.call(this, name);
}
var instance1 = new Child('Child1');
var instance2 = new Child('Child2');
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
console.log(instance2.colors); // ['red', 'blue', 'green']
console.log(instance1.name); // Child1
console.log(instance2.name); // Child2
这种继承方式解决了原型链继承中引用类型共享的问题,并且可以在子类型构造函数中向父类型构造函数传递参数。但是,这种方法无法继承父类原型上的属性和方法。
三、组合继承
组合继承结合了原型链继承和构造函数继承的优点,即通过构造函数继承属性,通过原型链的混成形式继承方法。这是JavaScript中最常用的继承模式。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
// 继承属性
Parent.call(this, name);
// 新属性
this.age = age;
}
// 继承方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
var instance = new Child('Nicholas', 29);
console.log(instance.getName()); // Nicholas
console.log(instance.getAge()); // 29
组合继承避免了原型链和构造函数继承的缺点,同时获得了二者的优点。然而,这种类型的继承也存在效率问题,父类构造函数会被执行两次,一次是设置子类型原型的时候,一次是在子类型构造函数内部。
四、原型式继承
Douglas Crockford提出了原型式继承,这种继承方式不必预先定义构造函数,而是直接通过克隆一个现有对象来实现继承。ECMAScript 5通过新增Object.create()
方法标准化了原型式继承。
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
这种方法的缺点和原型链继承一样,包含引用类型的属性值始终会被所有实例共享。
五、寄生式继承
寄生式继承与原型式继承紧密相关,它是一种增强对象的方法,即在原型式继承的基础上再添加方法或属性。它适用于主要关注对象而不是自定义类型和构造函数的场景。
function createAnother(original){
var clone = Object.create(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式来增强这个对象
console.log('hi');
};
return clone;
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'
这种方法虽然解决了给对象添加函数的问题,但与原型式继承一样,引用类型的属性会被所有实例共享。
六、寄生组合式继承
寄生组合式继承是对组合继承的进一步优化。它通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思想是不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
function SuperType(name){
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var instance = new SubType('Nicholas', 29);
instance.sayName(); // Nicholas
instance.sayAge(); // 29
寄生组合式继承是实现基类型继承的最有效方式。它只调用了一次SuperType构造函数,并且避免了在SubType.prototype上创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof和isPrototypeOf。这种模式是目前在实现继承的最理想的方法。
相关问答FAQs:
1. JavaScript中通过原型链实现继承的方法有哪些?
在JavaScript中,可以使用原型链来实现对象之间的继承。通过定义一个父对象的构造函数,并将其原型对象设置为一个新的实例对象,然后再通过子对象的构造函数来创建新的实例,实现继承。子对象通过原型链继承了父对象的属性和方法。
2. JavaScript中通过构造函数实现继承的方法有哪些?
除了原型链继承外,还可以利用构造函数来实现继承。在JavaScript中,可以通过在子对象的构造函数中调用父对象的构造函数,使用call
或apply
方法来实现继承。这样,子对象就可以继承父对象的属性和方法。
3. JavaScript中通过混合继承实现继承的方法有哪些?
混合继承是指同时利用原型链继承和构造函数继承的方法来实现继承。通过将父对象的原型对象复制一份给子对象的原型对象,并调用父对象的构造函数来实现继承。这样,子对象既能够通过原型链继承父对象的属性和方法,又能够在自身的构造函数中定义自己的属性和方法。这种继承方法可以更灵活地满足不同的需求。