面试题答案
一键面试JavaScript 原型链工作原理
- 原型对象:每个函数都有一个
prototype
属性,它指向一个对象,这个对象就是该函数所创建实例的原型对象。例如:
function Person() {}
console.log(Person.prototype);
- 实例与原型关系:当使用
new
关键字调用构造函数创建实例时,实例内部会有一个[[Prototype]]
(在现代 JavaScript 中可以通过__proto__
访问,但__proto__
已不推荐使用,应使用Object.getPrototypeOf()
)属性,它指向构造函数的原型对象。
let person = new Person();
console.log(person.__proto__ === Person.prototype);
- 属性查找:当访问实例的属性时,首先会在实例自身查找,如果找不到,则会沿着
[[Prototype]]
链向上查找,直到找到该属性或者到达原型链顶端(Object.prototype
,其[[Prototype]]
为null
)。
Person.prototype.name = 'default';
console.log(person.name);
通过原型链实现继承
- ES5 实现继承方式
- 原型链继承:
- 原理:让子类型的原型指向超类型的实例。
- 代码示例:
- 原型链继承:
function Animal() {
this.species = 'animal';
}
function Dog() {}
Dog.prototype = new Animal();
let dog = new Dog();
console.log(dog.species);
- **优点**:简单,易于理解,子类型可以访问超类型原型上的属性和方法。
- **缺点**:引用类型的属性被所有实例共享;在创建子类型实例时,无法向超类型构造函数传参。
- **构造函数继承**:
- **原理**:在子类型构造函数内部调用超类型构造函数,通过 `call` 或 `apply` 方法改变 `this` 指向。
- **代码示例**:
function Animal(name) {
this.name = name;
}
function Dog(name) {
Animal.call(this, name);
}
let dog = new Dog('Buddy');
console.log(dog.name);
- **优点**:可以解决原型链继承中引用类型属性共享问题,且可以向超类型构造函数传参。
- **缺点**:只能继承实例属性和方法,不能继承原型属性和方法。
- **组合继承**:
- **原理**:结合原型链继承和构造函数继承。先通过原型链继承超类型的原型属性和方法,再在子类型构造函数中调用超类型构造函数,继承实例属性。
- **代码示例**:
function Animal(name) {
this.name = name;
this.friends = ['cat'];
}
Animal.prototype.speak = function() {
console.log('I am an animal');
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
let dog1 = new Dog('Max');
let dog2 = new Dog('Sam');
dog1.friends.push('rabbit');
console.log(dog1.friends);
console.log(dog2.friends);
dog1.speak();
- **优点**:融合了原型链继承和构造函数继承的优点,既可以继承实例属性,又可以继承原型属性和方法。
- **缺点**:超类型构造函数会被调用两次,一次在 `new Animal()` 创建原型时,一次在 `Animal.call(this)` 时。
- **寄生组合式继承**:
- **原理**:通过创建一个空函数 F,让其原型指向超类型的原型,然后创建 F 的实例作为子类型的原型,最后修正子类型原型的 `constructor` 属性。
- **代码示例**:
function inheritPrototype(subType, superType) {
let F = function() {};
F.prototype = superType.prototype;
subType.prototype = new F();
subType.prototype.constructor = subType;
}
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log('I am an animal');
};
function Dog(name) {
Animal.call(this, name);
}
inheritPrototype(Dog, Animal);
let dog = new Dog('Tom');
dog.speak();
- **优点**:解决了组合继承中构造函数被调用两次的问题,是最理想的继承方式。
- **缺点**:实现相对复杂。
2. ES6 实现继承方式(类继承)
- 原理:使用 class
和 extends
关键字,内部实现依然基于原型链,但语法更加简洁和清晰。
- 代码示例:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log('I am an animal');
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
}
let dog = new Dog('Leo');
dog.speak();
- **优点**:语法糖,代码更简洁,易读性好;明确了类的概念,包括构造函数、方法等;通过 `super` 关键字方便地调用超类的构造函数和方法。
- **缺点**:本质上还是基于原型链,部分问题依然存在,如对于一些旧环境兼容性不如 ES5 继承方式(需转码工具如 Babel 辅助)。