1. 原型链继承
核心:让子类的原型指向父类的实例。
js
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayHi = function() {
console.log("Hi from Parent");
};
function Child() {}
Child.prototype = new Parent("Tom"); // 关键
const c1 = new Child();
c1.colors.push("green");
const c2 = new Child();
console.log(c2.colors); // ["red","blue","green"] ❌ 被共享了
console.log(c1.name); // "Tom"
console.log(c2.name); // "Tom"
问题出在哪?
关键是 调用 new Child()
时 ,并没有再执行 Parent
构造函数。
Parent("Tom")
只在 设置原型链的时候被调用了一次。- 之后无论你创建多少个
Child
实例,都会复用这个"已经带着 name=Tom 的对象"。 - 所以
Child
实例的name
永远都是"Tom"
,你没法在new Child("Jerry")
的时候传递参数给Parent
。
举例说明
vbscript
const c1 = new Child("Jerry");
console.log(c1.name); // ❌ 还是 "Tom",根本没用 "Jerry"
为什么?
因为 Child
构造函数里面压根没调用 Parent
,也就没法把 "Jerry"
传过去。
缺点:
- 父类实例属性(引用类型)会被所有子类实例共享。
- 不能向父类构造函数传参。
2. 借用构造函数继承(经典继承) ----解决原型继承 实例属性共享问题
核心:在子类构造函数里调用父类构造函数。
js
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function(){
console.log(this.name)
}
function Child(name) {
Parent.call(this, name); // 关键
}
const c1 = new Child("Tom");
c1.colors.push("green");
const c2 = new Child("Jerry");
console.log(c2.colors); // ["red","blue"] ✅ 独立
c2.sayName() // ❌ c2.sayName is not a function
优点:
- 解决了原型链继承共享引用属性的问题。
- 可以给父类构造函数传参。
缺点:
- 方法都在构造函数里定义,没法复用,浪费内存。// colors在每个子类实例上都有一份
- 不能继承父类原型上的方法
本质区别总结
- 原型链继承 :共享的是 父类实例对象 上的属性 → 所有子类实例共用同一个引用,
new Parent()
只执行了一次(在设置Child.prototype
时),所以只分配了一份存储空间。 - 构造函数继承 :每次实例化时重新执行父类构造函数 → 每个子类实例独立拥有自己的属性,
Parent.call(this)
在每个子类实例里都会执行一次,所以每次new Child()
都会分配一份新的存储空间。
3. 组合继承(原型链 + 构造函数) ✅ 常用
核心:结合两者优点。
js
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayHi = function() {
console.log("Hi, I am " + this.name);
};
function Child(name, age) {
Parent.call(this, name); // 借用构造函数
this.age = age;
}
Child.prototype = new Parent(); // 原型链
Child.prototype.constructor = Child;
const c1 = new Child("Tom", 18);
c1.sayHi(); // Hi, I am Tom
优点:
- 既能复用方法,又能独立拥有属性。
缺点:
- 父类构造函数会被调用两次(一次在
Parent.call
,一次在new Parent()
)。
4. 寄生组合继承(最推荐的 ES5 写法)
js
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHi = function() {
console.log(`I am ${this.name}`);
};
function Dog(name) {
Animal.call(this, name);
}
// 用 Object.create 避免多次调用 Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const d = new Dog("Lucky");
d.sayHi(); // I am Lucky
🔹 特点:
- 完美解决了组合继承的缺点。
- ES5 最佳实践。
5. ES6 class 继承(语法糖)
js
class Animal {
constructor(name) {
this.name = name;
}
sayHi() {
console.log("I am " + this.name);
}
}
class Dog extends Animal {
constructor(name, color) {
super(name); // 调用父类构造函数,相当于 Animal.call(this, name)
this.color = color;
}
bark() {
console.log("Woof!");
}
}
const d = new Dog("Lucky", "white");
d.sayHi(); // I am Lucky
d.bark(); // Woof!
super(name)
的作用:
- 调用父类构造函数
Animal(name)
。 - 把返回的结果绑定到当前子类实例的
this
。 🔹 特点:
- 语法更简洁直观。
- 内部还是基于原型链。