一、原型链继承
特点: 实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
将子类的原型指向父类构造函数的实例对象
修改简单数据类型不会造成数据共享,修改引用类型是会造成child实例之间的数据共享
js
function Parent() {
this.name = 'abc';
this.info = {
weight: 180,
height: 180
}
}
function Child() {
this.age = 12
}
const parent1 = new Parent()
Child.prototype = parent1 //将子类的原型指向了父类构造函数的实例对象
const child1 = new Child()
const child2 = new Child()
child1.name = 'xym'
child2.info.height = 100
console.log(child1)
console.log('---')
console.log(child2);
缺点: 1、新实例无法向父类构造函数传参。
2、所有新实例都会共享父类实例的引用类型属性。当原型上有一个引用类型的属性时,所有实例都共享的是该引用类型属性的 同一份引用。
二、借用构造函数继承
js
function Parent() {
this.name = 'abc';
this.info = ['hehe','haha']
}
Parent.prototype.say = function (){
console.log('111'); //缺点:无法继承父类原型上的方法
}
function Child() {
this.age = 12
Parent.call(this) //在子类构造函数中调用父类构造函数,父类中的this绑定到了子类实例上
}
const child1 = new Child()
const child2 = new Child()
child1.info = ['aaa']
console.log(child1)
console.log('---')
console.log(child2);
console.log(new Parent())
重点: 用 .call()和.apply() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复 制))
特点:
1、解决了原型链继承引用属性共享的问题。
2、可以继承多个构造函数属性(call 多个)。
js
function Father() {
this.fromFather = 'father';
}
function Mother() {
this.fromMother = 'mother';
}
function Child() {
Father.call(this);
Mother.call(this);
}
3 、在子实例中可向父实例传参。
js
function Parent (age){
this.age = age
}
function Son (name, age){
this.name = name
Parent.call(this, age) //可以在调用父构造函数时传参
}
const son1 = new Son('xym', 21)
console.log(son1);
缺点: 1、只能继承父类构造函数的属性,不能继承原型上的方法
2、无法实现构造函数的复用。(每次用每次都要重新调用)
三、组合继承(组合原型链继承和借用构造函数继承)
js
function Parent (){
this.name = ['aaa','bbb']
}
function Child (){
this.age = 21
Parent.call(this) //第二次调用Parent
}
Child.prototype = new Parent() // 第一次调用Parent
var child1 = new Child()
var child2 = new Child()
child1.name = ['aaa'] //为什么解决了共享属性的问题?在第二次调用Parent
//时,this.name覆盖掉了第一次用原型链继承的this.name
console.log(child1, child2);
重点: 结合了两种模式的优点,传参和复用
缺点: 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、寄生组合继承
js
function Parent(){
this.name = ['aaa','bbb']
}
function Child(){
Parent.call(this)
}
Parent.prototype.sing = function (){ console.log('111');
}
Child.prototype.dance = function (){ console.log('222');
}
//Object.create()为ES5新语法,如果不兼容,就自己手写实现
if(!Object.create){
Object.create = function(proto){
function F (){}
F.prototype = proto //F的prototype赋值为proto对象
return new F() // new 创建一个新实例对象,将这个对象的原型(__proto__)指向proto对象
}
}
Child.prototype = Object.create(Parent.prototype)
// Object.create() 创建一个空对象
// 将空对象的__proto__(原型)指向Parent.prototype
// 将新对象赋值给 Child.prototype
// 缺点:这里Child.prototype 重新赋值会把原本Child原型上的方法覆盖掉
console.log(new Child()); 这个缺点该怎么解决???
解决方案:
js
function Parent() {
this.name = ['aaa', 'bbb'];
}
function Child() {
Parent.call(this);
}
Parent.prototype.sing = function () { console.log('111'); };
// 先建立继承关系
Child.prototype = Object.create(Parent.prototype);
// 再添加子类方法
Child.prototype.dance = function () { console.log('222'); };
// 修复constructor指向
Child.prototype.constructor = Child;
console.log(new Child());
// 现在可以访问:
new Child().dance()
五、class 类实现继承
通过 extends 实现继承, super调用父类构造函数。子类实例可以直接调用父类的方法,子类class内部还可以直接调用父类方法
js
// 父类
class ParentClass {
constructor(name) {
this.name = name;
}
// 父类的一个方法
greet() {
console.log(`Hello from ParentClass! My name is ${this.name}.`);
}
}
class ChildClass extends ParentClass {
constructor(name, age) {
super(name); //继承属性
this.age = age;
}
// 子类自己的方法
introduce() {
console.log(`I am ${this.name} and I am ${this.age} years old.`);
this.greet()
}
}
const child = new ChildClass("Alice", 5);
// 子类实例可以直接调用父类的方法
child.greet(); // 输出: Hello from ParentClass! My name is Alice.
child.introduce(); // 输出: I am Alice and I am 5 years old.