JS 常见的 6 种继承方式

目录

  • 原型链继承
  • 构造函数继承(借助 call)
  • 组合继承(前两种组合)
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承--最优

原型链继承

原型链继承涉及的是构造函数、原型、实例化对象,这三者之前存在一定的关系:

● 每一个构造函数都有一个原型对象(prototype)

● 原型对象中包含一个指向构造函数的指针(constructor),指向对应的构造函数

● 实例中包含一个原型对象的指针([[Prototype]],这个内部属性无法直接访问,在FF,Chrome等浏览器可以通过__proto__得到)

function Person(){
	this.name="张三"
	this.arr=[1,2,3]
}
function Teacher(){
	this.teach="chinese"
}
Teacher.prototype=new Person()
console.log(new Teacher())

JS 常见的 6 种继承方式_第1张图片
如果构造函数创建多个对象,属性被多个对象共享,如果属性是引用类型,那个修改一个对象的属性,会导致其他对象的这个属性也会被修改(如果是直接赋值对象的属性,则不会触发其他对象上该属性的修改)

//原型链继承
function Person(){
  this.name="张三"
  this.arr=[1,2,3]
}
function Teacher(){
  this.teach="chinese"
}
Teacher.prototype=new Person()
// console.log(new Teacher())
//原型继承缺点:构造函数创建多个对象,对象的属性如果为引用类型,修改了该属性,其他对象的属性也会改变
let p1=new Teacher() 
// console.log(p1)     // {name:"张三",arr:[1,2,3]}
let p2=new Teacher()
p2.arr.push(4)
console.log(p1.arr)     // [1,2,3,4]
console.log(p2.arr)     // [1,2,3,4]

修改了p1的arr属性,为什么导致了p2的arr属性的改变?
因为两个实例使用的是同一个原型对象(Teacher.prototype),它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变化。

缺点:
1.原型对象如果有引用类型的属性,会被所有的示例对象所共享,容易引起数据修改混乱
2. 创建实例化对象的时候,不能向父类构造函数不能传递参数

构造函数继承(借助 call)

function Person(){
	this.name="张三"
	this.arr=[1,2,3]
}
Person.prototype.eating=function(){
	console.log('吃东西')
}
function Student(){
	Person.call(this)
	this.study={subject:'chinese'}
}
let s1=new Student()
let s2=new Student()
s1.study.subject='english'
console.log(s1.study)//{subject:'ending'}
console.log(s2.study)//{subject:'chinese'}
s1.eating()

这种方式实现了不同实例间数据的隔离,子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端,但问题是,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。
缺点:无法使用父类原型链上的方法

组合继承(前两种组合)

function Person() {
  this.name = '张三'
  this.arr = [1, 2, 3]
}
Person.prototype.eating = function () {
  console.log('吃东西')
}
function Student() {
  //第二次调用Person()
  Person.call(this)
  this.study = { subject: 'chinese' }
}
//第一次调用Person()
Student.prototype = new Person()
console.log(Student.prototype.constructor) //Person
//需要手动指定constructor为指向自己的构造函数,不然Student.prototype.constructor就为Person了,这样是错误的
Student.prototype.constructor=Student
console.log(Student.prototype.constructor)
let s1 = new Student()
let s2 = new Student()
s1.study.subject = 'english'
console.log(s1.study)
console.log(s1.name)
console.log(s2.study)
console.log(s1)
s1.eating()

JS 常见的 6 种继承方式_第2张图片
但是这里又增加了一个新问题:通过注释我们可以看到 Person 执行了两次,第一次是改变Student 的 prototype 的时候,第二次是通过 call 方法调用 Person 的时候,那么 Person 多构造一次就多进行了一次性能开销,这显然也是不太好的
缺点:
● 构造函数会被调用两次(一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候. ),造成了资源的浪费
● 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中

原型式继承

原型式继承是使用 ES5 里面的 Object.create 方法来实现继承.
Object.create 这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

let people = {
  name: '拉拉',
  friends: ['丁丁', '迪西', '小波'],
  getName: function () {
    return this.name
  },
}
let p1 = Object.create(people, {
  property1: {
    value: true,
    writable: true,
    configurable:true,
    enumerable:true
  },
})
let p2 = Object.create(people)
p1.name = 'jerry'
console.log(p1.name) //jerry
console.log(p1.getName()) //jerry
console.log(p2.name) //拉拉
p1.friends.push('jerry')
console.log(p1.friends) // ['丁丁', '迪西', '小波', 'jerry']
console.log(p2.friends) //['丁丁', '迪西', '小波', 'jerry']

通过上面代码,可以看到Object.create可以实现对象的继承,除了可以继承people的属性外,还可以使用继承的对象的方法(getName),但是也会出现引用数据类型“共享”的问题:多个实例的引用类型属性指向相同的内存,存在篡改的可能
缺点:实例化对象数据共享问题

寄生式继承

使用原型式继承可以获得目标对象的浅拷贝,利用浅拷贝的能力进行一些方法的增加,这样的继承方式叫做寄生式继承,缺点其实和原型式继承一样,优点是在父类的继承上增加了更多的方法。

let people = {
	name: '拉拉',
	friends: ['丁丁', '迪西', '小波'],
	getName: function () {
		return this.name
	},
}
function clone(obj){
	//第二次调用people
	let cloneObj=Object.create(obj)
	cloneObj.getFriends=function(){
		return this.friends
	}
	return cloneObj
}
//第一次调用people
let p1 = clone(people) 
let p2 = Object.create(people)
p1.name = 'jerry'
console.log(p1.name) //jerry
console.log(p1.getName()) //jerry
console.log(p1.getFriends()) //['丁丁', '迪西', '小波']
console.log(p2.name) //拉拉
p1.friends.push('jerry')
console.log(p1.friends) // ['丁丁', '迪西', '小波', 'jerry']
console.log(p2.friends) //['丁丁', '迪西', '小波', 'jerry']

在上面代码上,就会发现p1可以直接使用getFriends方法,但是这个方法的缺点:
1.引用数据类型“共享”的问题:p1.friends改变导致p2.friends改变

寄生组合式继承–最优

结合第四种中提及的继承方式( Object.create 实现),解决普通对象的继承问题的 Object.create 方法,把前面的继承有点结合,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式.

function Person() {
  this.name = '张三'
  this.arr = [1, 2, 3]
}
Person.prototype.eating = function () {
  console.log('吃东西')
}
function Student() {
  Person.call(this) //第一次执行Person()进行构造
  this.study = { subject: 'chinese' }
}
Student.prototype.play = function () {
  console.log('玩游戏')
}
function clone(child,parent){
  //这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  child.prototype=Object.create(parent.prototype)
  child.prototype.constructor=child
}
clone(Student,Person)
let s1=new Student()
let s2=new Student()
console.log(s1.name)
console.log(s1.eating())
s1.arr.push(4)
console.log(s1.arr)
console.log(s2.arr)

以上就是在JavaScript中常见的几种继承方式。每种方式都有其适用场景和优缺点,开发者可以根据实际需求选择使用。

你可能感兴趣的:(javascript,原型模式,开发语言)