JS原型链继承

时间:2023-02-17 15:04:48
function Parent() {
this.name = 'red';
this.eat = function () {
console.log('走这里了吗')
}
}
Parent.prototype.start = function () {
console.log(this.name)
}
function Children() {}

Children的原型对象=Parent的原型对象

缺点: 实例对象p指向Parent; 实例对象p不能调用构造函数Parent的方法和属性,只能调用Parent的原型对象的方法和属性

//仅继承了Parent原型对象上的方法和属性
Children.prototype = Parent.prototype
let p = new Children();
console.log(p.name)//undefined
console.log(p.eat)//undefined
console.log(p.start)
//ƒ () {
// console.log(this.name);
//}
console.log(p.constructor)
//ƒ Parent() {
// this.name = 'red';
// this.eat = function () {
// console.log('走这里了吗');
// };
}

Children.prototype = new Parent()

缺点: 实例对象p指向Parent

Children.prototype = new Parent()//继承了Parent的原型对象prototype的方法和属性以及constructor
let p = new Children();
console.log(p)//Children {}
console.log(p.name)//red
console.log(p.start)
//ƒ () {
// console.log(this.name);
// }
console.log(p.eat)
// ƒ () {
// console.log('走这里了吗');
// }
console.log(p.constructor)
// ƒ Parent() {
// this.name = 'red';
// this.eat = function () {
// console.log('走这里了吗');
// };
// }

Children.prototype = new Parent()

Children.prototype.constructor = Children;

Children.prototype = new Parent()//继承了Parent的原型对象prototype的方法和属性以及constructor
Children.prototype.constructor = Children;
let p = new Children();
console.log(p.constructor);
// ƒ Children() {}

缺点

  1. 如果父构造函数中含有引用类型的属性,当某个实例改变了该引用类型的属性,则所有实例共享该实例(基本数据类型不会)
  2. 创建Children实例无法传参
function Parent() {
this.name = 'red';
this.arr = [1, 2, 3];
this.eat = function () {
console.log('走这里了吗')
}
}
Parent.prototype.start = function () {
console.log(this.name)
}
function Children() {
}
Children.prototype = new Parent();
Children.prototype.constructor = Children
let p1 = new Children()
let p2 = new Children()
console.log(p1.arr)
p1.name = 'blue';
p1.arr.pop();
console.log(p1.name)//blue
console.log(p2.name)//red
console.log(p1.arr)//[2,3]
console.log(p2.arr)//[2,3]

解决缺点一(构造函数继承)

某实例改变父构造函数中引用类型属性的值,所有实例共享修改后的引用类型的属性值

function Parent() {
this.arr = [1, 2, 3];
}
Parent.prototype.start = function () {
}
function Children() {
Parent.apply(this)
//相当于执行了 this.arr = [1, 2, 3];

let p1 = new Children()
let p2 = new Children()
p1.arr.pop()
console.log(p1.arr)//[2,3]
console.log(p2.arr)//[1,2,3]

解决缺点二(构造函数继承)

生成实例时不能传参的问题

function Parent(color,name) {
this.color = color;
this.name = name;
this.arr = [1, 2, 3];
this.eat = function () {
console.log('走这里了吗')
}
}
Parent.prototype.start = function () {
// console.log(this.name,'prototype')
}
function Children() {
console.log(arguments,'arguments')
//call传参为多个数值
//apply传值为数组
// Parent.apply(this,arguments)
Parent.call(this,...arguments)
}
let p1 = new Children()
let p2 = new Children('red','zhangsan')
console.log(p2.color)//red
console.log(p2.name)//zhangsan
console.log(p1.eat===p2.eat)//false

再次出现的问题

我们每次生成实例的时候都会重新生成构造函数的属性和方法

function Children() {
//call传参为多个数值
//apply传值为数组
// Parent.apply(this,arguments)
Parent.call(this,...arguments)
}

解决重新生成实例时生成构造函数的属性和方法(组合继承:原型链继承+构造函数继承)

  • 原型链继承:
  1. 实例改变引用类型的属性,所有实例共享
  2. 不能传参。
  • 构造函数继承:
  1. 解决了以上原型链的两个问题
  2. 但是又出现了新的问题,每次生成实例都会重新生成一份构造函数的属性和方法
function Parent(color,name) {
this.color = color;
this.name = name;
this.arr = [1, 2, 3];
this.eat = function () {
console.log('走这里了吗')
}
}
Parent.prototype.start = function () {
console.log(this.name,'prototype')
}
function Children() {
console.log(arguments,'arguments')
//call传参为多个数值
//apply传值为数组
// Parent.apply(this,arguments)
// Parent.call(this,...arguments)
// Parent.apply(this,Array.prototype.slice.call(arguments, 1))
Parent.apply(this,[].slice.call(arguments, 1))
}
Children.prototype = new Parent();
Children.prototype.constructor = Children
let p1 = new Children(1,'red','zhangsan')
let p2 = new Children(2,'red','zhangsan')
console.log(p1.eat())
console.log(p2.eat())
console.log(p1.start===p2.start) //true

解决了三个痛点:

  1. 不可传参数
  2. 多占内存
  3. 改变引用类型属性,所有实例共享

寄生组合继承(再次出现问题,构造函数调用两次)

1. Parent.call(this,...arguments)
...
2.Children.prototype = new Parent();