原型链和继承深入学习
原型(Prototype)
在 JavaScript 中,每个对象都有一个指向其原型对象的内部链接,这个链接就是原型链的基础。
原型对象
javascript
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
const person = new Person("JavaScript");
console.log(person.greet()); // "Hello, I'm JavaScript"原型链查找机制
javascript
const obj = {};
// obj 的原型是 Object.prototype
console.log(obj.toString); // [Function: toString]
// 查找顺序:
// 1. obj 自身属性
// 2. Object.prototype
// 3. nullproto vs prototype
javascript
function Person() {}
const person = new Person();
// __proto__ 是实例对象的属性,指向构造函数的 prototype
console.log(person.__proto__ === Person.prototype); // true
// prototype 是构造函数的属性
console.log(Person.prototype); // Person {}
// 原型链的终点
console.log(Object.prototype.__proto__); // nullWARNING
__proto__ 是已废弃的属性,建议使用 Object.getPrototypeOf() 和 Object.setPrototypeOf()。
构造函数
构造函数模式
javascript
function Car(brand, model) {
this.brand = brand;
this.model = model;
}
Car.prototype.start = function() {
return `${this.brand} ${this.model} started`;
};
const car = new Car("Tesla", "Model 3");
console.log(car.start()); // "Tesla Model 3 started"new 操作符的执行过程
javascript
// new 操作符做了以下事情:
// 1. 创建一个新对象
// 2. 将构造函数的 prototype 赋值给新对象的 __proto__
// 3. 执行构造函数,this 指向新对象
// 4. 返回新对象(如果构造函数没有返回对象)
function MyNew(constructor, ...args) {
const obj = {};
obj.__proto__ = constructor.prototype;
const result = constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}继承的实现方式
1. 原型链继承
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} is eating`;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 原型链继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
return `${this.name} is barking`;
};
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"DANGER
原型链继承的问题:
- 子类实例共享父类引用类型属性
- 无法向父类构造函数传参
2. 构造函数继承
javascript
function Animal(name) {
this.name = name;
this.colors = ["red", "blue"];
}
function Dog(name, breed) {
Animal.call(this, name); // 调用父构造函数
this.breed = breed;
}
const dog1 = new Dog("Buddy", "Golden");
const dog2 = new Dog("Max", "Labrador");
dog1.colors.push("green");
console.log(dog1.colors); // ["red", "blue", "green"]
console.log(dog2.colors); // ["red", "blue"] (独立)TIP
构造函数继承解决了引用类型共享的问题,但无法继承父类原型上的方法。
3. 组合继承(最常用)
javascript
function Animal(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Animal.prototype.eat = function() {
return `${this.name} is eating`;
};
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 继承方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
return `${this.name} is barking`;
};
const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"4. 原型式继承
javascript
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
const animal = {
name: "Animal",
colors: ["red", "blue"]
};
const dog = object(animal);
dog.name = "Dog";
dog.colors.push("green");
console.log(dog.name); // "Dog"
console.log(animal.colors); // ["red", "blue", "green"] (共享)5. 寄生式继承
javascript
function createDog(original) {
const clone = object(original);
clone.bark = function() {
return "Woof!";
};
return clone;
}6. 寄生组合式继承(最佳实践)
javascript
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} is eating`;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
inheritPrototype(Dog, Animal);
Dog.prototype.bark = function() {
return `${this.name} is barking`;
};
const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"TIP
寄生组合式继承是最理想的继承方式,只调用一次父构造函数,避免了不必要的属性创建。
ES6 类继承
javascript
class Animal {
constructor(name) {
this.name = name;
}
eat() {
return `${this.name} is eating`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
return `${this.name} is barking`;
}
}
const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"原型链检测
instanceof
javascript
function Animal() {}
function Dog() {}
Dog.prototype = new Animal();
const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // trueisPrototypeOf
javascript
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // trueObject.getPrototypeOf
javascript
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true总结
- 原型:每个对象都有原型,通过原型链查找属性和方法
- 构造函数:用于创建对象的函数,通过
new操作符调用 - 继承:多种实现方式,寄生组合式继承是最佳实践
- ES6 类:语法糖,底层仍基于原型链实现