面试题答案
一键面试利用原型链实现继承的原理
在 JavaScript 中,每个对象都有一个 __proto__
属性,指向它的原型对象。当访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript 会沿着原型链向上查找,直到找到该属性或者到达原型链的顶端(null
)。通过将一个构造函数的原型对象设置为另一个构造函数的实例,就可以实现继承。
代码示例(未优化)
// 父构造函数
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name +'makes a sound.');
};
// 子构造函数
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 使用原型链实现继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
// 创建实例
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak();
可能出现的性能问题
- 原型对象共享:所有实例共享原型对象上的属性和方法,如果在原型对象上定义了可变类型(如数组、对象),一个实例对该属性的修改会影响到其他实例。
- 不必要的属性查找:原型链过长会导致属性查找变慢,因为每次查找属性都需要沿着原型链向上遍历。
优化策略
- 使用
Object.create()
:Object.create()
方法创建一个新对象,使用现有的对象来提供新创建对象的__proto__
。这样可以避免不必要的实例化。 - 寄生组合式继承:结合
Object.create()
和借用构造函数,既避免了原型对象共享问题,又保持了原型链的优势。
优化后的代码示例(寄生组合式继承)
// 父构造函数
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name +'makes a sound.');
};
// 子构造函数
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 创建原型对象
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
// 实现继承
inheritPrototype(Dog, Animal);
// 创建实例
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak();
通过这种方式,既利用了原型链实现继承,又避免了常见的性能问题。